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 "controller.h"
26 #include "mainwindow.h"
27 #include "groupview.h"
28 #include "detailedlistview.h"
29 #include "entryeditdialog.h"
30 #include "entryview.h"
31 #include "entryiconview.h"
32 #include "entry.h"
33 #include "entrygroup.h"
34 #include "field.h"
35 #include "filter.h"
36 #include "filterdialog.h"
37 #include "tellico_kernel.h"
38 #include "collection.h"
39 #include "document.h"
40 #include "borrower.h"
41 #include "filterview.h"
42 #include "loanview.h"
43 #include "entryupdater.h"
44 #include "entrymerger.h"
45 #include "utils/cursorsaver.h"
46 #include "gui/lineedit.h"
47 #include "gui/tabwidget.h"
48 #include "tellico_debug.h"
49 
50 #include <KLocalizedString>
51 #include <KMessageBox>
52 #include <KActionMenu>
53 
54 #include <QMenu>
55 
56 using Tellico::Controller;
57 
58 Controller* Controller::s_self = nullptr;
59 
Controller(Tellico::MainWindow * parent_)60 Controller::Controller(Tellico::MainWindow* parent_)
61     : QObject(parent_), m_mainWindow(parent_), m_working(false) {
62 }
63 
~Controller()64 Controller::~Controller() {
65 }
66 
addObserver(Tellico::Observer * obs)67 void Controller::addObserver(Tellico::Observer* obs) {
68   m_observers.append(obs);
69 }
70 
removeObserver(Tellico::Observer * obs)71 void Controller::removeObserver(Tellico::Observer* obs) {
72   m_observers.removeAll(obs);
73 }
74 
groupBy() const75 QString Controller::groupBy() const {
76   return m_mainWindow->m_groupView->groupBy();
77 }
78 
expandedGroupBy() const79 QStringList Controller::expandedGroupBy() const {
80   QStringList g;
81   g << groupBy();
82   // special case for pseudo-group
83   if(g[0] == Data::Collection::s_peopleGroupName) {
84     g.clear();
85     Data::FieldList fields = Data::Document::self()->collection()->peopleFields();
86     foreach(Data::FieldPtr field, fields) {
87       g << field->name();
88     }
89   }
90   // special case for no groups
91   if(g[0].isEmpty()) {
92     g.clear();
93   }
94   return g;
95 }
96 
sortTitles() const97 QStringList Controller::sortTitles() const {
98   QStringList list;
99   list << m_mainWindow->m_detailedView->sortColumnTitle1();
100   list << m_mainWindow->m_detailedView->sortColumnTitle2();
101   list << m_mainWindow->m_detailedView->sortColumnTitle3();
102   return list;
103 }
104 
visibleColumns() const105 QStringList Controller::visibleColumns() const {
106   return m_mainWindow->m_detailedView->visibleColumns();
107 }
108 
visibleEntries()109 Tellico::Data::EntryList Controller::visibleEntries() {
110   return m_mainWindow->m_detailedView->visibleEntries();
111 }
112 
slotCollectionAdded(Tellico::Data::CollPtr coll_)113 void Controller::slotCollectionAdded(Tellico::Data::CollPtr coll_) {
114   MARK;
115   // at start-up, this might get called too early, so check and bail
116   if(!coll_ || !m_mainWindow->m_groupView) {
117     return;
118   }
119 
120   // do this first because the group view will need it later
121   m_mainWindow->readCollectionOptions(coll_);
122   m_mainWindow->slotUpdateToolbarIcons();
123   m_mainWindow->updateEntrySources(); // has to be called before all the addCollection()
124   // calls in the widgets since they may want menu updates
125 
126 //  blockAllSignals(true);
127   m_mainWindow->m_detailedView->addCollection(coll_);
128   m_mainWindow->m_groupView->addCollection(coll_);
129   m_mainWindow->m_editDialog->resetLayout(coll_);
130   if(!coll_->filters().isEmpty()) {
131     m_mainWindow->addFilterView();
132     m_mainWindow->m_filterView->addCollection(coll_);
133     m_mainWindow->m_viewTabs->setTabBarHidden(false);
134   }
135   if(!coll_->borrowers().isEmpty()) {
136     m_mainWindow->addLoanView();
137     m_mainWindow->m_loanView->addCollection(coll_);
138     m_mainWindow->m_viewTabs->setTabBarHidden(false);
139   }
140 //  blockAllSignals(false);
141 
142   m_mainWindow->slotStatusMsg(i18n("Ready."));
143 
144   m_selectedEntries.clear();
145   m_mainWindow->slotEntryCount();
146 
147   // there really should be a lot of signals to connect to, but right now, the only one
148   // is used when a field is added on a merge
149   connect(&*coll_, &Data::Collection::mergeAddedField,
150           this, &Controller::slotFieldAdded);
151 
152   emit collectionAdded(coll_->type());
153 
154   updateActions();
155 
156   connect(&*coll_, &Data::Collection::signalGroupsModified,
157           m_mainWindow->m_groupView, &GroupView::slotModifyGroups);
158   connect(&*coll_, &Data::Collection::signalRefreshField,
159           this, &Controller::slotRefreshField);
160 }
161 
slotCollectionModified(Tellico::Data::CollPtr coll_)162 void Controller::slotCollectionModified(Tellico::Data::CollPtr coll_) {
163   // easiest thing is to signal collection deleted, then added?
164   // FIXME: Signals for delete collection and then added are yucky
165   slotCollectionDeleted(coll_);
166   slotCollectionAdded(coll_);
167   // https://bugs.kde.org/show_bug.cgi?id=386549
168   // at some point, I need to revisit the ::setImagesAreAvailable() methodology
169   // there are too many workarounds in the code for that
170   m_mainWindow->m_detailedView->slotRefreshImages();
171 }
172 
slotCollectionDeleted(Tellico::Data::CollPtr coll_)173 void Controller::slotCollectionDeleted(Tellico::Data::CollPtr coll_) {
174   blockAllSignals(true);
175   m_mainWindow->saveCollectionOptions(coll_);
176   m_mainWindow->m_groupView->removeCollection(coll_);
177   if(m_mainWindow->m_filterView) {
178     m_mainWindow->m_filterView->slotReset();
179   }
180   if(m_mainWindow->m_loanView) {
181     m_mainWindow->m_loanView->slotReset();
182   }
183   m_mainWindow->m_detailedView->removeCollection(coll_);
184   m_mainWindow->m_entryView->clear();
185   blockAllSignals(false);
186 
187   // disconnect all signals from the collection
188   // this is needed because the Collection::appendCollection() and mergeCollection()
189   // functions signal collection deleted then added for the same collection
190   coll_->disconnect();
191 }
192 
slotFieldAdded(Tellico::Data::CollPtr coll_,Tellico::Data::FieldPtr field_)193 void Controller::slotFieldAdded(Tellico::Data::CollPtr coll_, Tellico::Data::FieldPtr field_) {
194   addedField(coll_, field_);
195 }
196 
197 // TODO: should be adding entries to models rather than to widget observers
addedEntries(Tellico::Data::EntryList entries_)198 void Controller::addedEntries(Tellico::Data::EntryList entries_) {
199   blockAllSignals(true);
200   foreach(Observer* obs, m_observers) {
201     obs->addEntries(entries_);
202   }
203   m_mainWindow->slotQueueFilter();
204   blockAllSignals(false);
205 }
206 
modifiedEntries(Tellico::Data::EntryList entries_)207 void Controller::modifiedEntries(Tellico::Data::EntryList entries_) {
208   // when a new document is being loaded, loans are added to borrowers, which
209   // end up calling Entry::checkIn() which called Document::saveEntry() which calls here
210   // ignore that
211   if(!m_mainWindow->m_initialized) {
212     return;
213   }
214   blockAllSignals(true);
215   foreach(Observer* obs, m_observers) {
216     obs->modifyEntries(entries_);
217   }
218   m_mainWindow->m_entryView->slotRefresh(); // special case
219   blockAllSignals(false);
220 }
221 
removedEntries(Tellico::Data::EntryList entries_)222 void Controller::removedEntries(Tellico::Data::EntryList entries_) {
223   blockAllSignals(true);
224   foreach(Observer* obs, m_observers) {
225     obs->removeEntries(entries_);
226   }
227   foreach(Data::EntryPtr entry, entries_) {
228     m_selectedEntries.removeAll(entry);
229   }
230   m_mainWindow->slotEntryCount();
231   m_mainWindow->slotQueueFilter();
232   blockAllSignals(false);
233 }
234 
addedField(Tellico::Data::CollPtr coll_,Tellico::Data::FieldPtr field_)235 void Controller::addedField(Tellico::Data::CollPtr coll_, Tellico::Data::FieldPtr field_) {
236   foreach(Observer* obs, m_observers) {
237     obs->addField(coll_, field_);
238   }
239   m_mainWindow->m_entryView->slotRefresh();
240   m_mainWindow->slotUpdateCollectionToolBar(coll_);
241   m_mainWindow->slotQueueFilter();
242 }
243 
removedField(Tellico::Data::CollPtr coll_,Tellico::Data::FieldPtr field_)244 void Controller::removedField(Tellico::Data::CollPtr coll_, Tellico::Data::FieldPtr field_) {
245   foreach(Observer* obs, m_observers) {
246     obs->removeField(coll_, field_);
247   }
248   m_mainWindow->m_entryView->slotRefresh();
249   m_mainWindow->slotUpdateCollectionToolBar(coll_);
250   m_mainWindow->slotQueueFilter();
251 }
252 
modifiedField(Tellico::Data::CollPtr coll_,Tellico::Data::FieldPtr oldField_,Tellico::Data::FieldPtr newField_)253 void Controller::modifiedField(Tellico::Data::CollPtr coll_, Tellico::Data::FieldPtr oldField_, Tellico::Data::FieldPtr newField_) {
254   foreach(Observer* obs, m_observers) {
255     obs->modifyField(coll_, oldField_, newField_);
256   }
257   m_mainWindow->m_entryView->slotRefresh();
258   m_mainWindow->slotUpdateCollectionToolBar(coll_);
259   m_mainWindow->slotQueueFilter();
260 }
261 
reorderedFields(Tellico::Data::CollPtr coll_)262 void Controller::reorderedFields(Tellico::Data::CollPtr coll_) {
263   m_mainWindow->m_editDialog->resetLayout(coll_);
264   m_mainWindow->m_detailedView->reorderFields(coll_->fields());
265   m_mainWindow->slotUpdateCollectionToolBar(coll_);
266   m_mainWindow->m_entryView->slotRefresh();
267 }
268 
slotClearSelection()269 void Controller::slotClearSelection() {
270   if(m_working) {
271     return;
272   }
273 
274   m_working = true;
275   blockAllSignals(true);
276 
277   m_mainWindow->m_detailedView->clearSelection();
278   m_mainWindow->m_iconView->clearSelection();
279   m_mainWindow->m_groupView->clearSelection();
280   if(m_mainWindow->m_filterView) {
281     m_mainWindow->m_filterView->clearSelection();
282   }
283   if(m_mainWindow->m_loanView) {
284     m_mainWindow->m_loanView->clearSelection();
285   }
286 
287   blockAllSignals(false);
288 
289   m_selectedEntries.clear();
290   updateActions();
291   m_mainWindow->slotEntryCount();
292   m_working = false;
293 }
294 
slotUpdateSelection(const Tellico::Data::EntryList & entries_)295 void Controller::slotUpdateSelection(const Tellico::Data::EntryList& entries_) {
296   if(m_working) {
297     return;
298   }
299   m_working = true;
300 
301   m_selectedEntries = entries_;
302   updateActions();
303   m_mainWindow->slotEntryCount();
304   m_working = false;
305 }
306 
slotUpdateSelectedEntries(const QString & source_)307 void Controller::slotUpdateSelectedEntries(const QString& source_) {
308   if(m_selectedEntries.isEmpty()) {
309     return;
310   }
311 
312   // it deletes itself when done
313   // signal mapper strings can't be empty, "_all" is set in mainwindow
314   if(source_.isEmpty() || source_ == QLatin1String("_all")) {
315     new EntryUpdater(m_selectedEntries.front()->collection(), m_selectedEntries, this);
316   } else {
317     new EntryUpdater(source_, m_selectedEntries.front()->collection(), m_selectedEntries, this);
318   }
319 }
320 
slotDeleteSelectedEntries()321 void Controller::slotDeleteSelectedEntries() {
322   if(m_selectedEntries.isEmpty()) {
323     return;
324   }
325 
326   m_working = true;
327 
328   // confirm delete
329   if(m_selectedEntries.count() == 1) {
330     QString str = i18n("Do you really want to delete this entry?");
331     QString dontAsk = QStringLiteral("DeleteEntry");
332     int ret = KMessageBox::warningContinueCancel(Kernel::self()->widget(), str, i18n("Delete Entry"),
333                                                  KStandardGuiItem::del(),
334                                                  KStandardGuiItem::cancel(), dontAsk);
335     if(ret != KMessageBox::Continue) {
336       m_working = false;
337       return;
338     }
339   } else {
340     QStringList names;
341     foreach(Data::EntryPtr entry, m_selectedEntries) {
342       names += entry->title();
343     }
344     QString str = i18n("Do you really want to delete these entries?");
345     // historically called DeleteMultipleBooks, don't change
346     QString dontAsk = QStringLiteral("DeleteMultipleBooks");
347     int ret = KMessageBox::warningContinueCancelList(Kernel::self()->widget(), str, names,
348                                                      i18n("Delete Multiple Entries"),
349                                                      KStandardGuiItem::del(),
350                                                      KStandardGuiItem::cancel(), dontAsk);
351     if(ret != KMessageBox::Continue) {
352       m_working = false;
353       return;
354     }
355   }
356 
357   GUI::CursorSaver cs;
358   Kernel::self()->removeEntries(m_selectedEntries);
359   updateActions();
360 
361   m_working = false;
362 
363   // special case, the detailed list view selects the next item, so handle that
364 //  Data::EntryList newList;
365 //  for(GUI::ListViewItemListIt it(m_mainWindow->m_detailedView->selectedItems()); it.current(); ++it) {
366 //    newList.append(static_cast<EntryItem*>(it.current())->entry());
367 //  }
368 //  slotUpdateSelection(m_mainWindow->m_detailedView, newList);
369   slotClearSelection();
370 }
371 
slotMergeSelectedEntries()372 void Controller::slotMergeSelectedEntries() {
373   // merge requires at least 2 entries
374   if(m_selectedEntries.count() < 2) {
375     return;
376   }
377 
378   new EntryMerger(m_selectedEntries, this);
379 }
380 
slotRefreshField(Tellico::Data::FieldPtr field_)381 void Controller::slotRefreshField(Tellico::Data::FieldPtr field_) {
382 //  DEBUG_LINE;
383   // group view only needs to refresh if it's the title
384   if(field_->name() == QLatin1String("title")) {
385     m_mainWindow->m_groupView->populateCollection();
386   }
387   m_mainWindow->m_detailedView->slotRefresh();
388   m_mainWindow->m_entryView->slotRefresh();
389 }
390 
slotCopySelectedEntries()391 void Controller::slotCopySelectedEntries() {
392   if(m_selectedEntries.isEmpty()) {
393     return;
394   }
395 
396   // keep copy of selected entries
397   Data::EntryList old = m_selectedEntries;
398 
399   GUI::CursorSaver cs;
400   // need to create copies
401   Data::EntryList entries;
402   foreach(Data::EntryPtr it, m_selectedEntries) {
403     entries.append(Data::EntryPtr(new Data::Entry(*it)));
404   }
405   Kernel::self()->addEntries(entries, false);
406   slotUpdateSelection(old);
407 }
408 
blockAllSignals(bool block_) const409 void Controller::blockAllSignals(bool block_) const {
410 // sanity check
411   if(!m_mainWindow->m_initialized) {
412     return;
413   }
414   m_mainWindow->m_detailedView->blockSignals(block_);
415   m_mainWindow->m_groupView->blockSignals(block_);
416   m_mainWindow->m_quickFilter->blockSignals(block_);
417   if(m_mainWindow->m_loanView) {
418     m_mainWindow->m_loanView->blockSignals(block_);
419   }
420   if(m_mainWindow->m_filterView) {
421     m_mainWindow->m_filterView->blockSignals(block_);
422   }
423   m_mainWindow->m_editDialog->blockSignals(block_);
424   m_mainWindow->m_iconView->blockSignals(block_);
425 }
426 
slotUpdateFilter(Tellico::FilterPtr filter_)427 void Controller::slotUpdateFilter(Tellico::FilterPtr filter_) {
428   blockAllSignals(true);
429 
430   // the view takes over ownership of the filter
431   if(filter_ && !filter_->isEmpty()) {
432     // clear the icon view selection only
433     // the detailed view takes care of itself
434     m_mainWindow->m_iconView->clearSelection();
435     m_selectedEntries.clear();
436   }
437   updateActions();
438 
439   m_mainWindow->m_detailedView->setFilter(filter_); // takes ownership
440   if(!filter_ && m_mainWindow->m_filterView && !m_mainWindow->m_dontQueueFilter) {
441     // for example, when quick filter clears the selection
442     // the check against m_dontQueueFilter is to prevent the situation when the FilterView has an Entry selected
443     // which sends an empty filter selection, which would then clear the whole FilterView selection
444     m_mainWindow->m_filterView->clearSelection();
445   }
446 
447   blockAllSignals(false);
448 
449   m_mainWindow->slotEntryCount();
450 }
451 
clearFilter()452 void Controller::clearFilter() {
453   blockAllSignals(true);
454   m_mainWindow->m_quickFilter->clear();
455   m_mainWindow->m_detailedView->setFilter(Tellico::FilterPtr());
456   blockAllSignals(false);
457 }
458 
editEntry(Tellico::Data::EntryPtr entry_) const459 void Controller::editEntry(Tellico::Data::EntryPtr entry_) const {
460   m_mainWindow->slotShowEntryEditor();
461   m_mainWindow->m_editDialog->setContents(Data::EntryList() << entry_);
462 }
463 
plugCollectionActions(QMenu * popup_)464 void Controller::plugCollectionActions(QMenu* popup_) {
465   if(!popup_) {
466     return;
467   }
468 
469   popup_->addAction(m_mainWindow->action("coll_rename_collection"));
470   popup_->addAction(m_mainWindow->action("coll_fields"));
471   popup_->addAction(m_mainWindow->action("change_entry_grouping"));
472 }
473 
plugEntryActions(QMenu * popup_)474 void Controller::plugEntryActions(QMenu* popup_) {
475   if(!popup_) {
476     return;
477   }
478 
479 //  m_mainWindow->m_newEntry->plug(popup_);
480   popup_->addAction(m_mainWindow->m_editEntry);
481   popup_->addAction(m_mainWindow->m_copyEntry);
482   popup_->addAction(m_mainWindow->m_deleteEntry);
483   popup_->addAction(m_mainWindow->m_mergeEntry);
484   popup_->addMenu(m_mainWindow->m_updateEntryMenu->menu());
485   // there's a bug in KActionMenu with KXMLGUIFactory::plugActionList
486   // pluging the menu isn't enough to have the popup get populated
487   plugUpdateMenu(popup_);
488   popup_->addSeparator();
489   popup_->addAction(m_mainWindow->m_checkOutEntry);
490 }
491 
plugSortActions(QMenu * popup_)492 QMenu* Controller::plugSortActions(QMenu* popup_) {
493   if(!popup_) {
494     return nullptr;
495   }
496 
497   QMenu* sortMenu = popup_->addMenu(i18n("&Sort By"));
498   sortMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-sort"),
499                                      QIcon::fromTheme(QStringLiteral("view-sort-ascending"))));
500   foreach(Data::FieldPtr field, Data::Document::self()->collection()->fields()) {
501     // not allowed to sort by Image, Table, Para, or URL
502     if(field->type() == Data::Field::Image ||
503        field->type() == Data::Field::Table ||
504        field->type() == Data::Field::URL ||
505        field->type() == Data::Field::Para) {
506       continue;
507     }
508     sortMenu->addAction(field->title())->setData(QVariant::fromValue(field));
509   }
510   return sortMenu;
511 }
512 
plugUpdateMenu(QMenu * popup_)513 void Controller::plugUpdateMenu(QMenu* popup_) {
514   QMenu* updatePopup = nullptr;
515   foreach(QAction* action, popup_->actions()) {
516     if(action && action->text() == m_mainWindow->m_updateEntryMenu->text()) {
517       updatePopup = action->menu();
518       break;
519     }
520   }
521 
522   if(!updatePopup) {
523     return;
524   }
525 
526   // I can't figure out why the actions get duplicated, but they do
527   // so clear them all
528   updatePopup->removeAction(m_mainWindow->m_updateAll);
529   foreach(QAction* action, m_mainWindow->m_fetchActions) {
530     updatePopup->removeAction(action);
531   }
532 
533   // clear separator, too
534   updatePopup->clear();
535 
536   updatePopup->addAction(m_mainWindow->m_updateAll);
537   updatePopup->addSeparator();
538   foreach(QAction* action, m_mainWindow->m_fetchActions) {
539     updatePopup->addAction(action);
540   }
541 }
542 
updateActions() const543 void Controller::updateActions() const {
544   const bool emptySelection = m_selectedEntries.isEmpty();
545   m_mainWindow->stateChanged(QStringLiteral("empty_selection"),
546                              emptySelection ? KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse);
547   foreach(QAction* action, m_mainWindow->m_fetchActions) {
548     action->setEnabled(!emptySelection);
549   }
550   //only enable citation items when it's a bibliography
551   const bool isBibtex = Kernel::self()->collectionType() == Data::Collection::Bibtex;
552   if(isBibtex) {
553     m_mainWindow->action("cite_clipboard")->setEnabled(!emptySelection);
554     m_mainWindow->action("cite_lyxpipe")->setEnabled(!emptySelection);
555   }
556   m_mainWindow->m_checkInEntry->setEnabled(canCheckIn());
557 
558   if(m_selectedEntries.count() < 2) {
559     m_mainWindow->m_editEntry->setText(i18n("&Edit Entry..."));
560     m_mainWindow->m_copyEntry->setText(i18n("D&uplicate Entry"));
561     m_mainWindow->m_updateEntryMenu->setText(i18n("&Update Entry"));
562     m_mainWindow->m_deleteEntry->setText(i18n("&Delete Entry"));
563     m_mainWindow->m_mergeEntry->setEnabled(false);
564   } else {
565     m_mainWindow->m_editEntry->setText(i18n("&Edit Entries..."));
566     m_mainWindow->m_copyEntry->setText(i18n("D&uplicate Entries"));
567     m_mainWindow->m_updateEntryMenu->setText(i18n("&Update Entries"));
568     m_mainWindow->m_deleteEntry->setText(i18n("&Delete Entries"));
569     m_mainWindow->m_mergeEntry->setEnabled(true);
570   }
571 }
572 
addedBorrower(Tellico::Data::BorrowerPtr borrower_)573 void Controller::addedBorrower(Tellico::Data::BorrowerPtr borrower_) {
574   m_mainWindow->addLoanView(); // just in case
575   foreach(Observer* obs, m_observers) {
576     obs->addBorrower(borrower_);
577   }
578   m_mainWindow->m_viewTabs->setTabBarHidden(false);
579 }
580 
modifiedBorrower(Tellico::Data::BorrowerPtr borrower_)581 void Controller::modifiedBorrower(Tellico::Data::BorrowerPtr borrower_) {
582   foreach(Observer* obs, m_observers) {
583     if(borrower_->isEmpty()) {
584       obs->removeBorrower(borrower_);
585     } else {
586       obs->modifyBorrower(borrower_);
587     }
588   }
589   hideTabs();
590 }
591 
addedFilter(Tellico::FilterPtr filter_)592 void Controller::addedFilter(Tellico::FilterPtr filter_) {
593   m_mainWindow->addFilterView(); // just in case
594   foreach(Observer* obs, m_observers) {
595     obs->addFilter(filter_);
596   }
597   m_mainWindow->m_viewTabs->setTabBarHidden(false);
598 }
599 
removedFilter(Tellico::FilterPtr filter_)600 void Controller::removedFilter(Tellico::FilterPtr filter_) {
601   foreach(Observer* obs, m_observers) {
602     obs->removeFilter(filter_);
603   }
604   hideTabs();
605 }
606 
slotCheckOut()607 void Controller::slotCheckOut() {
608   if(m_selectedEntries.isEmpty()) {
609     return;
610   }
611 
612   Data::EntryList loanedEntries = m_selectedEntries;
613 
614   // check to see if any of the entries are already on-loan, and warn user
615   QMap<QString, Data::EntryPtr> alreadyLoaned;
616   foreach(Data::BorrowerPtr borrower, Data::Document::self()->collection()->borrowers()) {
617     foreach(Data::LoanPtr loan, borrower->loans()) {
618       if(m_selectedEntries.contains(loan->entry())) {
619         alreadyLoaned.insert(loan->entry()->title(), loan->entry());
620       }
621     }
622   }
623   if(!alreadyLoaned.isEmpty()) {
624     KMessageBox::informationList(Kernel::self()->widget(),
625                                  i18n("The following items are already loaned, but Tellico "
626                                       "does not currently support lending an item multiple "
627                                       "times. They will be removed from the list of items "
628                                       "to lend."),
629                                       alreadyLoaned.keys());
630     QMap<QString, Data::EntryPtr>::const_iterator it = alreadyLoaned.constBegin();
631     QMap<QString, Data::EntryPtr>::const_iterator end = alreadyLoaned.constEnd();
632     for( ; it != end; ++it) {
633       loanedEntries.removeAll(it.value());
634     }
635     if(loanedEntries.isEmpty()) {
636       return;
637     }
638   }
639 
640   if(Kernel::self()->addLoans(loanedEntries)) {
641     m_mainWindow->m_checkInEntry->setEnabled(true);
642   }
643 }
644 
slotCheckIn()645 void Controller::slotCheckIn() {
646   slotCheckIn(m_selectedEntries);
647 }
648 
slotCheckIn(const Tellico::Data::EntryList & entries_)649 void Controller::slotCheckIn(const Tellico::Data::EntryList& entries_) {
650   if(entries_.isEmpty()) {
651     return;
652   }
653 
654   Data::LoanList loans;
655   foreach(Data::EntryPtr entry, entries_) {
656     // these have to be in the loop since if a borrower gets empty
657     // it will be deleted, so the vector could change, for every entry iterator
658     Data::BorrowerList vec = Data::Document::self()->collection()->borrowers();
659     foreach(Data::BorrowerPtr borrower, vec) {
660       Data::LoanPtr l = borrower->loan(entry);
661       if(l) {
662         loans.append(l);
663         // assume it's only loaned once
664         break;
665       }
666     }
667   }
668 
669   if(Kernel::self()->removeLoans(loans)) {
670     m_mainWindow->m_checkInEntry->setEnabled(false);
671   }
672   hideTabs();
673 }
674 
hideTabs() const675 void Controller::hideTabs() const {
676   if((!m_mainWindow->m_filterView || m_mainWindow->m_filterView->isEmpty()) &&
677      (!m_mainWindow->m_loanView || m_mainWindow->m_loanView->isEmpty())) {
678     int idx = m_mainWindow->m_viewTabs->indexOf(m_mainWindow->m_groupView);
679     m_mainWindow->m_viewTabs->setCurrentIndex(idx);
680     m_mainWindow->m_viewTabs->setTabBarHidden(true);
681   }
682 }
683 
canCheckIn() const684 bool Controller::canCheckIn() const {
685   foreach(Data::EntryPtr entry, m_selectedEntries) {
686     if(entry->field(QStringLiteral("loaned")) == QLatin1String("true")) {
687       return true;
688     }
689   }
690   return false;
691 }
692 
updatedFetchers()693 void Controller::updatedFetchers() {
694   m_mainWindow->updateEntrySources();
695 }
696