/*************************************************************************** copyright : (C) 2003-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 "collectionfieldsdialog.h" #include "collection.h" #include "field.h" #include "collectionfactory.h" #include "gui/stringmapdialog.h" #include "tellico_kernel.h" #include "translators/tellico_xml.h" #include "tellico_utils.h" #include "tellico_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Tellico::FieldListBox; using Tellico::CollectionFieldsDialog; FieldListBox::FieldListBox(QListBox* listbox_, Data::FieldPtr field_) : GUI::ListBoxText(listbox_, field_->title()), m_field(field_) { } FieldListBox::FieldListBox(QListBox* listbox_, Data::FieldPtr field_, QListBoxItem* after_) : GUI::ListBoxText(listbox_, field_->title(), after_), m_field(field_) { } CollectionFieldsDialog::CollectionFieldsDialog(Data::CollPtr coll_, QWidget* parent_, const char* name_/*=0*/) : KDialogBase(parent_, name_, false, i18n("Collection Fields"), Help|Default|Ok|Apply|Cancel, Ok, false), m_coll(coll_), m_defaultCollection(0), m_currentField(0), m_modified(false), m_updatingValues(false), m_reordered(false), m_oldIndex(-1) { QWidget* page = new QWidget(this); setMainWidget(page); QHBoxLayout* topLayout = new QHBoxLayout(page, 0, KDialog::spacingHint()); QGroupBox* fieldsGroup = new QGroupBox(1, Qt::Horizontal, i18n("Current Fields"), page); topLayout->addWidget(fieldsGroup, 1); m_fieldsBox = new QListBox(fieldsGroup); m_fieldsBox->setMinimumWidth(150); Data::FieldVec fields = m_coll->fields(); for(Data::FieldVec::Iterator it = fields.begin(); it != fields.end(); ++it) { // ignore ReadOnly if(it->type() != Data::Field::ReadOnly) { (void) new FieldListBox(m_fieldsBox, it); } } connect(m_fieldsBox, SIGNAL(highlighted(int)), SLOT(slotHighlightedChanged(int))); QHBox* hb1 = new QHBox(fieldsGroup); hb1->setSpacing(KDialog::spacingHint()); m_btnNew = new KPushButton(i18n("New Field", "&New"), hb1); m_btnNew->setIconSet(BarIcon(QString::fromLatin1("filenew"), KIcon::SizeSmall)); QWhatsThis::add(m_btnNew, i18n("Add a new field to the collection")); m_btnDelete = new KPushButton(i18n("Delete Field", "&Delete"), hb1); m_btnDelete->setIconSet(BarIconSet(QString::fromLatin1("editdelete"), KIcon::SizeSmall)); QWhatsThis::add(m_btnDelete, i18n("Remove a field from the collection")); connect(m_btnNew, SIGNAL(clicked()), SLOT(slotNew()) ); connect(m_btnDelete, SIGNAL(clicked()), SLOT(slotDelete())); QHBox* hb2 = new QHBox(fieldsGroup); hb2->setSpacing(KDialog::spacingHint()); m_btnUp = new KPushButton(hb2); m_btnUp->setPixmap(BarIcon(QString::fromLatin1("up"), KIcon::SizeSmall)); QWhatsThis::add(m_btnUp, i18n("Move this field up in the list. The list order is important " "for the layout of the entry editor.")); m_btnDown = new KPushButton(hb2); m_btnDown->setPixmap(BarIcon(QString::fromLatin1("down"), KIcon::SizeSmall)); QWhatsThis::add(m_btnDown, i18n("Move this field down in the list. The list order is important " "for the layout of the entry editor.")); connect(m_btnUp, SIGNAL(clicked()), SLOT(slotMoveUp()) ); connect(m_btnDown, SIGNAL(clicked()), SLOT(slotMoveDown())); QVBox* vbox = new QVBox(page); vbox->setSpacing(KDialog::spacingHint()); topLayout->addWidget(vbox, 2); QGroupBox* propGroup = new QGroupBox(1, Qt::Horizontal, i18n("Field Properties"), vbox); QWidget* grid = new QWidget(propGroup); // (parent, nrows, ncols, margin, spacing) QGridLayout* layout = new QGridLayout(grid, 4, 4, 0, KDialog::spacingHint()); int row = -1; QLabel* label = new QLabel(i18n("&Title:"), grid); layout->addWidget(label, ++row, 0); m_titleEdit = new KLineEdit(grid); layout->addWidget(m_titleEdit, row, 1); label->setBuddy(m_titleEdit); QString whats = i18n("The title of the field"); QWhatsThis::add(label, whats); QWhatsThis::add(m_titleEdit, whats); connect(m_titleEdit, SIGNAL(textChanged(const QString&)), SLOT(slotModified())); label = new QLabel(i18n("T&ype:"), grid); layout->addWidget(label, row, 2); m_typeCombo = new KComboBox(grid); layout->addWidget(m_typeCombo, row, 3); label->setBuddy(m_typeCombo); whats = QString::fromLatin1(""); whats += i18n("The type of the field determines what values may be used. "); whats += i18n("Simple Text is used for most fields. "); whats += i18n("Paragraph is for large text blocks. "); whats += i18n("Choice limits the field to certain values. "); whats += i18n("Checkbox is for a simple yes/no value. "); whats += i18n("Number indicates that the field contains a numerical value. "); whats += i18n("URL is for fields which refer to URLs, including references to other files. "); whats += i18n("A Table may hold one or more columns of values. "); whats += i18n("An Image field holds a picture. "); whats += i18n("A Date field can be used for values with a day, month, and year. "); whats += i18n("A Rating field uses stars to show a rating number. "); whats += i18n("A Dependent field depends on the values of other " "fields, and is formatted according to the field description. "); whats += i18n("A Read Only is for internal values, possibly useful for import and export. "); whats += QString::fromLatin1(""); QWhatsThis::add(label, whats); QWhatsThis::add(m_typeCombo, whats); // the typeTitles match the fieldMap().values() but in a better order m_typeCombo->insertStringList(Data::Field::typeTitles()); connect(m_typeCombo, SIGNAL(activated(int)), SLOT(slotModified())); connect(m_typeCombo, SIGNAL(activated(const QString&)), SLOT(slotTypeChanged(const QString&))); label = new QLabel(i18n("Cate&gory:"), grid); layout->addWidget(label, ++row, 0); m_catCombo = new KComboBox(true, grid); layout->addWidget(m_catCombo, row, 1); label->setBuddy(m_catCombo); whats = i18n("The field category determines where the field is placed in the editor."); QWhatsThis::add(label, whats); QWhatsThis::add(m_catCombo, whats); // I don't want to include the categories for singleCategory fields QStringList cats; const QStringList allCats = m_coll->fieldCategories(); for(QStringList::ConstIterator it = allCats.begin(); it != allCats.end(); ++it) { Data::FieldVec fields = m_coll->fieldsByCategory(*it); if(!fields.isEmpty() && !fields.begin()->isSingleCategory()) { cats.append(*it); } } m_catCombo->insertStringList(cats); m_catCombo->setDuplicatesEnabled(false); connect(m_catCombo, SIGNAL(textChanged(const QString&)), SLOT(slotModified())); label = new QLabel(i18n("Descr&iption:"), grid); layout->addWidget(label, ++row, 0); m_descEdit = new KLineEdit(grid); m_descEdit->setMinimumWidth(150); layout->addMultiCellWidget(m_descEdit, row, row, 1, 3); label->setBuddy(m_descEdit); /* TRANSLATORS: Do not translate %{year} and %{title}. */ whats = i18n("The description is a useful reminder of what information is contained in the " "field. For Dependent fields, the description is a format string such as " "\"%{year} %{title}\" where the named fields get substituted in the string."); QWhatsThis::add(label, whats); QWhatsThis::add(m_descEdit, whats); connect(m_descEdit, SIGNAL(textChanged(const QString&)), SLOT(slotModified())); label = new QLabel(i18n("&Default value:"), grid); layout->addWidget(label, ++row, 0); m_defaultEdit = new KLineEdit(grid); layout->addMultiCellWidget(m_defaultEdit, row, row, 1, 3); label->setBuddy(m_defaultEdit); whats = i18n("A default value can be set for new entries."); QWhatsThis::add(label, whats); QWhatsThis::add(m_defaultEdit, whats); connect(m_defaultEdit, SIGNAL(textChanged(const QString&)), SLOT(slotModified())); label = new QLabel(i18n("A&llowed values:"), grid); layout->addWidget(label, ++row, 0); m_allowEdit = new KLineEdit(grid); layout->addMultiCellWidget(m_allowEdit, row, row, 1, 3); label->setBuddy(m_allowEdit); whats = i18n("For Choice-type fields, these are the only values allowed. They are " "placed in a combo box. The possible values have to be separated by a semi-colon, " "for example: \"dog; cat; mouse\""); QWhatsThis::add(label, whats); QWhatsThis::add(m_allowEdit, whats); connect(m_allowEdit, SIGNAL(textChanged(const QString&)), SLOT(slotModified())); label = new QLabel(i18n("Extended &properties:"), grid); layout->addWidget(label, ++row, 0); m_btnExtended = new KPushButton(i18n("&Set..."), grid); m_btnExtended->setIconSet(BarIcon(QString::fromLatin1("bookmark"), KIcon::SizeSmall)); layout->addWidget(m_btnExtended, row, 1); label->setBuddy(m_btnExtended); whats = i18n("Extended field properties are used to specify things such as the corresponding bibtex field."); QWhatsThis::add(label, whats); QWhatsThis::add(m_btnExtended, whats); connect(m_btnExtended, SIGNAL(clicked()), SLOT(slotShowExtendedProperties())); QButtonGroup* bg = new QButtonGroup(1, Qt::Horizontal, i18n("Format Options"), vbox); m_formatNone = new QRadioButton(i18n("No formatting"), bg); QWhatsThis::add(m_formatNone, i18n("This option prevents the field from ever being " "automatically formatted or capitalized.")); m_formatPlain = new QRadioButton(i18n("Allow auto-capitalization only"), bg); QWhatsThis::add(m_formatPlain, i18n("This option allows the field to be capitalized, but " "not specially formatted.")); m_formatTitle = new QRadioButton(i18n("Format as a title"), bg); QWhatsThis::add(m_formatTitle, i18n("This option capitalizes and formats the field as a " "title, but only if those options are globally set.")); m_formatName = new QRadioButton(i18n("Format as a name"), bg); QWhatsThis::add(m_formatName, i18n("This option capitalizes and formats the field as a " "name, but only if those options are globally set.")); connect(bg, SIGNAL(clicked(int)), SLOT(slotModified())); QGroupBox* optionsGroup = new QGroupBox(1, Qt::Horizontal, i18n("Field Options"), vbox); m_complete = new QCheckBox(i18n("Enable auto-completion"), optionsGroup); QWhatsThis::add(m_complete, i18n("If checked, KDE auto-completion will be enabled in the " "text edit box for this field.")); m_multiple = new QCheckBox(i18n("Allow multiple values"), optionsGroup); QWhatsThis::add(m_multiple, i18n("If checked, Tellico will parse the values in the field " "for multiple values, separated by a semi-colon.")); m_grouped = new QCheckBox(i18n("Allow grouping"), optionsGroup); QWhatsThis::add(m_grouped, i18n("If checked, this field may be used to group the entries in " "the group view.")); connect(m_complete, SIGNAL(clicked()), SLOT(slotModified())); connect(m_multiple, SIGNAL(clicked()), SLOT(slotModified())); connect(m_grouped, SIGNAL(clicked()), SLOT(slotModified())); // need to stretch at bottom vbox->setStretchFactor(new QWidget(vbox), 1); KAcceleratorManager::manage(vbox); // keep a default collection m_defaultCollection = CollectionFactory::collection(m_coll->type(), true); QWhatsThis::add(actionButton(KDialogBase::Default), i18n("Revert the selected field's properties to the default values.")); enableButtonOK(false); enableButtonApply(false); setHelp(QString::fromLatin1("fields-dialog")); // initially the m_typeCombo is populated with all types, but as soon as something is // selected in the fields box, the combo box is cleared and filled with the allowable // new types. The problem is that when more types are added, the size of the combo box // doesn't change. So when everything is laid out, the combo box needs to have all the // items there. QTimer::singleShot(0, this, SLOT(slotSelectInitial())); } CollectionFieldsDialog::~CollectionFieldsDialog() { } void CollectionFieldsDialog::slotSelectInitial() { m_fieldsBox->setSelected(0, true); } void CollectionFieldsDialog::slotOk() { updateField(); if(!checkValues()) { return; } applyChanges(); accept(); } void CollectionFieldsDialog::slotApply() { updateField(); if(!checkValues()) { return; } applyChanges(); } void CollectionFieldsDialog::applyChanges() { // start a command group, "Modify" is a generic term here since the commands could be add, modify, or delete Kernel::self()->beginCommandGroup(i18n("Modify Fields")); Data::FieldPtr field; for(Data::FieldVec::Iterator it = m_copiedFields.begin(); it != m_copiedFields.end(); ++it) { field = it; // check for Choice fields with removed values to warn user if(field->type() == Data::Field::Choice || field->type() == Data::Field::Rating) { QStringList oldValues = m_coll->fieldByName(field->name())->allowed(); QStringList newValues = field->allowed(); for(QStringList::ConstIterator vIt = oldValues.begin(); vIt != oldValues.end(); ++vIt) { if(newValues.contains(*vIt)) { continue; } int ret = KMessageBox::warningContinueCancel(this, i18n("Removing allowed values from the %1 field which " "currently exist in the collection may cause data corruption. " "Do you want to keep your modified values or cancel and revert " "to the current ones?").arg(field->title()), QString::null, i18n("Keep modified values")); if(ret != KMessageBox::Continue) { if(field->type() == Data::Field::Choice) { field->setAllowed(oldValues); } else { // rating field Data::FieldPtr oldField = m_coll->fieldByName(field->name()); field->setProperty(QString::fromLatin1("minimum"), oldField->property(QString::fromLatin1("minimum"))); field->setProperty(QString::fromLatin1("maximum"), oldField->property(QString::fromLatin1("maximum"))); } } break; } } Kernel::self()->modifyField(field); } for(Data::FieldVec::Iterator it = m_newFields.begin(); it != m_newFields.end(); ++it) { Kernel::self()->addField(it); } // set all text not to be colored, and get new list Data::FieldVec fields; for(QListBoxItem* item = m_fieldsBox->firstItem(); item; item = item->next()) { static_cast(item)->setColored(false); if(m_reordered) { Data::FieldPtr field = static_cast(item)->field(); if(field) { fields.append(field); } } } // if reordering fields, need to add ReadOnly fields since they were not shown if(m_reordered) { Data::FieldVec allFields = m_coll->fields(); for(Data::FieldVec::Iterator it = allFields.begin(); it != allFields.end(); ++it) { if(it->type() == Data::Field::ReadOnly) { fields.append(it); } } } if(fields.count() > 0) { Kernel::self()->reorderFields(fields); } // commit command group Kernel::self()->endCommandGroup(); // now clear copied fields m_copiedFields.clear(); // clear new ones, too m_newFields.clear(); m_currentField = static_cast(m_fieldsBox->selectedItem())->field(); // the field type might have changed, so need to update the type combo list with possible values if(m_currentField) { // set the updating flag since the values are changing and slots are firing // but we don't care about UI indications of changes bool wasUpdating = m_updatingValues; m_updatingValues = true; QString currType = m_typeCombo->currentText(); m_typeCombo->clear(); m_typeCombo->insertStringList(newTypesAllowed(m_currentField->type())); m_typeCombo->setCurrentItem(currType); // description might have been changed for dependent fields m_descEdit->setText(m_currentField->description()); m_updatingValues = wasUpdating; } enableButtonApply(false); } void CollectionFieldsDialog::slotNew() { // first update the current one with all the values from the edit widgets updateField(); // next check old values if(!checkValues()) { return; } QString name = QString::fromLatin1("custom") + QString::number(m_newFields.count()+1); int count = m_newFields.count() + 1; QString title = i18n("New Field") + QString::fromLatin1(" %1").arg(count); while(m_fieldsBox->findItem(title)) { ++count; title = i18n("New Field") + QString::fromLatin1(" %1").arg(count); } Data::FieldPtr field = new Data::Field(name, title); m_newFields.append(field); // myDebug() << "CollectionFieldsDialog::slotNew() - adding new field " << title << endl; m_currentField = field; FieldListBox* box = new FieldListBox(m_fieldsBox, field); m_fieldsBox->setSelected(box, true); box->setColored(true); m_fieldsBox->ensureCurrentVisible(); slotModified(); m_titleEdit->setFocus(); m_titleEdit->selectAll(); } void CollectionFieldsDialog::slotDelete() { if(!m_currentField) { return; } if(m_newFields.contains(m_currentField)) { // remove field from vector before deleting item containing field m_newFields.remove(m_currentField); m_fieldsBox->removeItem(m_fieldsBox->currentItem()); m_fieldsBox->setSelected(m_fieldsBox->currentItem(), true); m_fieldsBox->ensureCurrentVisible(); m_currentField = static_cast(m_fieldsBox->selectedItem())->field(); // KShared gets auto-deleted return; } bool success = Kernel::self()->removeField(m_currentField); if(success) { emit signalCollectionModified(); m_fieldsBox->removeItem(m_fieldsBox->currentItem()); m_fieldsBox->setSelected(m_fieldsBox->currentItem(), true); m_fieldsBox->ensureCurrentVisible(); m_currentField = static_cast(m_fieldsBox->selectedItem())->field(); enableButtonOK(true); } } void CollectionFieldsDialog::slotTypeChanged(const QString& type_) { Data::Field::Type type = Data::Field::Undef; const Data::Field::FieldMap fieldMap = Data::Field::typeMap(); for(Data::Field::FieldMap::ConstIterator it = fieldMap.begin(); it != fieldMap.end(); ++it) { if(it.data() == type_) { type = it.key(); break; } } if(type == Data::Field::Undef) { kdWarning() << "CollectionFieldsDialog::slotTypeChanged() - type name not recognized: " << type_ << endl; type = Data::Field::Line; } // only choice types gets allowed values m_allowEdit->setEnabled(type == Data::Field::Choice); // paragraphs, tables, and images are their own category bool isCategory = (type == Data::Field::Para || type == Data::Field::Table || type == Data::Field::Table2 || type == Data::Field::Image); m_catCombo->setEnabled(!isCategory); // formatting is only applicable when the type is simple text or a table bool isText = (type == Data::Field::Line || type == Data::Field::Table || type == Data::Field::Table2); // formatNone is the default m_formatPlain->setEnabled(isText); m_formatName->setEnabled(isText); m_formatTitle->setEnabled(isText); // multiple is only applicable for simple text and number isText = (type == Data::Field::Line || type == Data::Field::Number); m_multiple->setEnabled(isText); // completion is only applicable for simple text, number, and URL isText = (isText || type == Data::Field::URL); m_complete->setEnabled(isText); // grouping is not possible with paragraphs or images m_grouped->setEnabled(type != Data::Field::Para && type != Data::Field::Image); } void CollectionFieldsDialog::slotHighlightedChanged(int index_) { // myDebug() << "CollectionFieldsDialog::slotHighlightedChanged() - " << index_ << endl; // use this instead of blocking signals everywhere m_updatingValues = true; // first update the current one with all the values from the edit widgets updateField(); // next check old values if(!checkValues()) { m_fieldsBox->blockSignals(true); m_fieldsBox->setSelected(m_oldIndex, true); m_fieldsBox->blockSignals(false); m_updatingValues = false; return; } m_oldIndex = index_; m_btnUp->setEnabled(index_ > 0); m_btnDown->setEnabled(index_ < static_cast(m_fieldsBox->count())-1); FieldListBox* item = dynamic_cast(m_fieldsBox->item(index_)); if(!item) { return; } // need to get a pointer to the field with the new values to insert Data::FieldPtr field = item->field(); if(!field) { myDebug() << "CollectionFieldsDialog::slotHighlightedChanged() - no field found!" << endl; return; } m_titleEdit->setText(field->title()); // type is limited to certain types, unless it's a new field m_typeCombo->clear(); if(m_newFields.contains(field)) { m_typeCombo->insertStringList(newTypesAllowed(Data::Field::Undef)); } else { m_typeCombo->insertStringList(newTypesAllowed(field->type())); } // if the current name is not there, then this will change the list! const Data::Field::FieldMap& fieldMap = Data::Field::typeMap(); m_typeCombo->setCurrentText(fieldMap[field->type()]); slotTypeChanged(fieldMap[field->type()]); // just setting the text doesn't emit the activated signal if(field->type() == Data::Field::Choice) { m_allowEdit->setText(field->allowed().join(QString::fromLatin1("; "))); } else { m_allowEdit->clear(); } m_catCombo->setCurrentText(field->category()); // have to do this here m_descEdit->setText(field->description()); m_defaultEdit->setText(field->defaultValue()); switch(field->formatFlag()) { case Data::Field::FormatNone: case Data::Field::FormatDate: // as yet unimplemented m_formatNone->setChecked(true); break; case Data::Field::FormatPlain: m_formatPlain->setChecked(true); break; case Data::Field::FormatTitle: m_formatTitle->setChecked(true); break; case Data::Field::FormatName: m_formatName->setChecked(true); break; default: kdWarning() << "CollectionFieldsDialog::slotHighlightedChanged() - no format type!" << endl; break; } int flags = field->flags(); m_complete->setChecked(flags & Data::Field::AllowCompletion); m_multiple->setChecked(flags & Data::Field::AllowMultiple); m_grouped->setChecked(flags & Data::Field::AllowGrouped); m_btnDelete->setEnabled(!(flags & Data::Field::NoDelete)); // default button is enabled only if default collection contains the field if(m_defaultCollection) { bool hasField = m_defaultCollection->hasField(field->name()); actionButton(KDialogBase::Default)->setEnabled(hasField); } m_currentField = field; m_updatingValues = false; } void CollectionFieldsDialog::updateField() { // myDebug() << "CollectionFieldsDialog::updateField()" << endl; Data::FieldPtr field = m_currentField; if(!field || !m_modified) { return; } // only update name if it's one of the new ones if(m_newFields.contains(field)) { // name needs to be a valid XML element name QString name = XML::elementName(m_titleEdit->text().lower()); if(name.isEmpty()) { // might end up with empty string name = QString::fromLatin1("custom") + QString::number(m_newFields.count()+1); } while(m_coll->hasField(name)) { // ensure name uniqueness name += QString::fromLatin1("-new"); } field->setName(name); } const QString title = m_titleEdit->text().simplifyWhiteSpace(); updateTitle(title); const Data::Field::FieldMap& fieldMap = Data::Field::typeMap(); for(Data::Field::FieldMap::ConstIterator it = fieldMap.begin(); it != fieldMap.end(); ++it) { if(it.data() == m_typeCombo->currentText()) { field->setType(it.key()); break; } } if(field->type() == Data::Field::Choice) { const QRegExp rx(QString::fromLatin1("\\s*;\\s*")); field->setAllowed(QStringList::split(rx, m_allowEdit->text())); field->setProperty(QString::fromLatin1("minimum"), QString::null); field->setProperty(QString::fromLatin1("maximum"), QString::null); } else if(field->type() == Data::Field::Rating) { QString v = field->property(QString::fromLatin1("minimum")); if(v.isEmpty()) { field->setProperty(QString::fromLatin1("minimum"), QString::number(1)); } v = field->property(QString::fromLatin1("maximum")); if(v.isEmpty()) { field->setProperty(QString::fromLatin1("maximum"), QString::number(5)); } } if(field->isSingleCategory()) { field->setCategory(field->title()); } else { QString category = m_catCombo->currentText().simplifyWhiteSpace(); field->setCategory(category); m_catCombo->setCurrentItem(category, true); // if it doesn't exist, it's added } field->setDescription(m_descEdit->text()); field->setDefaultValue(m_defaultEdit->text()); if(m_formatTitle->isChecked()) { field->setFormatFlag(Data::Field::FormatTitle); } else if(m_formatName->isChecked()) { field->setFormatFlag(Data::Field::FormatName); } else if(m_formatPlain->isChecked()) { field->setFormatFlag(Data::Field::FormatPlain); } else { field->setFormatFlag(Data::Field::FormatNone); } int flags = 0; if(m_complete->isChecked()) { flags |= Data::Field::AllowCompletion; } if(m_grouped->isChecked()) { flags |= Data::Field::AllowGrouped; } if(m_multiple->isChecked()) { flags |= Data::Field::AllowMultiple; } field->setFlags(flags); m_modified = false; } // The purpose here is to first set the modified flag. Then, if the field being edited is one // that exists in the collection already, a deep copy needs to be made. void CollectionFieldsDialog::slotModified() { // myDebug() << "CollectionFieldsDialog::slotModified()" << endl; // if I'm just updating the values, I don't care if(m_updatingValues) { return; } m_modified = true; enableButtonOK(true); enableButtonApply(true); if(!m_currentField) { myDebug() << "CollectionFieldsDialog::slotModified() - no current field!" << endl; m_currentField = static_cast(m_fieldsBox->selectedItem())->field(); } // color the text static_cast(m_fieldsBox->selectedItem())->setColored(true); // check if copy exists already if(m_copiedFields.contains(m_currentField)) { return; } // or, check if is a new field, in which case no copy is needed // check if copy exists already if(m_newFields.contains(m_currentField)) { return; } m_currentField = new Data::Field(*m_currentField); m_copiedFields.append(m_currentField); static_cast(m_fieldsBox->selectedItem())->setField(m_currentField); } void CollectionFieldsDialog::updateTitle(const QString& title_) { // myDebug() << "CollectionFieldsDialog::updateTitle()" << endl; if(m_currentField && m_currentField->title() != title_) { m_fieldsBox->blockSignals(true); FieldListBox* oldItem = findItem(m_fieldsBox, m_currentField); if(!oldItem) { return; } oldItem->setText(title_); // will always be colored since it's new oldItem->setColored(true); m_fieldsBox->triggerUpdate(true); m_currentField->setTitle(title_); m_fieldsBox->blockSignals(false); } } void CollectionFieldsDialog::slotDefault() { if(!m_currentField) { return; } Data::FieldPtr defaultField = m_defaultCollection->fieldByName(m_currentField->name()); if(!defaultField) { return; } QString caption = i18n("Revert Field Properties"); QString text = i18n("

Do you really want to revert the properties for the %1 " "field back to their default values?

").arg(m_currentField->title()); QString dontAsk = QString::fromLatin1("RevertFieldProperties"); int ret = KMessageBox::warningContinueCancel(this, text, caption, i18n("Revert"), dontAsk); if(ret != KMessageBox::Continue) { return; } // now update all values with default m_updatingValues = true; m_titleEdit->setText(defaultField->title()); const Data::Field::FieldMap& fieldMap = Data::Field::typeMap(); m_typeCombo->setCurrentText(fieldMap[defaultField->type()]); slotTypeChanged(fieldMap[defaultField->type()]); // just setting the text doesn't emit the activated signal if(defaultField->type() == Data::Field::Choice) { m_allowEdit->setText(defaultField->allowed().join(QString::fromLatin1("; "))); } else { m_allowEdit->clear(); } m_catCombo->setCurrentText(defaultField->category()); // have to do this here m_descEdit->setText(defaultField->description()); m_defaultEdit->setText(defaultField->defaultValue()); switch(defaultField->formatFlag()) { case Data::Field::FormatNone: case Data::Field::FormatDate: m_formatNone->setChecked(true); break; case Data::Field::FormatPlain: m_formatPlain->setChecked(true); break; case Data::Field::FormatTitle: m_formatTitle->setChecked(true); break; case Data::Field::FormatName: m_formatName->setChecked(true); break; default: break; } int flags = defaultField->flags(); m_complete->setChecked(flags & Data::Field::AllowCompletion); m_multiple->setChecked(flags & Data::Field::AllowMultiple); m_grouped->setChecked(flags & Data::Field::AllowGrouped); m_btnDelete->setEnabled(!(defaultField->flags() & Data::Field::NoDelete)); // m_titleEdit->setFocus(); // m_titleEdit->selectAll(); m_updatingValues = false; slotModified(); } void CollectionFieldsDialog::slotMoveUp() { QListBoxItem* item = m_fieldsBox->selectedItem(); if(item) { FieldListBox* prev = static_cast(item->prev()); // could be 0 if(prev) { FieldListBox* newPrev = new FieldListBox(m_fieldsBox, prev->field(), item); newPrev->setColored(prev->isColored()); delete prev; m_fieldsBox->ensureCurrentVisible(); // since the current one doesn't get re-highlighted, need to highlighted doesn't get emitted slotHighlightedChanged(m_fieldsBox->currentItem()); } } m_reordered = true; // don't call slotModified() since that creates a deep copy. m_modified = true; enableButtonOK(true); enableButtonApply(true); } void CollectionFieldsDialog::slotMoveDown() { FieldListBox* item = dynamic_cast(m_fieldsBox->selectedItem()); if(item) { QListBoxItem* next = item->next(); // could be 0 if(next) { FieldListBox* newItem = new FieldListBox(m_fieldsBox, item->field(), next); newItem->setColored(item->isColored()); delete item; m_fieldsBox->setSelected(newItem, true); m_fieldsBox->ensureCurrentVisible(); } } m_reordered = true; // don't call slotModified() since that creates a deep copy. m_modified = true; enableButtonOK(true); enableButtonApply(true); } Tellico::FieldListBox* CollectionFieldsDialog::findItem(const QListBox* box_, Data::FieldPtr field_) { // myDebug() << "CollectionFieldsDialog::findItem()" << endl; for(QListBoxItem* item = box_->firstItem(); item; item = item->next()) { FieldListBox* textItem = static_cast(item); if(textItem->field() == field_) { return textItem; } } return 0; } bool CollectionFieldsDialog::slotShowExtendedProperties() { if(!m_currentField) { return false; } // the default value is included in properties, but it has a // separate edit box QString dv = m_currentField->defaultValue(); StringMap props = m_currentField->propertyList(); props.remove(QString::fromLatin1("default")); StringMapDialog dlg(props, this, "ExtendedPropertiesDialog", true); dlg.setCaption(i18n("Extended Field Properties")); dlg.setLabels(i18n("Property"), i18n("Value")); if(dlg.exec() == QDialog::Accepted) { props = dlg.stringMap(); if(!dv.isEmpty()) { props.insert(QString::fromLatin1("default"), dv); } m_currentField->setPropertyList(props); slotModified(); return true; } return false; } bool CollectionFieldsDialog::checkValues() { if(!m_currentField) { return true; } const QString title = m_currentField->title(); // find total number of boxes with this title in case multiple new ones with same title were added int titleCount = 0; for(uint i = 0; i < m_fieldsBox->count(); ++i) { if(m_fieldsBox->item(i)->text() == title) { ++titleCount; } } if((m_coll->fieldByTitle(title) && m_coll->fieldNameByTitle(title) != m_currentField->name()) || titleCount > 1) { // already have a field with this title KMessageBox::sorry(this, i18n("A field with this title already exists. Please enter a different title.")); m_titleEdit->selectAll(); return false; } const QString category = m_currentField->category(); if(category.isEmpty()) { KMessageBox::sorry(this, i18n("The category may not be empty. Please enter a category.")); m_catCombo->lineEdit()->selectAll(); return false; } Data::FieldVec fields = m_coll->fieldsByCategory(category); if(!fields.isEmpty() && fields.begin()->isSingleCategory() && fields.begin()->name() != m_currentField->name()) { // can't have this category, cause it conflicts with a single-category field KMessageBox::sorry(this, i18n("A field may not be in the same category as a Paragraph, " "Table or Image field. Please enter a different category.")); m_catCombo->lineEdit()->selectAll(); return false; } // the combobox is disabled for single-category fields if(!m_catCombo->isEnabled() && m_coll->fieldByTitle(title) && m_coll->fieldNameByTitle(title) != m_currentField->name()) { KMessageBox::sorry(this, i18n("A field's title may not be the same as an existing category. " "Please enter a different title.")); m_titleEdit->selectAll(); return false; } // check for rating values outside bounds if(m_currentField->type() == Data::Field::Rating) { bool ok; // ok to ignore this here int low = Tellico::toUInt(m_currentField->property(QString::fromLatin1("minimum")), &ok); int high = Tellico::toUInt(m_currentField->property(QString::fromLatin1("maximum")), &ok); while(low < 1 || low > 9 || high < 1 || high > 10 || low >= high) { KMessageBox::sorry(this, i18n("The range for a rating field must be between 1 and 10, " "and the lower bound must be less than the higher bound. " "Please enter different low and high properties.")); if(slotShowExtendedProperties()) { low = Tellico::toUInt(m_currentField->property(QString::fromLatin1("minimum")), &ok); high = Tellico::toUInt(m_currentField->property(QString::fromLatin1("maximum")), &ok); } else { return false; } } } else if(m_currentField->type() == Data::Field::Table) { bool ok; // ok to ignore this here int ncols = Tellico::toUInt(m_currentField->property(QString::fromLatin1("columns")), &ok); // also enforced in GUI::TableFieldWidget if(ncols > 10) { KMessageBox::sorry(this, i18n("Tables are limited to a maximum of ten columns.")); m_currentField->setProperty(QString::fromLatin1("columns"), QString::fromLatin1("10")); } } return true; } // only certain type changes are allowed QStringList CollectionFieldsDialog::newTypesAllowed(int type_ /*=0*/) { // Undef means return all if(type_ == Data::Field::Undef) { return Data::Field::typeTitles(); } const Data::Field::FieldMap& fieldMap = Data::Field::typeMap(); QStringList newTypes; switch(type_) { case Data::Field::Line: // might not work if converted to a number or URL, but ok case Data::Field::Number: case Data::Field::URL: newTypes += fieldMap[Data::Field::Line]; newTypes += fieldMap[Data::Field::Para]; newTypes += fieldMap[Data::Field::Number]; newTypes += fieldMap[Data::Field::URL]; newTypes += fieldMap[Data::Field::Table]; break; case Data::Field::Date: newTypes += fieldMap[Data::Field::Line]; newTypes += fieldMap[Data::Field::Date]; break; case Data::Field::Bool: // doesn't really make sense, but can't hurt newTypes += fieldMap[Data::Field::Line]; newTypes += fieldMap[Data::Field::Para]; newTypes += fieldMap[Data::Field::Bool]; newTypes += fieldMap[Data::Field::Number]; newTypes += fieldMap[Data::Field::URL]; newTypes += fieldMap[Data::Field::Table]; break; case Data::Field::Choice: newTypes += fieldMap[Data::Field::Line]; newTypes += fieldMap[Data::Field::Para]; newTypes += fieldMap[Data::Field::Choice]; newTypes += fieldMap[Data::Field::Number]; newTypes += fieldMap[Data::Field::URL]; newTypes += fieldMap[Data::Field::Table]; newTypes += fieldMap[Data::Field::Rating]; break; case Data::Field::Table: // not really a good idea since the "::" will be exposed, but allow it case Data::Field::Table2: newTypes += fieldMap[Data::Field::Line]; newTypes += fieldMap[Data::Field::Number]; newTypes += fieldMap[Data::Field::Table]; break; case Data::Field::Para: newTypes += fieldMap[Data::Field::Line]; newTypes += fieldMap[Data::Field::Para]; break; case Data::Field::Rating: newTypes += fieldMap[Data::Field::Choice]; newTypes += fieldMap[Data::Field::Rating]; break; // these can never be changed case Data::Field::Image: case Data::Field::Dependent: newTypes = fieldMap[static_cast(type_)]; break; default: myDebug() << "CollectionFieldsDialog::newTypesAllowed() - no match for " << type_ << endl; newTypes = Data::Field::typeTitles(); break; } return newTypes; } #include "collectionfieldsdialog.moc"