1 /***************************************************************************
2 Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
3 ***************************************************************************/
4
5 /***************************************************************************
6 * *
7 * This program is free software; you can redistribute it and/or *
8 * modify it under the terms of the GNU General Public License as *
9 * published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) version 3 or any later version *
11 * accepted by the membership of KDE e.V. (or its successor approved *
12 * by the membership of KDE e.V.), which shall act as a proxy *
13 * defined in Section 14 of version 3 of the license. *
14 * *
15 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. *
22 * *
23 ***************************************************************************/
24
25 #include "tellico_kernel.h"
26 #include "document.h"
27 #include "collection.h"
28 #include "controller.h"
29 #include "filter.h"
30 #include "filterdialog.h"
31 #include "loandialog.h"
32 #include "commands/collectioncommand.h"
33 #include "commands/fieldcommand.h"
34 #include "commands/filtercommand.h"
35 #include "commands/addentries.h"
36 #include "commands/modifyentries.h"
37 #include "commands/updateentries.h"
38 #include "commands/removeentries.h"
39 #include "commands/removeloans.h"
40 #include "commands/reorderfields.h"
41 #include "commands/renamecollection.h"
42 #include "collectionfactory.h"
43 #include "utils/stringset.h"
44 #include "utils/cursorsaver.h"
45 #include "utils/mergeconflictresolver.h"
46
47 #include <KMessageBox>
48 #include <KLocalizedString>
49
50 #include <QInputDialog>
51 #include <QUndoStack>
52
53 using Tellico::Kernel;
54 Kernel* Kernel::s_self = nullptr;
55
Kernel(QWidget * parent)56 Kernel::Kernel(QWidget* parent) : m_widget(parent)
57 , m_commandHistory(new QUndoStack(parent)) {
58 }
59
~Kernel()60 Kernel::~Kernel() {
61 }
62
URL() const63 QUrl Kernel::URL() const {
64 return Data::Document::self()->URL();
65 }
66
collectionType() const67 int Kernel::collectionType() const {
68 return Data::Document::self()->collection() ?
69 Data::Document::self()->collection()->type() :
70 Data::Collection::Base;
71 }
72
collectionTypeName() const73 QString Kernel::collectionTypeName() const {
74 return CollectionFactory::typeName(collectionType());
75 }
76
sorry(const QString & text_,QWidget * widget_)77 void Kernel::sorry(const QString& text_, QWidget* widget_/* =nullptr */) {
78 if(text_.isEmpty()) {
79 return;
80 }
81 GUI::CursorSaver cs(Qt::ArrowCursor);
82 KMessageBox::sorry(widget_ ? widget_ : m_widget, text_);
83 }
84
beginCommandGroup(const QString & name_)85 void Kernel::beginCommandGroup(const QString& name_) {
86 m_commandHistory->beginMacro(name_);
87 }
88
endCommandGroup()89 void Kernel::endCommandGroup() {
90 m_commandHistory->endMacro();
91 }
92
resetHistory()93 void Kernel::resetHistory() {
94 m_commandHistory->clear();
95 m_commandHistory->setClean();
96 }
97
addField(Tellico::Data::FieldPtr field_)98 bool Kernel::addField(Tellico::Data::FieldPtr field_) {
99 if(!field_) {
100 return false;
101 }
102 doCommand(new Command::FieldCommand(Command::FieldCommand::FieldAdd,
103 Data::Document::self()->collection(),
104 field_));
105 return true;
106 }
107
modifyField(Tellico::Data::FieldPtr field_)108 bool Kernel::modifyField(Tellico::Data::FieldPtr field_) {
109 if(!field_) {
110 return false;
111 }
112 Data::FieldPtr oldField = Data::Document::self()->collection()->fieldByName(field_->name());
113 if(!oldField) {
114 return false;
115 }
116 doCommand(new Command::FieldCommand(Command::FieldCommand::FieldModify,
117 Tellico::Data::Document::self()->collection(),
118 field_,
119 oldField));
120 return true;
121 }
122
removeField(Tellico::Data::FieldPtr field_)123 bool Kernel::removeField(Tellico::Data::FieldPtr field_) {
124 if(!field_) {
125 return false;
126 }
127 doCommand(new Command::FieldCommand(Command::FieldCommand::FieldRemove,
128 Tellico::Data::Document::self()->collection(),
129 field_));
130 return true;
131 }
132
addEntries(Tellico::Data::EntryList entries_,bool checkFields_)133 void Kernel::addEntries(Tellico::Data::EntryList entries_, bool checkFields_) {
134 if(entries_.isEmpty()) {
135 return;
136 }
137
138 QUndoCommand* cmd = new Command::AddEntries(Tellico::Data::Document::self()->collection(), entries_);
139 if(checkFields_) {
140 beginCommandGroup(cmd->text());
141
142 // this is the same as in Command::UpdateEntries::redo()
143 Tellico::Data::CollPtr c = Data::Document::self()->collection();
144 Tellico::Data::FieldList fields = entries_[0]->collection()->fields();
145
146 auto p = Merge::mergeFields(c, fields, entries_);
147 Data::FieldList modifiedFields = p.first;
148 Data::FieldList addedFields = p.second;
149
150 foreach(Tellico::Data::FieldPtr field, modifiedFields) {
151 if(c->hasField(field->name())) {
152 doCommand(new Command::FieldCommand(Command::FieldCommand::FieldModify, c,
153 field, c->fieldByName(field->name())));
154 }
155 }
156
157 foreach(Tellico::Data::FieldPtr field, addedFields) {
158 doCommand(new Command::FieldCommand(Command::FieldCommand::FieldAdd, c, field));
159 }
160 }
161 doCommand(cmd);
162 if(checkFields_) {
163 endCommandGroup();
164 }
165 }
166
modifyEntries(Tellico::Data::EntryList oldEntries_,Tellico::Data::EntryList newEntries_,const QStringList & modifiedFields_)167 void Kernel::modifyEntries(Tellico::Data::EntryList oldEntries_, Tellico::Data::EntryList newEntries_, const QStringList& modifiedFields_) {
168 if(newEntries_.isEmpty()) {
169 return;
170 }
171
172 doCommand(new Command::ModifyEntries(Data::Document::self()->collection(), oldEntries_, newEntries_, modifiedFields_));
173 }
174
updateEntry(Tellico::Data::EntryPtr oldEntry_,Tellico::Data::EntryPtr newEntry_,bool overWrite_)175 void Kernel::updateEntry(Tellico::Data::EntryPtr oldEntry_, Tellico::Data::EntryPtr newEntry_, bool overWrite_) {
176 if(!newEntry_) {
177 return;
178 }
179
180 doCommand(new Command::UpdateEntries(Tellico::Data::Document::self()->collection(), oldEntry_, newEntry_, overWrite_));
181 }
182
removeEntries(Tellico::Data::EntryList entries_)183 void Kernel::removeEntries(Tellico::Data::EntryList entries_) {
184 if(entries_.isEmpty()) {
185 return;
186 }
187
188 doCommand(new Command::RemoveEntries(Tellico::Data::Document::self()->collection(), entries_));
189 }
190
addLoans(Tellico::Data::EntryList entries_)191 bool Kernel::addLoans(Tellico::Data::EntryList entries_) {
192 if(entries_.isEmpty()) {
193 return false;
194 }
195
196 LoanDialog dlg(entries_, m_widget);
197 if(dlg.exec() != QDialog::Accepted) {
198 return false;
199 }
200
201 QUndoCommand* cmd = dlg.createCommand();
202 if(!cmd) {
203 return false;
204 }
205 doCommand(cmd);
206 return true;
207 }
208
modifyLoan(Tellico::Data::LoanPtr loan_)209 bool Kernel::modifyLoan(Tellico::Data::LoanPtr loan_) {
210 if(!loan_) {
211 return false;
212 }
213
214 LoanDialog dlg(loan_, m_widget);
215 if(dlg.exec() != QDialog::Accepted) {
216 return false;
217 }
218
219 QUndoCommand* cmd = dlg.createCommand();
220 if(!cmd) {
221 return false;
222 }
223 doCommand(cmd);
224 return true;
225 }
226
removeLoans(Tellico::Data::LoanList loans_)227 bool Kernel::removeLoans(Tellico::Data::LoanList loans_) {
228 if(loans_.isEmpty()) {
229 return true;
230 }
231
232 doCommand(new Command::RemoveLoans(loans_));
233 return true;
234 }
235
addFilter(Tellico::FilterPtr filter_)236 void Kernel::addFilter(Tellico::FilterPtr filter_) {
237 if(!filter_) {
238 return;
239 }
240
241 doCommand(new Command::FilterCommand(Command::FilterCommand::FilterAdd, filter_));
242 }
243
modifyFilter(Tellico::FilterPtr filter_)244 bool Kernel::modifyFilter(Tellico::FilterPtr filter_) {
245 if(!filter_) {
246 return false;
247 }
248
249 FilterDialog filterDlg(FilterDialog::ModifyFilter, m_widget);
250 // need to create a new filter object
251 FilterPtr newFilter(new Filter(*filter_));
252 filterDlg.setFilter(newFilter);
253 if(filterDlg.exec() != QDialog::Accepted) {
254 return false;
255 }
256
257 newFilter = filterDlg.currentFilter();
258 doCommand(new Command::FilterCommand(Command::FilterCommand::FilterModify, newFilter, filter_));
259 return true;
260 }
261
removeFilter(Tellico::FilterPtr filter_)262 bool Kernel::removeFilter(Tellico::FilterPtr filter_) {
263 if(!filter_) {
264 return false;
265 }
266
267 QString str = i18n("Do you really want to delete this filter?");
268 QString dontAsk = QStringLiteral("DeleteFilter");
269 int ret = KMessageBox::questionYesNo(m_widget, str, i18n("Delete Filter?"),
270 KStandardGuiItem::yes(), KStandardGuiItem::no(), dontAsk);
271 if(ret != KMessageBox::Yes) {
272 return false;
273 }
274
275 doCommand(new Command::FilterCommand(Command::FilterCommand::FilterRemove, filter_));
276 return true;
277 }
278
reorderFields(const Tellico::Data::FieldList & fields_)279 void Kernel::reorderFields(const Tellico::Data::FieldList& fields_) {
280 doCommand(new Command::ReorderFields(Data::Document::self()->collection(),
281 Data::Document::self()->collection()->fields(),
282 fields_));
283 }
284
appendCollection(Tellico::Data::CollPtr coll_)285 void Kernel::appendCollection(Tellico::Data::CollPtr coll_) {
286 doCommand(new Command::CollectionCommand(Command::CollectionCommand::Append,
287 Data::Document::self()->collection(),
288 coll_));
289 }
290
mergeCollection(Tellico::Data::CollPtr coll_)291 void Kernel::mergeCollection(Tellico::Data::CollPtr coll_) {
292 doCommand(new Command::CollectionCommand(Command::CollectionCommand::Merge,
293 Data::Document::self()->collection(),
294 coll_));
295 }
296
replaceCollection(Tellico::Data::CollPtr coll_)297 void Kernel::replaceCollection(Tellico::Data::CollPtr coll_) {
298 doCommand(new Command::CollectionCommand(Command::CollectionCommand::Replace,
299 Data::Document::self()->collection(),
300 coll_));
301 }
302
renameCollection()303 void Kernel::renameCollection() {
304 bool ok;
305 QString newTitle = QInputDialog::getText(m_widget, i18n("Rename Collection"), i18n("New collection name:"),
306 QLineEdit::Normal, Data::Document::self()->collection()->title(), &ok);
307 if(ok) {
308 doCommand(new Command::RenameCollection(Data::Document::self()->collection(), newTitle));
309 }
310 }
311
doCommand(QUndoCommand * command_)312 void Kernel::doCommand(QUndoCommand* command_) {
313 m_commandHistory->push(command_);
314 }
315
askAndMerge(Tellico::Data::EntryPtr entry1_,Tellico::Data::EntryPtr entry2_,Tellico::Data::FieldPtr field_,QString value1_,QString value2_)316 int Kernel::askAndMerge(Tellico::Data::EntryPtr entry1_, Tellico::Data::EntryPtr entry2_, Tellico::Data::FieldPtr field_,
317 QString value1_, QString value2_) {
318 QString title1 = entry1_->field(QStringLiteral("title"));
319 QString title2 = entry2_->field(QStringLiteral("title"));
320 if(title1 == title2) {
321 title1 = i18n("Entry 1");
322 title2 = i18n("Entry 2");
323 }
324 if(value1_.isEmpty()) {
325 value1_ = entry1_->field(field_);
326 }
327 if(value2_.isEmpty()) {
328 value2_ = entry2_->field(field_);
329 }
330 QString text = QLatin1String("<qt>")
331 + i18n("Conflicting values for %1 were found while merging entries.", field_->title())
332 + QString::fromLatin1("<br/><center><table><tr>"
333 "<th>%1</th>"
334 "<th>%2</th></tr>").arg(title1, title2)
335 + QStringLiteral("<tr><td><em>%1</em></td>").arg(value1_)
336 + QStringLiteral("<td><em>%1</em></td></tr></table></center>").arg(value2_)
337 + i18n("Please choose which value to keep.")
338 + QLatin1String("</qt>");
339
340 int ret = KMessageBox::warningYesNoCancel(Kernel::self()->widget(),
341 text,
342 i18n("Merge Entries"),
343 KGuiItem(i18n("Select value from %1", title1)),
344 KGuiItem(i18n("Select value from %1", title2)));
345 switch(ret) {
346 case KMessageBox::Cancel: return Merge::ConflictResolver::CancelMerge;
347 case KMessageBox::Yes: return Merge::ConflictResolver::KeepFirst; // keep original value
348 case KMessageBox::No: return Merge::ConflictResolver::KeepSecond; // use newer value
349 }
350 return Merge::ConflictResolver::CancelMerge;
351 }
352