1 /************************************************************************
2 **
3 ** Copyright (C) 2015-2021 Kevin B. Hendricks, Stratford, Ontario, Canada
4 ** Copyright (C) 2012 John Schember <john@nachtimwald.com>
5 ** Copyright (C) 2012 Dave Heiland
6 ** Copyright (C) 2012 Grant Drake
7 **
8 ** This file is part of Sigil.
9 **
10 ** Sigil is free software: you can redistribute it and/or modify
11 ** it under the terms of the GNU General Public License as published by
12 ** the Free Software Foundation, either version 3 of the License, or
13 ** (at your option) any later version.
14 **
15 ** Sigil is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ** GNU General Public License for more details.
19 **
20 ** You should have received a copy of the GNU General Public License
21 ** along with Sigil. If not, see <http://www.gnu.org/licenses/>.
22 **
23 *************************************************************************/
24
25 #include <QtCore/QCoreApplication>
26 #include <QByteArray>
27 #include <QDataStream>
28 #include <QtCore/QTime>
29 #include <QFileInfo>
30 #include <QFile>
31 #include <QRegularExpression>
32 #include <QDebug>
33
34 #include "MiscEditors/SearchEditorModel.h"
35 #include "Misc/Utility.h"
36 #include "sigil_constants.h"
37 #include "sigil_exception.h"
38
39 static const QString SETTINGS_FILE = "sigil_searches_v2.ini";
40 static const QString OLD_SETTINGS_FILE = "sigil_searches.ini";
41
42 static const QString SETTINGS_GROUP = "search_entries";
43 static const QString ENTRY_NAME = "Name";
44 static const QString ENTRY_FIND = "Find";
45 static const QString ENTRY_REPLACE = "Replace";
46 static const QString ENTRY_CONTROLS = "Controls";
47
48 const int COLUMNS = 4;
49
50 static const int IS_GROUP_ROLE = Qt::UserRole + 1;
51 static const int FULLNAME_ROLE = Qt::UserRole + 2;
52
53 static const QString SEARCH_EXAMPLES_FILE = "search_entries.ini";
54
55 SearchEditorModel *SearchEditorModel::m_instance = 0;
56
instance()57 SearchEditorModel *SearchEditorModel::instance()
58 {
59 if (m_instance == 0) {
60 m_instance = new SearchEditorModel();
61 }
62
63 return m_instance;
64 }
65
SearchEditorModel(QObject * parent)66 SearchEditorModel::SearchEditorModel(QObject *parent)
67 : QStandardItemModel(parent),
68 m_FSWatcher(new QFileSystemWatcher()),
69 m_IsDataModified(false)
70 {
71 m_SettingsPath = Utility::DefinePrefsDir() + "/" + SETTINGS_FILE;
72 QString OldSettingsPath = Utility::DefinePrefsDir() + "/" + OLD_SETTINGS_FILE;
73 QFileInfo fi(m_SettingsPath);
74 if (!fi.exists()) {
75 QFileInfo oldfi(OldSettingsPath);
76 if (oldfi.exists() && oldfi.isFile()) {
77 // create v2 settings file from old one
78 bool success = QFile::copy(oldfi.absoluteFilePath(), m_SettingsPath);
79 if (!success) {
80 qDebug() << "Failed to create v2 saved searches from old";
81 }
82 }
83 }
84 QStringList header;
85 header.append(tr("Name"));
86 header.append(tr("Find"));
87 header.append(tr("Replace"));
88 header.append(tr("Controls"));
89 setHorizontalHeaderLabels(header);
90 LoadInitialData();
91 // Save it to make sure we have a file in case it was loaded from examples
92 SaveData();
93
94 if (!m_FSWatcher->files().contains(m_SettingsPath)) {
95 m_FSWatcher->addPath(m_SettingsPath);
96 }
97
98 connect(m_FSWatcher, SIGNAL(fileChanged(const QString &)),
99 this, SLOT(SettingsFileChanged(const QString &)), Qt::DirectConnection);
100 connect(this, SIGNAL(itemChanged(QStandardItem *)),
101 this, SLOT(ItemChangedHandler(QStandardItem *)));
102 connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
103 this, SLOT(RowsRemovedHandler(const QModelIndex &, int, int)));
104 }
105
~SearchEditorModel()106 SearchEditorModel::~SearchEditorModel()
107 {
108 if (m_instance) {
109 delete m_instance;
110 m_instance = 0;
111 }
112 }
113
SetDataModified(bool modified)114 void SearchEditorModel::SetDataModified(bool modified)
115 {
116 m_IsDataModified = modified;
117 }
118
IsDataModified()119 bool SearchEditorModel::IsDataModified()
120 {
121 return m_IsDataModified;
122 }
123
supportedDropActions() const124 Qt::DropActions SearchEditorModel::supportedDropActions() const
125 {
126 return Qt::MoveAction;
127 }
128
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)129 bool SearchEditorModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
130 {
131 QModelIndex new_parent(parent);
132
133 if (parent.column() > 0) {
134 // User is trying to drop onto a child column - treat it as though they are dropping in the initial column.
135 new_parent = index(parent.row(), 0, parent.parent());
136 }
137
138 if (column > 0) {
139 // Same as above but for when user drops into the between row selector in a child column.
140 column = 0;
141 }
142
143 // If dropped on an non-group entry convert to parent item group/row
144 if (new_parent.isValid() && !itemFromIndex(new_parent)->data(IS_GROUP_ROLE).toBool()) {
145 row = new_parent.row() + 1;
146 new_parent = new_parent.parent();
147 }
148
149 if (!QStandardItemModel::dropMimeData(data, action, row, column, new_parent)) {
150 return false;
151 }
152
153 // As our drag/drop completed successfully, recalculate the fullname for all the items in the model
154 UpdateFullName(invisibleRootItem());
155
156 // If we dropped onto a parent group, make sure it is expanded.
157 if (new_parent.isValid()) {
158 emit ItemDropped(new_parent);
159 }
160
161 SetDataModified(true);
162 return true;
163 }
164
RowsRemovedHandler(const QModelIndex & parent,int start,int end)165 void SearchEditorModel::RowsRemovedHandler(const QModelIndex &parent, int start, int end)
166 {
167 SetDataModified(true);
168 }
169
170
ItemChangedHandler(QStandardItem * item)171 void SearchEditorModel::ItemChangedHandler(QStandardItem *item)
172 {
173 Q_ASSERT(item);
174
175 if (item->column() == 3) {
176 QString controls = item->text();
177 item->setToolTip(BuildControlsToolTip(controls));
178 }
179
180 if (item->column() != 0) {
181 SetDataModified(true);
182 return;
183 }
184
185 // Restore name if nothing entered or contains a group indicator
186 if (item->text().isEmpty() || item->text().contains("/")) {
187 QString name = item->data(FULLNAME_ROLE).toString();
188 name.replace(QRegularExpression("/$"), "");
189
190 if (name.contains("/")) {
191 name = name.split("/").last();
192 }
193
194 item->setText(name);
195 return;
196 }
197
198 Rename(item);
199 }
200
Rename(QStandardItem * item,const QString & name)201 void SearchEditorModel::Rename(QStandardItem *item, const QString &name)
202 {
203 // Update the name and all its children
204 // Disconnect change signal while changing the items
205 disconnect(this, SIGNAL(itemChanged(QStandardItem *)),
206 this, SLOT(ItemChangedHandler(QStandardItem *)));
207
208 // If item name not already changed, set it
209 if (name != "") {
210 disconnect(this, SIGNAL(itemChanged(QStandardItem *)),
211 this, SLOT(ItemChangedHandler(QStandardItem *)));
212 item->setText(name);
213 connect(this, SIGNAL(itemChanged(QStandardItem *)),
214 this, SLOT(ItemChangedHandler(QStandardItem *)));
215 }
216
217 UpdateFullName(item);
218 connect(this, SIGNAL(itemChanged(QStandardItem *)),
219 this, SLOT(ItemChangedHandler(QStandardItem *)));
220 SetDataModified(true);
221 }
222
UpdateFullName(QStandardItem * item)223 void SearchEditorModel::UpdateFullName(QStandardItem *item)
224 {
225 QStandardItem *parent_item = item->parent();
226
227 if (!parent_item) {
228 parent_item = invisibleRootItem();
229 }
230
231 QString fullname = parent_item->data(FULLNAME_ROLE).toString() + item->text();
232
233 if (item->data(IS_GROUP_ROLE).toBool()) {
234 fullname.append("/");
235 }
236
237 item->setToolTip(fullname);
238 item->setData(fullname, FULLNAME_ROLE);
239
240 for (int row = 0; row < item->rowCount(); row++) {
241 UpdateFullName(item->child(row, 0));
242 }
243 }
244
SettingsFileChanged(const QString & path) const245 void SearchEditorModel::SettingsFileChanged(const QString &path) const
246 {
247 // The file may have been deleted prior to writing a new version - give it a chance to write.
248 QTime wake_time = QTime::currentTime().addMSecs(1000);
249
250 while (!QFile::exists(path) && QTime::currentTime() < wake_time) {
251 QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
252 }
253
254 // The signal is also received after resource files are removed / renamed,
255 // but it can be safely ignored because QFileSystemWatcher automatically stops watching them.
256 if (QFile::exists(path)) {
257 // Some editors write the updated contents to a temporary file
258 // and then atomically move it over the watched file.
259 // In this case QFileSystemWatcher loses track of the file, so we have to add it again.
260 if (!m_FSWatcher->files().contains(path)) {
261 m_FSWatcher->addPath(path);
262 }
263
264 instance()->LoadInitialData();
265 emit SettingsFileUpdated();
266 }
267 }
268
ItemIsGroup(QStandardItem * item)269 bool SearchEditorModel::ItemIsGroup(QStandardItem *item)
270 {
271 return item->data(IS_GROUP_ROLE).toBool();
272 }
273
GetFullName(QStandardItem * item)274 QString SearchEditorModel::GetFullName(QStandardItem *item)
275 {
276 return item->data(FULLNAME_ROLE).toString();
277 }
278
GetEntryFromName(const QString & name,QStandardItem * item)279 SearchEditorModel::searchEntry *SearchEditorModel::GetEntryFromName(const QString &name, QStandardItem *item)
280 {
281 return GetEntry(GetItemFromName(name, item));
282 }
283
GetItemFromName(const QString & name,QStandardItem * item)284 QStandardItem *SearchEditorModel::GetItemFromName(const QString &name, QStandardItem *item)
285 {
286 QStandardItem *found_item = NULL;
287
288 if (!item) {
289 item = invisibleRootItem();
290 }
291
292 if (item != invisibleRootItem() && item->data(FULLNAME_ROLE).toString() == name) {
293 return item;
294 }
295
296 for (int row = 0; row < item->rowCount(); row++) {
297 found_item = GetItemFromName(name, item->child(row, 0));
298
299 // Return with first found entry
300 if (found_item) {
301 return found_item;
302 }
303 }
304
305 return found_item;
306 }
307
LoadInitialData()308 void SearchEditorModel::LoadInitialData()
309 {
310 removeRows(0, rowCount());
311 LoadData();
312
313 if (invisibleRootItem()->rowCount() == 0) {
314 AddExampleEntries();
315 }
316
317 SetDataModified(false);
318 }
319
LoadData(const QString & filename,QStandardItem * item)320 void SearchEditorModel::LoadData(const QString &filename, QStandardItem *item)
321 {
322 QString settings_path = filename;
323 if (settings_path.isEmpty()) settings_path = m_SettingsPath;
324
325 SettingsStore ss(settings_path);
326
327 int size = ss.beginReadArray(SETTINGS_GROUP);
328
329 // Add one entry at a time to the list
330 for (int i = 0; i < size; ++i) {
331 ss.setArrayIndex(i);
332 SearchEditorModel::searchEntry *entry = new SearchEditorModel::searchEntry();
333 QString fullname = ss.value(ENTRY_NAME).toString();
334 fullname.replace(QRegularExpression("\\s*/+\\s*"), "/");
335 fullname.replace(QRegularExpression("^/"), "");
336 entry->is_group = fullname.endsWith("/");
337 // Name is set to fullname only while looping through parent groups when adding
338 entry->name = fullname;
339 entry->fullname = fullname;
340 entry->find = ss.value(ENTRY_FIND).toString();
341 entry->replace = ss.value(ENTRY_REPLACE).toString();
342 entry->controls = ss.value(ENTRY_CONTROLS, "").toString();
343 AddFullNameEntry(entry, item);
344 // done with the temporary entry so remove it
345 delete entry;
346 }
347 ss.endArray();
348 }
349
350
LoadTextData(const QString & filename,QStandardItem * item,const QChar & sep)351 void SearchEditorModel::LoadTextData(const QString &filename, QStandardItem *item, const QChar &sep)
352 {
353 QFileInfo fi(filename);
354 QString groupname = fi.fileName() + "/";
355 int cnt = 1;
356 if (fi.exists()) {
357 QString data = Utility::ReadUnicodeTextFile(filename);
358 QStringList datalines = data.split('\n');
359 foreach(QString aline, datalines) {
360 if (!aline.isEmpty()) {
361 QStringList findreplace;
362 if (sep == ',') {
363 findreplace = Utility::parseCSVLine(aline);
364 } else {
365 findreplace = aline.split(sep);
366 }
367 // add to end to prevent errors
368 findreplace << "" << "" << "" << "";
369 QString localname = "rep" + QStringLiteral("%1").arg(cnt, 5, 10, QLatin1Char('0'));
370 QString fullname;
371 SearchEditorModel::searchEntry *entry = new SearchEditorModel::searchEntry();
372 // if no name info appears
373 if (findreplace.at(0).isEmpty()) {
374 fullname = groupname + localname;
375 } else {
376 // this was created by an Export so fullname is present
377 fullname = findreplace.at(0);
378 }
379 fullname.replace(QRegularExpression("\\s*/+\\s*"), "/");
380 fullname.replace(QRegularExpression("^/"), "");
381 entry->is_group = fullname.endsWith("/");
382 // Name is set to fullname only while looping through parent groups when adding
383 entry->name = fullname;
384 entry->fullname = fullname;
385 entry->find = findreplace.at(1);
386 entry->replace = findreplace.at(2);
387 entry->controls = findreplace.at(3);
388
389 AddFullNameEntry(entry, item);
390 // done with the temporary entry so remove it
391 delete entry;
392 cnt++;
393 }
394 }
395 }
396 }
397
398
AddFullNameEntry(SearchEditorModel::searchEntry * entry,QStandardItem * parent_item,int row)399 void SearchEditorModel::AddFullNameEntry(SearchEditorModel::searchEntry *entry, QStandardItem *parent_item, int row)
400 {
401 if (!parent_item) {
402 parent_item = invisibleRootItem();
403 }
404
405 if (parent_item != invisibleRootItem() && !parent_item->data(IS_GROUP_ROLE).toBool()) {
406 row = parent_item->row() + 1;
407 parent_item = parent_item->parent();
408 }
409
410 if (row < 0) {
411 row = parent_item->rowCount();
412 }
413
414 QString entry_name = entry->name;
415
416 if (entry->name.contains("/")) {
417 QStringList group_names = entry->name.split("/", QString::SkipEmptyParts);
418 entry_name = group_names.last();
419
420 if (!entry->is_group) {
421 group_names.removeLast();
422 }
423
424 foreach(QString group_name, group_names) {
425 bool found = false;
426
427 for (int r = 0; r < parent_item->rowCount(); r++) {
428 if (parent_item->child(r, 0)->data(IS_GROUP_ROLE).toBool() && parent_item->child(r, 0)->text() == group_name) {
429 parent_item = parent_item->child(r, 0);
430 found = true;
431 break;
432 }
433 }
434
435 if (!found) {
436 SearchEditorModel::searchEntry *new_entry = new SearchEditorModel::searchEntry();
437 new_entry->is_group = true;
438 new_entry->name = group_name;
439 parent_item = AddEntryToModel(new_entry, new_entry->is_group, parent_item, parent_item->rowCount());
440 delete new_entry;
441 }
442 }
443 row = parent_item->rowCount();
444 }
445
446 if (!entry->is_group) {
447 entry->name = entry_name;
448 AddEntryToModel(entry, entry->is_group, parent_item, row);
449 }
450 }
451
452
FillControls(const QList<QStandardItem * > & items)453 void SearchEditorModel::FillControls(const QList<QStandardItem*> &items)
454 {
455 if (items.isEmpty()) return;
456
457 QStandardItem *source_item = items.at(0);
458
459 QStandardItem *parent_item = invisibleRootItem();
460 if (source_item->parent()) {
461 parent_item = source_item->parent();
462 }
463 QString controls = parent_item->child(source_item->row(), 3)->text();
464
465 for (int i = 1; i < items.size(); i++) {
466 QStandardItem* aitem = items.at(i);
467 parent_item = invisibleRootItem();
468 if (aitem->parent()) {
469 parent_item = aitem->parent();
470 }
471 parent_item->child(aitem->row(), 3)->setText(controls);
472 }
473 SetDataModified(true);
474 }
475
476
BuildControlsToolTip(const QString & controls)477 QString SearchEditorModel::BuildControlsToolTip(const QString & controls)
478 {
479 QString tooltip_controls = "";
480 if (controls != "") {
481 if (controls.contains("NL")) {
482 tooltip_controls.append("NL - " + tr("Mode: Normal") + "\n");
483 }
484 if (controls.contains("RX")) {
485 tooltip_controls.append("RX - " + tr("Mode: Regular Expression") + "\n");
486 }
487 if (controls.contains("CS")) {
488 tooltip_controls.append("CS - " + tr("Mode: Case Sensitive") + "\n");
489 }
490 if (controls.contains("UP")) {
491 tooltip_controls.append("UP - " + tr("Direction: Up") + "\n");
492 }
493 if (controls.contains("DN")) {
494 tooltip_controls.append("DN - " + tr("Direction: Down") + "\n");
495 }
496 if (controls.contains("CF")) {
497 tooltip_controls.append("CF - " + tr("Target: Current File") + "\n");
498 }
499 if (controls.contains("AH")) {
500 tooltip_controls.append("AH - " + tr("Target: All HTML Files") + "\n");
501 }
502 if (controls.contains("SH")) {
503 tooltip_controls.append("SH - " + tr("Target: Selected HTML Files") + "\n");
504 }
505 if (controls.contains("TH")) {
506 tooltip_controls.append("TH - " + tr("Target: Tabbed HTML Files") + "\n");
507 }
508 if (controls.contains("AC")) {
509 tooltip_controls.append("AC - " + tr("Target: All CSS Files") + "\n");
510 }
511 if (controls.contains("SC")) {
512 tooltip_controls.append("SC - " + tr("Target: Selected CSS Files") + "\n");
513 }
514 if (controls.contains("TC")) {
515 tooltip_controls.append("TC - " + tr("Target: Tabbed CSS Files") + "\n");
516 }
517 if (controls.contains("OP")) {
518 tooltip_controls.append("OP - " + tr("Target: OPF File") + "\n");
519 }
520 if (controls.contains("NX")) {
521 tooltip_controls.append("NX - " + tr("Target: NCX File") + "\n");
522 }
523 if (controls.contains("DA")) {
524 tooltip_controls.append("DA - " + tr("Option: DotAll") + "\n");
525 }
526 if (controls.contains("MM")) {
527 tooltip_controls.append("MM - " + tr("Option: Minimal Match") + "\n");
528 }
529 if (controls.contains("AT")) {
530 tooltip_controls.append("AT - " + tr("Option: Auto Tokenise") + "\n");
531 }
532 if (controls.contains("WR")) {
533 tooltip_controls.append("WR - " + tr("Option: Wrap") + "\n");
534 }
535 }
536 return tooltip_controls;
537 }
538
AddEntryToModel(SearchEditorModel::searchEntry * entry,bool is_group,QStandardItem * parent_item,int row)539 QStandardItem *SearchEditorModel::AddEntryToModel(SearchEditorModel::searchEntry *entry, bool is_group, QStandardItem *parent_item, int row)
540 {
541 // parent_item must be a group item
542 if (!parent_item) {
543 parent_item = invisibleRootItem();
544 }
545
546 // Create an empty entry if none supplied
547 if (!entry) {
548 entry = new SearchEditorModel::searchEntry();
549 entry->is_group = is_group;
550
551 if (!is_group) {
552 entry->name = "Search";
553 entry->find = "";
554 entry->replace = "";
555 entry->controls = "";
556 } else {
557 entry->name = "Group";
558 }
559 }
560
561 entry->fullname = entry->name;
562
563 if (parent_item != invisibleRootItem()) {
564 // Set the fullname based on the parent entry
565 entry->fullname = parent_item->data(FULLNAME_ROLE).toString() + entry->name;
566 }
567
568 if (entry->is_group) {
569 entry->fullname += "/";
570 }
571
572 QList<QStandardItem *> rowItems;
573 QStandardItem *group_item = parent_item;
574
575 if (entry->is_group) {
576 group_item = new QStandardItem(entry->name);
577 QFont font;
578 font.setWeight(QFont::Bold);
579 group_item->setFont(font);
580 rowItems << group_item;
581
582 for (int col = 1; col < COLUMNS ; col++) {
583 QStandardItem *item = new QStandardItem("");
584 item->setEditable(false);
585 rowItems << item;
586 }
587 } else {
588 rowItems << new QStandardItem(entry->name);
589 rowItems << new QStandardItem(entry->find);
590 rowItems << new QStandardItem(entry->replace);
591 rowItems << new QStandardItem(entry->controls);
592 }
593
594 rowItems[0]->setData(entry->is_group, IS_GROUP_ROLE);
595 rowItems[0]->setData(entry->fullname, FULLNAME_ROLE);
596 rowItems[0]->setToolTip(entry->fullname);
597 rowItems[3]->setToolTip(BuildControlsToolTip(entry->controls));
598
599 // Add the new item to the model at the specified row
600 QStandardItem *new_item;
601
602 if (row < 0 || row >= parent_item->rowCount()) {
603 parent_item->appendRow(rowItems);
604 new_item = parent_item->child(parent_item->rowCount() - 1, 0);
605 } else {
606 parent_item->insertRow(row, rowItems);
607 new_item = parent_item->child(row, 0);
608 }
609
610 SetDataModified(true);
611 return new_item;
612 }
613
AddExampleEntries()614 void SearchEditorModel::AddExampleEntries()
615 {
616 QString examples_dir;
617 #ifdef Q_OS_MAC
618 examples_dir = QCoreApplication::applicationDirPath() + "/../examples/";
619 #elif defined(Q_OS_WIN32)
620 examples_dir = QCoreApplication::applicationDirPath() + "/examples/";
621 #else
622 // all flavours of linux / unix
623 // user supplied environment variable to 'share/sigil' directory will override everything
624 if (!sigil_extra_root.isEmpty()) {
625 examples_dir = sigil_extra_root + "/examples/";
626 } else {
627 examples_dir = sigil_share_root + "/examples/";
628 }
629 #endif
630 LoadData(examples_dir % SEARCH_EXAMPLES_FILE);
631 }
632
GetNonGroupItems(QList<QStandardItem * > items)633 QList<QStandardItem *> SearchEditorModel::GetNonGroupItems(QList<QStandardItem *> items)
634 {
635 QList<QStandardItem *> all_items;
636 foreach(QStandardItem * item, items) {
637 all_items.append(GetNonGroupItems(item));
638 }
639 return all_items;
640 }
641
GetNonGroupItems(QStandardItem * item)642 QList<QStandardItem *> SearchEditorModel::GetNonGroupItems(QStandardItem *item)
643 {
644 QList<QStandardItem *> items;
645
646 if (!item->data(IS_GROUP_ROLE).toBool()) {
647 items.append(item);
648 }
649
650 for (int row = 0; row < item->rowCount(); row++) {
651 items.append(GetNonGroupItems(item->child(row, 0)));
652 }
653
654 return items;
655 }
656
GetNonParentItems(QList<QStandardItem * > items)657 QList<QStandardItem *> SearchEditorModel::GetNonParentItems(QList<QStandardItem *> items)
658 {
659 QList<QStandardItem *> all_items;
660 foreach(QStandardItem * item, items) {
661 all_items.append(GetNonParentItems(item));
662 }
663 return all_items;
664 }
665
GetNonParentItems(QStandardItem * item)666 QList<QStandardItem *> SearchEditorModel::GetNonParentItems(QStandardItem *item)
667 {
668 QList<QStandardItem *> items;
669
670 if (item->rowCount() == 0) {
671 if (item != invisibleRootItem()) {
672 items.append(item);
673 }
674 }
675
676 for (int row = 0; row < item->rowCount(); row++) {
677 items.append(GetNonParentItems(item->child(row, 0)));
678 }
679
680 return items;
681 }
682
GetEntries(QList<QStandardItem * > items)683 QList<SearchEditorModel::searchEntry *> SearchEditorModel::GetEntries(QList<QStandardItem *> items)
684 {
685 QList<SearchEditorModel::searchEntry *> entries;
686 foreach(QStandardItem * item, items) {
687 entries.append(GetEntry(item));
688 }
689 return entries;
690 }
691
GetEntry(QStandardItem * item)692 SearchEditorModel::searchEntry *SearchEditorModel::GetEntry(QStandardItem *item)
693 {
694 QStandardItem *parent_item;
695
696 if (item->parent()) {
697 parent_item = item->parent();
698 } else {
699 parent_item = invisibleRootItem();
700 }
701
702 SearchEditorModel::searchEntry *entry = new SearchEditorModel::searchEntry();
703 entry->is_group = parent_item->child(item->row(), 0)->data(IS_GROUP_ROLE).toBool();
704 entry->fullname = parent_item->child(item->row(), 0)->data(FULLNAME_ROLE).toString();
705 entry->name = parent_item->child(item->row(), 0)->text();
706 entry->find = parent_item->child(item->row(), 1)->text();
707 entry->replace = parent_item->child(item->row(), 2)->text();
708 entry->controls = parent_item->child(item->row(), 3)->text();
709 return entry;
710 }
711
GetItemFromId(quintptr id,int row,QStandardItem * item) const712 QStandardItem *SearchEditorModel::GetItemFromId(quintptr id, int row, QStandardItem *item) const
713 {
714 QStandardItem *found_item = NULL;
715
716 if (!item) {
717 item = invisibleRootItem();
718 }
719
720 if (item->index().internalId() == id) {
721 if (item->parent()) {
722 item = item->parent();
723 } else {
724 item = invisibleRootItem();
725 }
726
727 return item->child(row, 0);
728 }
729
730 for (int r = 0; r < item->rowCount(); r++) {
731 found_item = GetItemFromId((quintptr)id, row, item->child(r, 0));
732
733 // Return with first found entry
734 if (found_item) {
735 return found_item;
736 }
737 }
738
739 return found_item;
740 }
741
742
SaveTextData(QList<SearchEditorModel::searchEntry * > entries,const QString & filename,const QChar & sep)743 QString SearchEditorModel::SaveTextData(QList<SearchEditorModel::searchEntry *> entries,
744 const QString &filename,
745 const QChar &sep)
746 {
747 QString message = "";
748 bool clean_up_needed = false;
749 QString save_path = filename;
750
751 // Save everything if no entries selected
752 if (entries.isEmpty()) {
753 QList<QStandardItem *> items = GetNonParentItems(invisibleRootItem());
754
755 if (!items.isEmpty()) {
756 // GetEntries calls GetEntry which creates each entry with new
757 entries = GetEntries(items);
758 clean_up_needed = true;
759 }
760 }
761
762 // Create Data to write to file
763 QStringList res;
764 foreach(SearchEditorModel::searchEntry * entry, entries) {
765 QStringList data;
766 data << entry->fullname;
767 if (!entry->is_group) {
768 data << entry->find;
769 data << entry->replace;
770 data << entry->controls;
771 }
772 if (sep == ',') {
773 res << Utility::createCSVLine(data);
774 } else {
775 res << data.join(sep);
776 }
777 }
778 QString text = res.join('\n');
779 try {
780 Utility::WriteUnicodeTextFile(text, save_path);
781 } catch (CannotOpenFile& e) {
782 message = QString(e.what());
783 }
784
785 // delete each entry if we created them above
786 if (clean_up_needed) {
787 foreach(SearchEditorModel::searchEntry* entry, entries) {
788 delete entry;
789 }
790 }
791 return message;
792 }
793
794
SaveData(QList<SearchEditorModel::searchEntry * > entries,const QString & filename)795 QString SearchEditorModel::SaveData(QList<SearchEditorModel::searchEntry *> entries, const QString &filename)
796 {
797 QString message = "";
798 bool clean_up_needed = false;
799 QString settings_path = filename;
800 if (settings_path.isEmpty()) settings_path = m_SettingsPath;
801
802 // Save everything if no entries selected
803 if (entries.isEmpty()) {
804 QList<QStandardItem *> items = GetNonParentItems(invisibleRootItem());
805
806 if (!items.isEmpty()) {
807 // GetEntries calls GetEntry which creates each entry with new
808 entries = GetEntries(items);
809 clean_up_needed = true;
810 }
811 }
812
813 // Stop watching the file while we save it
814 if (m_FSWatcher->files().contains(settings_path)) {
815 m_FSWatcher->removePath(settings_path);
816 }
817
818 // Open the default file for save, or specific file for export
819 {
820 SettingsStore ss(settings_path);
821
822 // ss.sync();
823
824 if (!ss.isWritable()) {
825 message = tr("Unable to create file %1").arg(filename);
826 // Watch the file again
827 m_FSWatcher->addPath(settings_path);
828 // delete each entry if we created them above
829 if (clean_up_needed) {
830 foreach(SearchEditorModel::searchEntry* entry, entries) {
831 delete entry;
832 }
833 }
834 return message;
835 }
836
837 // Remove the old values to account for deletions
838 ss.remove(SETTINGS_GROUP);
839 ss.beginWriteArray(SETTINGS_GROUP);
840 int i = 0;
841 foreach(SearchEditorModel::searchEntry * entry, entries) {
842 ss.setArrayIndex(i++);
843 ss.setValue(ENTRY_NAME, entry->fullname);
844
845 if (!entry->is_group) {
846 ss.setValue(ENTRY_FIND, entry->find);
847 ss.setValue(ENTRY_REPLACE, entry->replace);
848 ss.setValue(ENTRY_CONTROLS, entry->controls);
849 }
850 }
851
852 // delete each entry if we created them above
853 if (clean_up_needed) {
854 foreach(SearchEditorModel::searchEntry* entry, entries) {
855 delete entry;
856 }
857 }
858
859 ss.endArray();
860
861 // Make sure file is created/updated so it can be checked
862 ss.sync();
863 }
864
865 // Watch the file again
866 m_FSWatcher->addPath(settings_path);
867 SetDataModified(false);
868 return message;
869 }
870
data(const QModelIndex & index,int role) const871 QVariant SearchEditorModel::data(const QModelIndex &index, int role) const
872 {
873 if (index.isValid() && index.column() > 0 && role == Qt::SizeHintRole) {
874 // Make all rows the same height using the name column to ensure text limited to a single line
875 return data(this->index(0, 0), role).toSize();
876 }
877
878 return QStandardItemModel::data(index, role);
879 }
880