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