Tellico – collection manager for books, videos, music
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.

1039 lines
39KB

  1. /***************************************************************************
  2. copyright : (C) 2003-2006 by Robby Stephenson
  3. email : robby@periapsis.org
  4. ***************************************************************************/
  5. /***************************************************************************
  6. * *
  7. * This program is free software; you can redistribute it and/or modify *
  8. * it under the terms of version 2 of the GNU General Public License as *
  9. * published by the Free Software Foundation; *
  10. * *
  11. ***************************************************************************/
  12. #include "collectionfieldsdialog.h"
  13. #include "collection.h"
  14. #include "field.h"
  15. #include "collectionfactory.h"
  16. #include "gui/stringmapdialog.h"
  17. #include "tellico_kernel.h"
  18. #include "translators/tellico_xml.h"
  19. #include "tellico_utils.h"
  20. #include "tellico_debug.h"
  21. #include <tdelocale.h>
  22. #include <kcombobox.h>
  23. #include <klineedit.h>
  24. #include <kiconloader.h>
  25. #include <tdemessagebox.h>
  26. #include <kpushbutton.h>
  27. #include <tdeaccelmanager.h>
  28. #include <tqgroupbox.h>
  29. #include <tqlayout.h>
  30. #include <tqhbox.h>
  31. #include <tqvbox.h>
  32. #include <tqlabel.h>
  33. #include <tqradiobutton.h>
  34. #include <tqbuttongroup.h>
  35. #include <tqcheckbox.h>
  36. #include <tqregexp.h>
  37. #include <tqwhatsthis.h>
  38. #include <tqtimer.h>
  39. using Tellico::FieldListBox;
  40. using Tellico::CollectionFieldsDialog;
  41. FieldListBox::FieldListBox(TQListBox* listbox_, Data::FieldPtr field_)
  42. : GUI::ListBoxText(listbox_, field_->title()), m_field(field_) {
  43. }
  44. FieldListBox::FieldListBox(TQListBox* listbox_, Data::FieldPtr field_, TQListBoxItem* after_)
  45. : GUI::ListBoxText(listbox_, field_->title(), after_), m_field(field_) {
  46. }
  47. CollectionFieldsDialog::CollectionFieldsDialog(Data::CollPtr coll_, TQWidget* parent_, const char* name_/*=0*/)
  48. : KDialogBase(parent_, name_, false, i18n("Collection Fields"), Help|Default|Ok|Apply|Cancel, Ok, false),
  49. m_coll(coll_),
  50. m_defaultCollection(0),
  51. m_currentField(0),
  52. m_modified(false),
  53. m_updatingValues(false),
  54. m_reordered(false),
  55. m_oldIndex(-1) {
  56. TQWidget* page = new TQWidget(this);
  57. setMainWidget(page);
  58. TQHBoxLayout* topLayout = new TQHBoxLayout(page, 0, KDialog::spacingHint());
  59. TQGroupBox* fieldsGroup = new TQGroupBox(1, Qt::Horizontal, i18n("Current Fields"), page);
  60. topLayout->addWidget(fieldsGroup, 1);
  61. m_fieldsBox = new TQListBox(fieldsGroup);
  62. m_fieldsBox->setMinimumWidth(150);
  63. Data::FieldVec fields = m_coll->fields();
  64. for(Data::FieldVec::Iterator it = fields.begin(); it != fields.end(); ++it) {
  65. // ignore ReadOnly
  66. if(it->type() != Data::Field::ReadOnly) {
  67. (void) new FieldListBox(m_fieldsBox, it);
  68. }
  69. }
  70. connect(m_fieldsBox, TQT_SIGNAL(highlighted(int)), TQT_SLOT(slotHighlightedChanged(int)));
  71. TQHBox* hb1 = new TQHBox(fieldsGroup);
  72. hb1->setSpacing(KDialog::spacingHint());
  73. m_btnNew = new KPushButton(i18n("New Field", "&New"), hb1);
  74. m_btnNew->setIconSet(BarIcon(TQString::fromLatin1("document-new"), TDEIcon::SizeSmall));
  75. TQWhatsThis::add(m_btnNew, i18n("Add a new field to the collection"));
  76. m_btnDelete = new KPushButton(i18n("Delete Field", "&Delete"), hb1);
  77. m_btnDelete->setIconSet(BarIconSet(TQString::fromLatin1("edit-delete"), TDEIcon::SizeSmall));
  78. TQWhatsThis::add(m_btnDelete, i18n("Remove a field from the collection"));
  79. connect(m_btnNew, TQT_SIGNAL(clicked()), TQT_SLOT(slotNew()) );
  80. connect(m_btnDelete, TQT_SIGNAL(clicked()), TQT_SLOT(slotDelete()));
  81. TQHBox* hb2 = new TQHBox(fieldsGroup);
  82. hb2->setSpacing(KDialog::spacingHint());
  83. m_btnUp = new KPushButton(hb2);
  84. m_btnUp->setPixmap(BarIcon(TQString::fromLatin1("go-up"), TDEIcon::SizeSmall));
  85. TQWhatsThis::add(m_btnUp, i18n("Move this field up in the list. The list order is important "
  86. "for the layout of the entry editor."));
  87. m_btnDown = new KPushButton(hb2);
  88. m_btnDown->setPixmap(BarIcon(TQString::fromLatin1("go-down"), TDEIcon::SizeSmall));
  89. TQWhatsThis::add(m_btnDown, i18n("Move this field down in the list. The list order is important "
  90. "for the layout of the entry editor."));
  91. connect(m_btnUp, TQT_SIGNAL(clicked()), TQT_SLOT(slotMoveUp()) );
  92. connect(m_btnDown, TQT_SIGNAL(clicked()), TQT_SLOT(slotMoveDown()));
  93. TQVBox* vbox = new TQVBox(page);
  94. vbox->setSpacing(KDialog::spacingHint());
  95. topLayout->addWidget(vbox, 2);
  96. TQGroupBox* propGroup = new TQGroupBox(1, Qt::Horizontal, i18n("Field Properties"), vbox);
  97. TQWidget* grid = new TQWidget(propGroup);
  98. // (parent, nrows, ncols, margin, spacing)
  99. TQGridLayout* layout = new TQGridLayout(grid, 4, 4, 0, KDialog::spacingHint());
  100. int row = -1;
  101. TQLabel* label = new TQLabel(i18n("&Title:"), grid);
  102. layout->addWidget(label, ++row, 0);
  103. m_titleEdit = new KLineEdit(grid);
  104. layout->addWidget(m_titleEdit, row, 1);
  105. label->setBuddy(m_titleEdit);
  106. TQString whats = i18n("The title of the field");
  107. TQWhatsThis::add(label, whats);
  108. TQWhatsThis::add(m_titleEdit, whats);
  109. connect(m_titleEdit, TQT_SIGNAL(textChanged(const TQString&)), TQT_SLOT(slotModified()));
  110. label = new TQLabel(i18n("T&ype:"), grid);
  111. layout->addWidget(label, row, 2);
  112. m_typeCombo = new KComboBox(grid);
  113. layout->addWidget(m_typeCombo, row, 3);
  114. label->setBuddy(m_typeCombo);
  115. whats = TQString::fromLatin1("<qt>");
  116. whats += i18n("The type of the field determines what values may be used. ");
  117. whats += i18n("<i>Simple Text</i> is used for most fields. ");
  118. whats += i18n("<i>Paragraph</i> is for large text blocks. ");
  119. whats += i18n("<i>Choice</i> limits the field to certain values. ");
  120. whats += i18n("<i>Checkbox</i> is for a simple yes/no value. ");
  121. whats += i18n("<i>Number</i> indicates that the field contains a numerical value. ");
  122. whats += i18n("<i>URL</i> is for fields which refer to URLs, including references to other files. ");
  123. whats += i18n("A <i>Table</i> may hold one or more columns of values. ");
  124. whats += i18n("An <i>Image</i> field holds a picture. ");
  125. whats += i18n("A <i>Date</i> field can be used for values with a day, month, and year. ");
  126. whats += i18n("A <i>Rating</i> field uses stars to show a rating number. ");
  127. whats += i18n("A <i>Dependent</i> field depends on the values of other "
  128. "fields, and is formatted according to the field description. ");
  129. whats += i18n("A <i>Read Only</i> is for internal values, possibly useful for import and export. ");
  130. whats += TQString::fromLatin1("</qt>");
  131. TQWhatsThis::add(label, whats);
  132. TQWhatsThis::add(m_typeCombo, whats);
  133. // the typeTitles match the fieldMap().values() but in a better order
  134. m_typeCombo->insertStringList(Data::Field::typeTitles());
  135. connect(m_typeCombo, TQT_SIGNAL(activated(int)), TQT_SLOT(slotModified()));
  136. connect(m_typeCombo, TQT_SIGNAL(activated(const TQString&)), TQT_SLOT(slotTypeChanged(const TQString&)));
  137. label = new TQLabel(i18n("Cate&gory:"), grid);
  138. layout->addWidget(label, ++row, 0);
  139. m_catCombo = new KComboBox(true, grid);
  140. layout->addWidget(m_catCombo, row, 1);
  141. label->setBuddy(m_catCombo);
  142. whats = i18n("The field category determines where the field is placed in the editor.");
  143. TQWhatsThis::add(label, whats);
  144. TQWhatsThis::add(m_catCombo, whats);
  145. // I don't want to include the categories for singleCategory fields
  146. TQStringList cats;
  147. const TQStringList allCats = m_coll->fieldCategories();
  148. for(TQStringList::ConstIterator it = allCats.begin(); it != allCats.end(); ++it) {
  149. Data::FieldVec fields = m_coll->fieldsByCategory(*it);
  150. if(!fields.isEmpty() && !fields.begin()->isSingleCategory()) {
  151. cats.append(*it);
  152. }
  153. }
  154. m_catCombo->insertStringList(cats);
  155. m_catCombo->setDuplicatesEnabled(false);
  156. connect(m_catCombo, TQT_SIGNAL(textChanged(const TQString&)), TQT_SLOT(slotModified()));
  157. label = new TQLabel(i18n("Descr&iption:"), grid);
  158. layout->addWidget(label, ++row, 0);
  159. m_descEdit = new KLineEdit(grid);
  160. m_descEdit->setMinimumWidth(150);
  161. layout->addMultiCellWidget(m_descEdit, row, row, 1, 3);
  162. label->setBuddy(m_descEdit);
  163. /* TRANSLATORS: Do not translate %{year} and %{title}. */
  164. whats = i18n("The description is a useful reminder of what information is contained in the "
  165. "field. For <i>Dependent</i> fields, the description is a format string such as "
  166. "\"%{year} %{title}\" where the named fields get substituted in the string.");
  167. TQWhatsThis::add(label, whats);
  168. TQWhatsThis::add(m_descEdit, whats);
  169. connect(m_descEdit, TQT_SIGNAL(textChanged(const TQString&)), TQT_SLOT(slotModified()));
  170. label = new TQLabel(i18n("&Default value:"), grid);
  171. layout->addWidget(label, ++row, 0);
  172. m_defaultEdit = new KLineEdit(grid);
  173. layout->addMultiCellWidget(m_defaultEdit, row, row, 1, 3);
  174. label->setBuddy(m_defaultEdit);
  175. whats = i18n("<qt>A default value can be set for new entries.</qt>");
  176. TQWhatsThis::add(label, whats);
  177. TQWhatsThis::add(m_defaultEdit, whats);
  178. connect(m_defaultEdit, TQT_SIGNAL(textChanged(const TQString&)), TQT_SLOT(slotModified()));
  179. label = new TQLabel(i18n("A&llowed values:"), grid);
  180. layout->addWidget(label, ++row, 0);
  181. m_allowEdit = new KLineEdit(grid);
  182. layout->addMultiCellWidget(m_allowEdit, row, row, 1, 3);
  183. label->setBuddy(m_allowEdit);
  184. whats = i18n("<qt>For <i>Choice</i>-type fields, these are the only values allowed. They are "
  185. "placed in a combo box. The possible values have to be separated by a semi-colon, "
  186. "for example: \"dog; cat; mouse\"</qt>");
  187. TQWhatsThis::add(label, whats);
  188. TQWhatsThis::add(m_allowEdit, whats);
  189. connect(m_allowEdit, TQT_SIGNAL(textChanged(const TQString&)), TQT_SLOT(slotModified()));
  190. label = new TQLabel(i18n("Extended &properties:"), grid);
  191. layout->addWidget(label, ++row, 0);
  192. m_btnExtended = new KPushButton(i18n("&Set..."), grid);
  193. m_btnExtended->setIconSet(BarIcon(TQString::fromLatin1("bookmark"), TDEIcon::SizeSmall));
  194. layout->addWidget(m_btnExtended, row, 1);
  195. label->setBuddy(m_btnExtended);
  196. whats = i18n("Extended field properties are used to specify things such as the corresponding bibtex field.");
  197. TQWhatsThis::add(label, whats);
  198. TQWhatsThis::add(m_btnExtended, whats);
  199. connect(m_btnExtended, TQT_SIGNAL(clicked()), TQT_SLOT(slotShowExtendedProperties()));
  200. TQButtonGroup* bg = new TQButtonGroup(1, Qt::Horizontal, i18n("Format Options"), vbox);
  201. m_formatNone = new TQRadioButton(i18n("No formatting"), bg);
  202. TQWhatsThis::add(m_formatNone, i18n("This option prevents the field from ever being "
  203. "automatically formatted or capitalized."));
  204. m_formatPlain = new TQRadioButton(i18n("Allow auto-capitalization only"), bg);
  205. TQWhatsThis::add(m_formatPlain, i18n("This option allows the field to be capitalized, but "
  206. "not specially formatted."));
  207. m_formatTitle = new TQRadioButton(i18n("Format as a title"), bg);
  208. TQWhatsThis::add(m_formatTitle, i18n("This option capitalizes and formats the field as a "
  209. "title, but only if those options are globally set."));
  210. m_formatName = new TQRadioButton(i18n("Format as a name"), bg);
  211. TQWhatsThis::add(m_formatName, i18n("This option capitalizes and formats the field as a "
  212. "name, but only if those options are globally set."));
  213. connect(bg, TQT_SIGNAL(clicked(int)), TQT_SLOT(slotModified()));
  214. TQGroupBox* optionsGroup = new TQGroupBox(1, Qt::Horizontal, i18n("Field Options"), vbox);
  215. m_complete = new TQCheckBox(i18n("Enable auto-completion"), optionsGroup);
  216. TQWhatsThis::add(m_complete, i18n("If checked, TDE auto-completion will be enabled in the "
  217. "text edit box for this field."));
  218. m_multiple = new TQCheckBox(i18n("Allow multiple values"), optionsGroup);
  219. TQWhatsThis::add(m_multiple, i18n("If checked, Tellico will parse the values in the field "
  220. "for multiple values, separated by a semi-colon."));
  221. m_grouped = new TQCheckBox(i18n("Allow grouping"), optionsGroup);
  222. TQWhatsThis::add(m_grouped, i18n("If checked, this field may be used to group the entries in "
  223. "the group view."));
  224. connect(m_complete, TQT_SIGNAL(clicked()), TQT_SLOT(slotModified()));
  225. connect(m_multiple, TQT_SIGNAL(clicked()), TQT_SLOT(slotModified()));
  226. connect(m_grouped, TQT_SIGNAL(clicked()), TQT_SLOT(slotModified()));
  227. // need to stretch at bottom
  228. vbox->setStretchFactor(new TQWidget(vbox), 1);
  229. // keep a default collection
  230. m_defaultCollection = CollectionFactory::collection(m_coll->type(), true);
  231. TQWhatsThis::add(actionButton(KDialogBase::Default),
  232. i18n("Revert the selected field's properties to the default values."));
  233. enableButtonOK(false);
  234. enableButtonApply(false);
  235. setHelp(TQString::fromLatin1("fields-dialog"));
  236. // initially the m_typeCombo is populated with all types, but as soon as something is
  237. // selected in the fields box, the combo box is cleared and filled with the allowable
  238. // new types. The problem is that when more types are added, the size of the combo box
  239. // doesn't change. So when everything is laid out, the combo box needs to have all the
  240. // items there.
  241. TQTimer::singleShot(0, this, TQT_SLOT(slotSelectInitial()));
  242. }
  243. CollectionFieldsDialog::~CollectionFieldsDialog() {
  244. }
  245. void CollectionFieldsDialog::slotSelectInitial() {
  246. // the accel management is here so that it doesn't cause conflicts with the
  247. // ones explicitly set in the constructor
  248. TDEAcceleratorManager::manage(mainWidget());
  249. m_fieldsBox->setSelected(0, true);
  250. }
  251. void CollectionFieldsDialog::slotOk() {
  252. updateField();
  253. if(!checkValues()) {
  254. return;
  255. }
  256. applyChanges();
  257. accept();
  258. }
  259. void CollectionFieldsDialog::slotApply() {
  260. updateField();
  261. if(!checkValues()) {
  262. return;
  263. }
  264. applyChanges();
  265. }
  266. void CollectionFieldsDialog::applyChanges() {
  267. // start a command group, "Modify" is a generic term here since the commands could be add, modify, or delete
  268. Kernel::self()->beginCommandGroup(i18n("Modify Fields"));
  269. Data::FieldPtr field;
  270. for(Data::FieldVec::Iterator it = m_copiedFields.begin(); it != m_copiedFields.end(); ++it) {
  271. field = it;
  272. // check for Choice fields with removed values to warn user
  273. if(field->type() == Data::Field::Choice || field->type() == Data::Field::Rating) {
  274. TQStringList oldValues = m_coll->fieldByName(field->name())->allowed();
  275. TQStringList newValues = field->allowed();
  276. for(TQStringList::ConstIterator vIt = oldValues.begin(); vIt != oldValues.end(); ++vIt) {
  277. if(newValues.contains(*vIt)) {
  278. continue;
  279. }
  280. int ret = KMessageBox::warningContinueCancel(this,
  281. i18n("<qt>Removing allowed values from the <i>%1</i> field which "
  282. "currently exist in the collection may cause data corruption. "
  283. "Do you want to keep your modified values or cancel and revert "
  284. "to the current ones?</qt>").arg(field->title()),
  285. TQString(),
  286. i18n("Keep modified values"));
  287. if(ret != KMessageBox::Continue) {
  288. if(field->type() == Data::Field::Choice) {
  289. field->setAllowed(oldValues);
  290. } else { // rating field
  291. Data::FieldPtr oldField = m_coll->fieldByName(field->name());
  292. field->setProperty(TQString::fromLatin1("minimum"), oldField->property(TQString::fromLatin1("minimum")));
  293. field->setProperty(TQString::fromLatin1("maximum"), oldField->property(TQString::fromLatin1("maximum")));
  294. }
  295. }
  296. break;
  297. }
  298. }
  299. Kernel::self()->modifyField(field);
  300. }
  301. for(Data::FieldVec::Iterator it = m_newFields.begin(); it != m_newFields.end(); ++it) {
  302. Kernel::self()->addField(it);
  303. }
  304. // set all text not to be colored, and get new list
  305. Data::FieldVec fields;
  306. for(TQListBoxItem* item = m_fieldsBox->firstItem(); item; item = item->next()) {
  307. static_cast<FieldListBox*>(item)->setColored(false);
  308. if(m_reordered) {
  309. Data::FieldPtr field = static_cast<FieldListBox*>(item)->field();
  310. if(field) {
  311. fields.append(field);
  312. }
  313. }
  314. }
  315. // if reordering fields, need to add ReadOnly fields since they were not shown
  316. if(m_reordered) {
  317. Data::FieldVec allFields = m_coll->fields();
  318. for(Data::FieldVec::Iterator it = allFields.begin(); it != allFields.end(); ++it) {
  319. if(it->type() == Data::Field::ReadOnly) {
  320. fields.append(it);
  321. }
  322. }
  323. }
  324. if(fields.count() > 0) {
  325. Kernel::self()->reorderFields(fields);
  326. }
  327. // commit command group
  328. Kernel::self()->endCommandGroup();
  329. // now clear copied fields
  330. m_copiedFields.clear();
  331. // clear new ones, too
  332. m_newFields.clear();
  333. m_currentField = static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->field();
  334. // the field type might have changed, so need to update the type combo list with possible values
  335. if(m_currentField) {
  336. // set the updating flag since the values are changing and slots are firing
  337. // but we don't care about UI indications of changes
  338. bool wasUpdating = m_updatingValues;
  339. m_updatingValues = true;
  340. TQString currType = m_typeCombo->currentText();
  341. m_typeCombo->clear();
  342. m_typeCombo->insertStringList(newTypesAllowed(m_currentField->type()));
  343. m_typeCombo->setCurrentItem(currType);
  344. // description might have been changed for dependent fields
  345. m_descEdit->setText(m_currentField->description());
  346. m_updatingValues = wasUpdating;
  347. }
  348. enableButtonApply(false);
  349. }
  350. void CollectionFieldsDialog::slotNew() {
  351. // first update the current one with all the values from the edit widgets
  352. updateField();
  353. // next check old values
  354. if(!checkValues()) {
  355. return;
  356. }
  357. TQString name = TQString::fromLatin1("custom") + TQString::number(m_newFields.count()+1);
  358. size_t count = m_newFields.count() + 1;
  359. TQString title = i18n("New Field") + TQString::fromLatin1(" %1").arg(count);
  360. while(m_fieldsBox->findItem(title)) {
  361. ++count;
  362. title = i18n("New Field") + TQString::fromLatin1(" %1").arg(count);
  363. }
  364. Data::FieldPtr field = new Data::Field(name, title);
  365. m_newFields.append(field);
  366. // myDebug() << "CollectionFieldsDialog::slotNew() - adding new field " << title << endl;
  367. m_currentField = field;
  368. FieldListBox* box = new FieldListBox(m_fieldsBox, field);
  369. m_fieldsBox->setSelected(box, true);
  370. box->setColored(true);
  371. m_fieldsBox->ensureCurrentVisible();
  372. slotModified();
  373. m_titleEdit->setFocus();
  374. m_titleEdit->selectAll();
  375. }
  376. void CollectionFieldsDialog::slotDelete() {
  377. if(!m_currentField) {
  378. return;
  379. }
  380. if(m_newFields.contains(m_currentField)) {
  381. // remove field from vector before deleting item containing field
  382. m_newFields.remove(m_currentField);
  383. m_fieldsBox->removeItem(m_fieldsBox->currentItem());
  384. m_fieldsBox->setSelected(m_fieldsBox->currentItem(), true);
  385. m_fieldsBox->ensureCurrentVisible();
  386. m_currentField = static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->field(); // TDEShared gets auto-deleted
  387. return;
  388. }
  389. bool success = Kernel::self()->removeField(m_currentField);
  390. if(success) {
  391. emit signalCollectionModified();
  392. m_fieldsBox->removeItem(m_fieldsBox->currentItem());
  393. m_fieldsBox->setSelected(m_fieldsBox->currentItem(), true);
  394. m_fieldsBox->ensureCurrentVisible();
  395. m_currentField = static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->field();
  396. enableButtonOK(true);
  397. }
  398. }
  399. void CollectionFieldsDialog::slotTypeChanged(const TQString& type_) {
  400. Data::Field::Type type = Data::Field::Undef;
  401. const Data::Field::FieldMap fieldMap = Data::Field::typeMap();
  402. for(Data::Field::FieldMap::ConstIterator it = fieldMap.begin(); it != fieldMap.end(); ++it) {
  403. if(it.data() == type_) {
  404. type = it.key();
  405. break;
  406. }
  407. }
  408. if(type == Data::Field::Undef) {
  409. kdWarning() << "CollectionFieldsDialog::slotTypeChanged() - type name not recognized: " << type_ << endl;
  410. type = Data::Field::Line;
  411. }
  412. // only choice types gets allowed values
  413. m_allowEdit->setEnabled(type == Data::Field::Choice);
  414. // paragraphs, tables, and images are their own category
  415. bool isCategory = (type == Data::Field::Para || type == Data::Field::Table ||
  416. type == Data::Field::Table2 || type == Data::Field::Image);
  417. m_catCombo->setEnabled(!isCategory);
  418. // formatting is only applicable when the type is simple text or a table
  419. bool isText = (type == Data::Field::Line || type == Data::Field::Table ||
  420. type == Data::Field::Table2);
  421. // formatNone is the default
  422. m_formatPlain->setEnabled(isText);
  423. m_formatName->setEnabled(isText);
  424. m_formatTitle->setEnabled(isText);
  425. // multiple is only applicable for simple text and number
  426. isText = (type == Data::Field::Line || type == Data::Field::Number);
  427. m_multiple->setEnabled(isText);
  428. // completion is only applicable for simple text, number, and URL
  429. isText = (isText || type == Data::Field::URL);
  430. m_complete->setEnabled(isText);
  431. // grouping is not possible with paragraphs or images
  432. m_grouped->setEnabled(type != Data::Field::Para && type != Data::Field::Image);
  433. }
  434. void CollectionFieldsDialog::slotHighlightedChanged(int index_) {
  435. // myDebug() << "CollectionFieldsDialog::slotHighlightedChanged() - " << index_ << endl;
  436. // use this instead of blocking signals everywhere
  437. m_updatingValues = true;
  438. // first update the current one with all the values from the edit widgets
  439. updateField();
  440. // next check old values
  441. if(!checkValues()) {
  442. m_fieldsBox->blockSignals(true);
  443. m_fieldsBox->setSelected(m_oldIndex, true);
  444. m_fieldsBox->blockSignals(false);
  445. m_updatingValues = false;
  446. return;
  447. }
  448. m_oldIndex = index_;
  449. m_btnUp->setEnabled(index_ > 0);
  450. m_btnDown->setEnabled(index_ < static_cast<int>(m_fieldsBox->count())-1);
  451. FieldListBox* item = dynamic_cast<FieldListBox*>(m_fieldsBox->item(index_));
  452. if(!item) {
  453. return;
  454. }
  455. // need to get a pointer to the field with the new values to insert
  456. Data::FieldPtr field = item->field();
  457. if(!field) {
  458. myDebug() << "CollectionFieldsDialog::slotHighlightedChanged() - no field found!" << endl;
  459. return;
  460. }
  461. m_titleEdit->setText(field->title());
  462. // type is limited to certain types, unless it's a new field
  463. m_typeCombo->clear();
  464. if(m_newFields.contains(field)) {
  465. m_typeCombo->insertStringList(newTypesAllowed(Data::Field::Undef));
  466. } else {
  467. m_typeCombo->insertStringList(newTypesAllowed(field->type()));
  468. }
  469. // if the current name is not there, then this will change the list!
  470. const Data::Field::FieldMap& fieldMap = Data::Field::typeMap();
  471. m_typeCombo->setCurrentText(fieldMap[field->type()]);
  472. slotTypeChanged(fieldMap[field->type()]); // just setting the text doesn't emit the activated signal
  473. if(field->type() == Data::Field::Choice) {
  474. m_allowEdit->setText(field->allowed().join(TQString::fromLatin1("; ")));
  475. } else {
  476. m_allowEdit->clear();
  477. }
  478. m_catCombo->setCurrentText(field->category()); // have to do this here
  479. m_descEdit->setText(field->description());
  480. m_defaultEdit->setText(field->defaultValue());
  481. switch(field->formatFlag()) {
  482. case Data::Field::FormatNone:
  483. case Data::Field::FormatDate: // as yet unimplemented
  484. m_formatNone->setChecked(true);
  485. break;
  486. case Data::Field::FormatPlain:
  487. m_formatPlain->setChecked(true);
  488. break;
  489. case Data::Field::FormatTitle:
  490. m_formatTitle->setChecked(true);
  491. break;
  492. case Data::Field::FormatName:
  493. m_formatName->setChecked(true);
  494. break;
  495. default:
  496. kdWarning() << "CollectionFieldsDialog::slotHighlightedChanged() - no format type!" << endl;
  497. break;
  498. }
  499. int flags = field->flags();
  500. m_complete->setChecked(flags & Data::Field::AllowCompletion);
  501. m_multiple->setChecked(flags & Data::Field::AllowMultiple);
  502. m_grouped->setChecked(flags & Data::Field::AllowGrouped);
  503. m_btnDelete->setEnabled(!(flags & Data::Field::NoDelete));
  504. // default button is enabled only if default collection contains the field
  505. if(m_defaultCollection) {
  506. bool hasField = m_defaultCollection->hasField(field->name());
  507. actionButton(KDialogBase::Default)->setEnabled(hasField);
  508. }
  509. m_currentField = field;
  510. m_updatingValues = false;
  511. }
  512. void CollectionFieldsDialog::updateField() {
  513. // myDebug() << "CollectionFieldsDialog::updateField()" << endl;
  514. Data::FieldPtr field = m_currentField;
  515. if(!field || !m_modified) {
  516. return;
  517. }
  518. // only update name if it's one of the new ones
  519. if(m_newFields.contains(field)) {
  520. // name needs to be a valid XML element name
  521. TQString name = XML::elementName(m_titleEdit->text().lower());
  522. if(name.isEmpty()) { // might end up with empty string
  523. name = TQString::fromLatin1("custom") + TQString::number(m_newFields.count()+1);
  524. }
  525. while(m_coll->hasField(name)) { // ensure name uniqueness
  526. name += TQString::fromLatin1("-new");
  527. }
  528. field->setName(name);
  529. }
  530. const TQString title = m_titleEdit->text().simplifyWhiteSpace();
  531. updateTitle(title);
  532. const Data::Field::FieldMap& fieldMap = Data::Field::typeMap();
  533. for(Data::Field::FieldMap::ConstIterator it = fieldMap.begin(); it != fieldMap.end(); ++it) {
  534. if(it.data() == m_typeCombo->currentText()) {
  535. field->setType(it.key());
  536. break;
  537. }
  538. }
  539. if(field->type() == Data::Field::Choice) {
  540. const TQRegExp rx(TQString::fromLatin1("\\s*;\\s*"));
  541. field->setAllowed(TQStringList::split(rx, m_allowEdit->text()));
  542. field->setProperty(TQString::fromLatin1("minimum"), TQString());
  543. field->setProperty(TQString::fromLatin1("maximum"), TQString());
  544. } else if(field->type() == Data::Field::Rating) {
  545. TQString v = field->property(TQString::fromLatin1("minimum"));
  546. if(v.isEmpty()) {
  547. field->setProperty(TQString::fromLatin1("minimum"), TQString::number(1));
  548. }
  549. v = field->property(TQString::fromLatin1("maximum"));
  550. if(v.isEmpty()) {
  551. field->setProperty(TQString::fromLatin1("maximum"), TQString::number(5));
  552. }
  553. }
  554. if(field->isSingleCategory()) {
  555. field->setCategory(field->title());
  556. } else {
  557. TQString category = m_catCombo->currentText().simplifyWhiteSpace();
  558. field->setCategory(category);
  559. m_catCombo->setCurrentItem(category, true); // if it doesn't exist, it's added
  560. }
  561. field->setDescription(m_descEdit->text());
  562. field->setDefaultValue(m_defaultEdit->text());
  563. if(m_formatTitle->isChecked()) {
  564. field->setFormatFlag(Data::Field::FormatTitle);
  565. } else if(m_formatName->isChecked()) {
  566. field->setFormatFlag(Data::Field::FormatName);
  567. } else if(m_formatPlain->isChecked()) {
  568. field->setFormatFlag(Data::Field::FormatPlain);
  569. } else {
  570. field->setFormatFlag(Data::Field::FormatNone);
  571. }
  572. int flags = 0;
  573. if(m_complete->isChecked()) {
  574. flags |= Data::Field::AllowCompletion;
  575. }
  576. if(m_grouped->isChecked()) {
  577. flags |= Data::Field::AllowGrouped;
  578. }
  579. if(m_multiple->isChecked()) {
  580. flags |= Data::Field::AllowMultiple;
  581. }
  582. field->setFlags(flags);
  583. m_modified = false;
  584. }
  585. // The purpose here is to first set the modified flag. Then, if the field being edited is one
  586. // that exists in the collection already, a deep copy needs to be made.
  587. void CollectionFieldsDialog::slotModified() {
  588. // myDebug() << "CollectionFieldsDialog::slotModified()" << endl;
  589. // if I'm just updating the values, I don't care
  590. if(m_updatingValues) {
  591. return;
  592. }
  593. m_modified = true;
  594. enableButtonOK(true);
  595. enableButtonApply(true);
  596. if(!m_currentField) {
  597. myDebug() << "CollectionFieldsDialog::slotModified() - no current field!" << endl;
  598. m_currentField = static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->field();
  599. }
  600. // color the text
  601. static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->setColored(true);
  602. // check if copy exists already
  603. if(m_copiedFields.contains(m_currentField)) {
  604. return;
  605. }
  606. // or, check if is a new field, in which case no copy is needed
  607. // check if copy exists already
  608. if(m_newFields.contains(m_currentField)) {
  609. return;
  610. }
  611. m_currentField = new Data::Field(*m_currentField);
  612. m_copiedFields.append(m_currentField);
  613. static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->setField(m_currentField);
  614. }
  615. void CollectionFieldsDialog::updateTitle(const TQString& title_) {
  616. // myDebug() << "CollectionFieldsDialog::updateTitle()" << endl;
  617. if(m_currentField && m_currentField->title() != title_) {
  618. m_fieldsBox->blockSignals(true);
  619. FieldListBox* oldItem = findItem(m_fieldsBox, m_currentField);
  620. if(!oldItem) {
  621. return;
  622. }
  623. oldItem->setText(title_);
  624. // will always be colored since it's new
  625. oldItem->setColored(true);
  626. m_fieldsBox->triggerUpdate(true);
  627. m_currentField->setTitle(title_);
  628. m_fieldsBox->blockSignals(false);
  629. }
  630. }
  631. void CollectionFieldsDialog::slotDefault() {
  632. if(!m_currentField) {
  633. return;
  634. }
  635. Data::FieldPtr defaultField = m_defaultCollection->fieldByName(m_currentField->name());
  636. if(!defaultField) {
  637. return;
  638. }
  639. TQString caption = i18n("Revert Field Properties");
  640. TQString text = i18n("<qt><p>Do you really want to revert the properties for the <em>%1</em> "
  641. "field back to their default values?</p></qt>").arg(m_currentField->title());
  642. TQString dontAsk = TQString::fromLatin1("RevertFieldProperties");
  643. int ret = KMessageBox::warningContinueCancel(this, text, caption, i18n("Revert"), dontAsk);
  644. if(ret != KMessageBox::Continue) {
  645. return;
  646. }
  647. // now update all values with default
  648. m_updatingValues = true;
  649. m_titleEdit->setText(defaultField->title());
  650. const Data::Field::FieldMap& fieldMap = Data::Field::typeMap();
  651. m_typeCombo->setCurrentText(fieldMap[defaultField->type()]);
  652. slotTypeChanged(fieldMap[defaultField->type()]); // just setting the text doesn't emit the activated signal
  653. if(defaultField->type() == Data::Field::Choice) {
  654. m_allowEdit->setText(defaultField->allowed().join(TQString::fromLatin1("; ")));
  655. } else {
  656. m_allowEdit->clear();
  657. }
  658. m_catCombo->setCurrentText(defaultField->category()); // have to do this here
  659. m_descEdit->setText(defaultField->description());
  660. m_defaultEdit->setText(defaultField->defaultValue());
  661. switch(defaultField->formatFlag()) {
  662. case Data::Field::FormatNone:
  663. case Data::Field::FormatDate:
  664. m_formatNone->setChecked(true);
  665. break;
  666. case Data::Field::FormatPlain:
  667. m_formatPlain->setChecked(true);
  668. break;
  669. case Data::Field::FormatTitle:
  670. m_formatTitle->setChecked(true);
  671. break;
  672. case Data::Field::FormatName:
  673. m_formatName->setChecked(true);
  674. break;
  675. default:
  676. break;
  677. }
  678. int flags = defaultField->flags();
  679. m_complete->setChecked(flags & Data::Field::AllowCompletion);
  680. m_multiple->setChecked(flags & Data::Field::AllowMultiple);
  681. m_grouped->setChecked(flags & Data::Field::AllowGrouped);
  682. m_btnDelete->setEnabled(!(defaultField->flags() & Data::Field::NoDelete));
  683. // m_titleEdit->setFocus();
  684. // m_titleEdit->selectAll();
  685. m_updatingValues = false;
  686. slotModified();
  687. }
  688. void CollectionFieldsDialog::slotMoveUp() {
  689. TQListBoxItem* item = m_fieldsBox->selectedItem();
  690. if(item) {
  691. FieldListBox* prev = static_cast<FieldListBox*>(item->prev()); // could be 0
  692. if(prev) {
  693. FieldListBox* newPrev = new FieldListBox(m_fieldsBox, prev->field(), item);
  694. newPrev->setColored(prev->isColored());
  695. delete prev;
  696. m_fieldsBox->ensureCurrentVisible();
  697. // since the current one doesn't get re-highlighted, need to highlighted doesn't get emitted
  698. slotHighlightedChanged(m_fieldsBox->currentItem());
  699. }
  700. }
  701. m_reordered = true;
  702. // don't call slotModified() since that creates a deep copy.
  703. m_modified = true;
  704. enableButtonOK(true);
  705. enableButtonApply(true);
  706. }
  707. void CollectionFieldsDialog::slotMoveDown() {
  708. FieldListBox* item = dynamic_cast<FieldListBox*>(m_fieldsBox->selectedItem());
  709. if(item) {
  710. TQListBoxItem* next = item->next(); // could be 0
  711. if(next) {
  712. FieldListBox* newItem = new FieldListBox(m_fieldsBox, item->field(), next);
  713. newItem->setColored(item->isColored());
  714. delete item;
  715. m_fieldsBox->setSelected(newItem, true);
  716. m_fieldsBox->ensureCurrentVisible();
  717. }
  718. }
  719. m_reordered = true;
  720. // don't call slotModified() since that creates a deep copy.
  721. m_modified = true;
  722. enableButtonOK(true);
  723. enableButtonApply(true);
  724. }
  725. Tellico::FieldListBox* CollectionFieldsDialog::findItem(const TQListBox* box_, Data::FieldPtr field_) {
  726. // myDebug() << "CollectionFieldsDialog::findItem()" << endl;
  727. for(TQListBoxItem* item = box_->firstItem(); item; item = item->next()) {
  728. FieldListBox* textItem = static_cast<FieldListBox*>(item);
  729. if(textItem->field() == field_) {
  730. return textItem;
  731. }
  732. }
  733. return 0;
  734. }
  735. bool CollectionFieldsDialog::slotShowExtendedProperties() {
  736. if(!m_currentField) {
  737. return false;
  738. }
  739. // the default value is included in properties, but it has a
  740. // separate edit box
  741. TQString dv = m_currentField->defaultValue();
  742. StringMap props = m_currentField->propertyList();
  743. props.remove(TQString::fromLatin1("default"));
  744. StringMapDialog dlg(props, this, "ExtendedPropertiesDialog", true);
  745. dlg.setCaption(i18n("Extended Field Properties"));
  746. dlg.setLabels(i18n("Property"), i18n("Value"));
  747. if(dlg.exec() == TQDialog::Accepted) {
  748. props = dlg.stringMap();
  749. if(!dv.isEmpty()) {
  750. props.insert(TQString::fromLatin1("default"), dv);
  751. }
  752. m_currentField->setPropertyList(props);
  753. slotModified();
  754. return true;
  755. }
  756. return false;
  757. }
  758. bool CollectionFieldsDialog::checkValues() {
  759. if(!m_currentField) {
  760. return true;
  761. }
  762. const TQString title = m_currentField->title();
  763. // find total number of boxes with this title in case multiple new ones with same title were added
  764. int titleCount = 0;
  765. for(uint i = 0; i < m_fieldsBox->count(); ++i) {
  766. if(m_fieldsBox->item(i)->text() == title) {
  767. ++titleCount;
  768. }
  769. }
  770. if((m_coll->fieldByTitle(title) && m_coll->fieldNameByTitle(title) != m_currentField->name()) ||
  771. titleCount > 1) {
  772. // already have a field with this title
  773. KMessageBox::sorry(this, i18n("A field with this title already exists. Please enter a different title."));
  774. m_titleEdit->selectAll();
  775. return false;
  776. }
  777. const TQString category = m_currentField->category();
  778. if(category.isEmpty()) {
  779. KMessageBox::sorry(this, i18n("<qt>The category may not be empty. Please enter a category.</qt>"));
  780. m_catCombo->lineEdit()->selectAll();
  781. return false;
  782. }
  783. Data::FieldVec fields = m_coll->fieldsByCategory(category);
  784. if(!fields.isEmpty() && fields.begin()->isSingleCategory() && fields.begin()->name() != m_currentField->name()) {
  785. // can't have this category, cause it conflicts with a single-category field
  786. KMessageBox::sorry(this, i18n("<qt>A field may not be in the same category as a <em>Paragraph</em>, "
  787. "<em>Table</em> or <em>Image</em> field. Please enter a different category.</qt>"));
  788. m_catCombo->lineEdit()->selectAll();
  789. return false;
  790. }
  791. // the combobox is disabled for single-category fields
  792. if(!m_catCombo->isEnabled() && m_coll->fieldByTitle(title) && m_coll->fieldNameByTitle(title) != m_currentField->name()) {
  793. KMessageBox::sorry(this, i18n("A field's title may not be the same as an existing category. "
  794. "Please enter a different title."));
  795. m_titleEdit->selectAll();
  796. return false;
  797. }
  798. // check for rating values outside bounds
  799. if(m_currentField->type() == Data::Field::Rating) {
  800. bool ok; // ok to ignore this here
  801. int low = Tellico::toUInt(m_currentField->property(TQString::fromLatin1("minimum")), &ok);
  802. int high = Tellico::toUInt(m_currentField->property(TQString::fromLatin1("maximum")), &ok);
  803. while(low < 1 || low > 9 || high < 1 || high > 10 || low >= high) {
  804. KMessageBox::sorry(this, i18n("The range for a rating field must be between 1 and 10, "
  805. "and the lower bound must be less than the higher bound. "
  806. "Please enter different low and high properties."));
  807. if(slotShowExtendedProperties()) {
  808. low = Tellico::toUInt(m_currentField->property(TQString::fromLatin1("minimum")), &ok);
  809. high = Tellico::toUInt(m_currentField->property(TQString::fromLatin1("maximum")), &ok);
  810. } else {
  811. return false;
  812. }
  813. }
  814. } else if(m_currentField->type() == Data::Field::Table) {
  815. bool ok; // ok to ignore this here
  816. int ncols = Tellico::toUInt(m_currentField->property(TQString::fromLatin1("columns")), &ok);
  817. // also enforced in GUI::TableFieldWidget
  818. if(ncols > 10) {
  819. KMessageBox::sorry(this, i18n("Tables are limited to a maximum of ten columns."));
  820. m_currentField->setProperty(TQString::fromLatin1("columns"), TQString::fromLatin1("10"));
  821. }
  822. }
  823. return true;
  824. }
  825. // only certain type changes are allowed
  826. TQStringList CollectionFieldsDialog::newTypesAllowed(int type_ /*=0*/) {
  827. // Undef means return all
  828. if(type_ == Data::Field::Undef) {
  829. return Data::Field::typeTitles();
  830. }
  831. const Data::Field::FieldMap& fieldMap = Data::Field::typeMap();
  832. TQStringList newTypes;
  833. switch(type_) {
  834. case Data::Field::Line: // might not work if converted to a number or URL, but ok
  835. case Data::Field::Number:
  836. case Data::Field::URL:
  837. newTypes += fieldMap[Data::Field::Line];
  838. newTypes += fieldMap[Data::Field::Para];
  839. newTypes += fieldMap[Data::Field::Number];
  840. newTypes += fieldMap[Data::Field::URL];
  841. newTypes += fieldMap[Data::Field::Table];
  842. break;
  843. case Data::Field::Date:
  844. newTypes += fieldMap[Data::Field::Line];
  845. newTypes += fieldMap[Data::Field::Date];
  846. break;
  847. case Data::Field::Bool: // doesn't really make sense, but can't hurt
  848. newTypes += fieldMap[Data::Field::Line];
  849. newTypes += fieldMap[Data::Field::Para];
  850. newTypes += fieldMap[Data::Field::Bool];
  851. newTypes += fieldMap[Data::Field::Number];
  852. newTypes += fieldMap[Data::Field::URL];
  853. newTypes += fieldMap[Data::Field::Table];
  854. break;
  855. case Data::Field::Choice:
  856. newTypes += fieldMap[Data::Field::Line];
  857. newTypes += fieldMap[Data::Field::Para];
  858. newTypes += fieldMap[Data::Field::Choice];
  859. newTypes += fieldMap[Data::Field::Number];
  860. newTypes += fieldMap[Data::Field::URL];
  861. newTypes += fieldMap[Data::Field::Table];
  862. newTypes += fieldMap[Data::Field::Rating];
  863. break;
  864. case Data::Field::Table: // not really a good idea since the "::" will be exposed, but allow it
  865. case Data::Field::Table2:
  866. newTypes += fieldMap[Data::Field::Line];
  867. newTypes += fieldMap[Data::Field::Number];
  868. newTypes += fieldMap[Data::Field::Table];
  869. break;
  870. case Data::Field::Para:
  871. newTypes += fieldMap[Data::Field::Line];
  872. newTypes += fieldMap[Data::Field::Para];
  873. break;
  874. case Data::Field::Rating:
  875. newTypes += fieldMap[Data::Field::Choice];
  876. newTypes += fieldMap[Data::Field::Rating];
  877. break;
  878. // these can never be changed
  879. case Data::Field::Image:
  880. case Data::Field::Dependent:
  881. newTypes = fieldMap[static_cast<Data::Field::Type>(type_)];
  882. break;
  883. default:
  884. myDebug() << "CollectionFieldsDialog::newTypesAllowed() - no match for " << type_ << endl;
  885. newTypes = Data::Field::typeTitles();
  886. break;
  887. }
  888. return newTypes;
  889. }
  890. #include "collectionfieldsdialog.moc"