File indexing completed on 2024-04-28 05:08:16

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "controller.h"
0026 #include "mainwindow.h"
0027 #include "groupview.h"
0028 #include "detailedlistview.h"
0029 #include "entryeditdialog.h"
0030 #include "entryview.h"
0031 #include "entryiconview.h"
0032 #include "entry.h"
0033 #include "entrygroup.h"
0034 #include "field.h"
0035 #include "filter.h"
0036 #include "tellico_kernel.h"
0037 #include "collection.h"
0038 #include "document.h"
0039 #include "borrower.h"
0040 #include "filterview.h"
0041 #include "loanview.h"
0042 #include "entryupdater.h"
0043 #include "entrymerger.h"
0044 #include "utils/cursorsaver.h"
0045 #include "gui/lineedit.h"
0046 #include "gui/tabwidget.h"
0047 #include "tellico_debug.h"
0048 
0049 #include <KLocalizedString>
0050 #include <KMessageBox>
0051 #include <KActionMenu>
0052 
0053 #include <QMenu>
0054 
0055 using Tellico::Controller;
0056 
0057 Controller* Controller::s_self = nullptr;
0058 
0059 Controller::Controller(Tellico::MainWindow* parent_)
0060     : QObject(parent_), m_mainWindow(parent_), m_working(false) {
0061 }
0062 
0063 Controller::~Controller() {
0064 }
0065 
0066 void Controller::addObserver(Tellico::Observer* obs) {
0067   m_observers.append(obs);
0068 }
0069 
0070 void Controller::removeObserver(Tellico::Observer* obs) {
0071   m_observers.removeAll(obs);
0072 }
0073 
0074 QString Controller::groupBy() const {
0075   return m_mainWindow->m_groupView->groupBy();
0076 }
0077 
0078 QStringList Controller::expandedGroupBy() const {
0079   QStringList g;
0080   g << groupBy();
0081   // special case for pseudo-group
0082   if(g[0] == Data::Collection::s_peopleGroupName) {
0083     g.clear();
0084     Data::FieldList fields = Data::Document::self()->collection()->peopleFields();
0085     foreach(Data::FieldPtr field, fields) {
0086       g << field->name();
0087     }
0088   }
0089   // special case for no groups
0090   if(g[0].isEmpty()) {
0091     g.clear();
0092   }
0093   return g;
0094 }
0095 
0096 QStringList Controller::sortTitles() const {
0097   QStringList list;
0098   list << m_mainWindow->m_detailedView->sortColumnTitle1();
0099   list << m_mainWindow->m_detailedView->sortColumnTitle2();
0100   list << m_mainWindow->m_detailedView->sortColumnTitle3();
0101   return list;
0102 }
0103 
0104 QStringList Controller::visibleColumns() const {
0105   return m_mainWindow->m_detailedView->visibleColumns();
0106 }
0107 
0108 Tellico::Data::EntryList Controller::visibleEntries() {
0109   return m_mainWindow->m_detailedView->visibleEntries();
0110 }
0111 
0112 void Controller::slotCollectionAdded(Tellico::Data::CollPtr coll_) {
0113   MARK;
0114   // at start-up, this might get called too early, so check and bail
0115   if(!coll_ || !m_mainWindow->m_groupView) {
0116     return;
0117   }
0118 
0119   // do this first because the group view will need it later
0120   m_mainWindow->readCollectionOptions(coll_);
0121   m_mainWindow->slotUpdateToolbarIcons();
0122   m_mainWindow->updateEntrySources(); // has to be called before all the addCollection()
0123   // calls in the widgets since they may want menu updates
0124 
0125   m_mainWindow->m_detailedView->addCollection(coll_);
0126   m_mainWindow->m_groupView->addCollection(coll_);
0127   m_mainWindow->m_editDialog->resetLayout(coll_);
0128   if(!coll_->filters().isEmpty()) {
0129     m_mainWindow->addFilterView();
0130     m_mainWindow->m_filterView->addCollection(coll_);
0131     m_mainWindow->m_viewTabs->setTabBarHidden(false);
0132   }
0133   if(!coll_->borrowers().isEmpty()) {
0134     m_mainWindow->addLoanView();
0135     m_mainWindow->m_loanView->addCollection(coll_);
0136     m_mainWindow->m_viewTabs->setTabBarHidden(false);
0137   }
0138 
0139   m_mainWindow->slotStatusMsg(i18n("Ready."));
0140 
0141   m_selectedEntries.clear();
0142   m_mainWindow->slotEntryCount();
0143 
0144   // there really should be a lot of signals to connect to, but right now, the only one
0145   // is used when a field is added on a merge
0146   connect(&*coll_, &Data::Collection::mergeAddedField,
0147           this, &Controller::slotFieldAdded);
0148 
0149   emit collectionAdded(coll_->type());
0150 
0151   updateActions();
0152 
0153   connect(&*coll_, &Data::Collection::signalGroupsModified,
0154           m_mainWindow->m_groupView, &GroupView::slotModifyGroups);
0155   connect(&*coll_, &Data::Collection::signalRefreshField,
0156           this, &Controller::slotRefreshField);
0157 }
0158 
0159 void Controller::slotCollectionModified(Tellico::Data::CollPtr coll_, bool structuralChange_) {
0160   Data::EntryList prevSelection = m_selectedEntries;
0161   blockAllSignals(true);
0162   m_mainWindow->m_groupView->removeCollection(coll_);
0163   if(m_mainWindow->m_filterView) {
0164     m_mainWindow->m_filterView->slotReset();
0165   }
0166   if(m_mainWindow->m_loanView) {
0167     m_mainWindow->m_loanView->slotReset();
0168   }
0169   // TODO: removing and adding the collection in the detailed view is overkill
0170   // find a more elegant way to refresh the view
0171   m_mainWindow->m_detailedView->removeCollection(coll_);
0172   blockAllSignals(false);
0173 
0174   if(structuralChange_) {
0175     m_mainWindow->m_editDialog->resetLayout(coll_);
0176   }
0177   // the selected entries list gets cleared when the detailed list view removes the collection
0178   m_selectedEntries = prevSelection;
0179   m_mainWindow->m_editDialog->setContents(m_selectedEntries);
0180   m_mainWindow->m_detailedView->addCollection(coll_);
0181   m_mainWindow->m_groupView->addCollection(coll_);
0182   if(!coll_->filters().isEmpty()) {
0183     m_mainWindow->addFilterView();
0184     m_mainWindow->m_filterView->addCollection(coll_);
0185     m_mainWindow->m_viewTabs->setTabBarHidden(false);
0186   }
0187   if(!coll_->borrowers().isEmpty()) {
0188     m_mainWindow->addLoanView();
0189     m_mainWindow->m_loanView->addCollection(coll_);
0190     m_mainWindow->m_viewTabs->setTabBarHidden(false);
0191   }
0192 
0193   m_mainWindow->slotStatusMsg(i18n("Ready."));
0194   m_mainWindow->slotEntryCount();
0195 
0196   // https://bugs.kde.org/show_bug.cgi?id=386549
0197   // at some point, I need to revisit the ::setImagesAreAvailable() methodology
0198   // there are too many workarounds in the code for that
0199   m_mainWindow->m_detailedView->slotRefreshImages();
0200 }
0201 
0202 void Controller::slotCollectionDeleted(Tellico::Data::CollPtr coll_) {
0203   blockAllSignals(true);
0204   m_mainWindow->saveCollectionOptions(coll_);
0205   m_mainWindow->m_groupView->removeCollection(coll_);
0206   if(m_mainWindow->m_filterView) {
0207     m_mainWindow->m_filterView->slotReset();
0208   }
0209   if(m_mainWindow->m_loanView) {
0210     m_mainWindow->m_loanView->slotReset();
0211   }
0212   m_mainWindow->m_detailedView->removeCollection(coll_);
0213   m_mainWindow->m_entryView->clear();
0214   blockAllSignals(false);
0215 
0216   // disconnect all signals from the collection
0217   // this is needed because the Collection::appendCollection() and mergeCollection()
0218   // functions signal collection deleted then added for the same collection
0219   coll_->disconnect();
0220 }
0221 
0222 void Controller::slotFieldAdded(Tellico::Data::CollPtr coll_, Tellico::Data::FieldPtr field_) {
0223   addedField(coll_, field_);
0224 }
0225 
0226 // TODO: should be adding entries to models rather than to widget observers
0227 void Controller::addedEntries(Tellico::Data::EntryList entries_) {
0228   blockAllSignals(true);
0229   foreach(Observer* obs, m_observers) {
0230     obs->addEntries(entries_);
0231   }
0232   m_mainWindow->slotQueueFilter();
0233   blockAllSignals(false);
0234 }
0235 
0236 void Controller::modifiedEntries(Tellico::Data::EntryList entries_) {
0237   // when a new document is being loaded, loans are added to borrowers, which
0238   // end up calling Entry::checkIn() which called Document::saveEntry() which calls here
0239   // ignore that
0240   if(!m_mainWindow->m_initialized) {
0241     return;
0242   }
0243   blockAllSignals(true);
0244   foreach(Observer* obs, m_observers) {
0245     obs->modifyEntries(entries_);
0246   }
0247   m_mainWindow->m_entryView->slotRefresh(); // special case
0248   blockAllSignals(false);
0249 }
0250 
0251 void Controller::removedEntries(Tellico::Data::EntryList entries_) {
0252   blockAllSignals(true);
0253   foreach(Observer* obs, m_observers) {
0254     obs->removeEntries(entries_);
0255   }
0256   foreach(Data::EntryPtr entry, entries_) {
0257     m_selectedEntries.removeAll(entry);
0258   }
0259   m_mainWindow->slotEntryCount();
0260   m_mainWindow->slotQueueFilter();
0261   blockAllSignals(false);
0262 }
0263 
0264 void Controller::addedField(Tellico::Data::CollPtr coll_, Tellico::Data::FieldPtr field_) {
0265   foreach(Observer* obs, m_observers) {
0266     obs->addField(coll_, field_);
0267   }
0268   m_mainWindow->m_entryView->slotRefresh();
0269   m_mainWindow->slotUpdateCollectionToolBar(coll_);
0270   m_mainWindow->slotQueueFilter();
0271 }
0272 
0273 void Controller::removedField(Tellico::Data::CollPtr coll_, Tellico::Data::FieldPtr field_) {
0274   foreach(Observer* obs, m_observers) {
0275     obs->removeField(coll_, field_);
0276   }
0277   m_mainWindow->m_entryView->slotRefresh();
0278   m_mainWindow->slotUpdateCollectionToolBar(coll_);
0279   m_mainWindow->slotQueueFilter();
0280 }
0281 
0282 void Controller::modifiedField(Tellico::Data::CollPtr coll_, Tellico::Data::FieldPtr oldField_, Tellico::Data::FieldPtr newField_) {
0283   foreach(Observer* obs, m_observers) {
0284     obs->modifyField(coll_, oldField_, newField_);
0285   }
0286   m_mainWindow->m_entryView->slotRefresh();
0287   m_mainWindow->slotUpdateCollectionToolBar(coll_);
0288   m_mainWindow->slotQueueFilter();
0289 }
0290 
0291 void Controller::reorderedFields(Tellico::Data::CollPtr coll_) {
0292   m_mainWindow->m_editDialog->resetLayout(coll_);
0293   m_mainWindow->m_detailedView->reorderFields(coll_->fields());
0294   m_mainWindow->slotUpdateCollectionToolBar(coll_);
0295   m_mainWindow->m_entryView->slotRefresh();
0296 }
0297 
0298 void Controller::slotClearSelection() {
0299   if(m_working) {
0300     return;
0301   }
0302 
0303   m_working = true;
0304   blockAllSignals(true);
0305 
0306   m_mainWindow->m_detailedView->clearSelection();
0307   m_mainWindow->m_iconView->clearSelection();
0308   m_mainWindow->m_groupView->clearSelection();
0309   if(m_mainWindow->m_filterView) {
0310     m_mainWindow->m_filterView->clearSelection();
0311   }
0312   if(m_mainWindow->m_loanView) {
0313     m_mainWindow->m_loanView->clearSelection();
0314   }
0315 
0316   blockAllSignals(false);
0317 
0318   m_selectedEntries.clear();
0319   updateActions();
0320   m_mainWindow->slotEntryCount();
0321   m_working = false;
0322 }
0323 
0324 void Controller::slotUpdateSelection(const Tellico::Data::EntryList& entries_) {
0325   if(m_working) {
0326     return;
0327   }
0328   m_working = true;
0329 
0330   m_selectedEntries = entries_;
0331   updateActions();
0332   m_mainWindow->slotEntryCount();
0333   m_working = false;
0334 }
0335 
0336 void Controller::slotUpdateSelectedEntries(const QString& source_) {
0337   if(m_selectedEntries.isEmpty()) {
0338     return;
0339   }
0340 
0341   // it deletes itself when done
0342   // signal mapper strings can't be empty, "_all" is set in mainwindow
0343   if(source_.isEmpty() || source_ == QLatin1String("_all")) {
0344     new EntryUpdater(m_selectedEntries.front()->collection(), m_selectedEntries, this);
0345   } else {
0346     new EntryUpdater(source_, m_selectedEntries.front()->collection(), m_selectedEntries, this);
0347   }
0348 }
0349 
0350 void Controller::slotDeleteSelectedEntries() {
0351   if(m_selectedEntries.isEmpty()) {
0352     return;
0353   }
0354 
0355   m_working = true;
0356 
0357   // confirm delete
0358   if(m_selectedEntries.count() == 1) {
0359     QString str = i18n("Do you really want to delete this entry?");
0360     QString dontAsk = QStringLiteral("DeleteEntry");
0361     int ret = KMessageBox::warningContinueCancel(Kernel::self()->widget(), str, i18n("Delete Entry"),
0362                                                  KStandardGuiItem::del(),
0363                                                  KStandardGuiItem::cancel(), dontAsk);
0364     if(ret != KMessageBox::Continue) {
0365       m_working = false;
0366       return;
0367     }
0368   } else {
0369     QStringList names;
0370     foreach(Data::EntryPtr entry, m_selectedEntries) {
0371       names += entry->title();
0372     }
0373     QString str = i18n("Do you really want to delete these entries?");
0374     // historically called DeleteMultipleBooks, don't change
0375     QString dontAsk = QStringLiteral("DeleteMultipleBooks");
0376     int ret = KMessageBox::warningContinueCancelList(Kernel::self()->widget(), str, names,
0377                                                      i18n("Delete Multiple Entries"),
0378                                                      KStandardGuiItem::del(),
0379                                                      KStandardGuiItem::cancel(), dontAsk);
0380     if(ret != KMessageBox::Continue) {
0381       m_working = false;
0382       return;
0383     }
0384   }
0385 
0386   GUI::CursorSaver cs;
0387   Kernel::self()->removeEntries(m_selectedEntries);
0388   updateActions();
0389 
0390   m_working = false;
0391 
0392   // special case, the detailed list view selects the next item, so handle that
0393 //  Data::EntryList newList;
0394 //  for(GUI::ListViewItemListIt it(m_mainWindow->m_detailedView->selectedItems()); it.current(); ++it) {
0395 //    newList.append(static_cast<EntryItem*>(it.current())->entry());
0396 //  }
0397 //  slotUpdateSelection(m_mainWindow->m_detailedView, newList);
0398   slotClearSelection();
0399 }
0400 
0401 void Controller::slotMergeSelectedEntries() {
0402   // merge requires at least 2 entries
0403   if(m_selectedEntries.count() < 2) {
0404     return;
0405   }
0406 
0407   new EntryMerger(m_selectedEntries, this);
0408 }
0409 
0410 void Controller::slotRefreshField(Tellico::Data::FieldPtr field_) {
0411 //  DEBUG_LINE;
0412   // group view only needs to refresh if it's the title
0413   if(field_->name() == QLatin1String("title")) {
0414     m_mainWindow->m_groupView->populateCollection();
0415   }
0416   m_mainWindow->m_detailedView->slotRefresh();
0417   m_mainWindow->m_entryView->slotRefresh();
0418 }
0419 
0420 void Controller::slotCopySelectedEntries() {
0421   if(m_selectedEntries.isEmpty()) {
0422     return;
0423   }
0424 
0425   // keep copy of selected entries
0426   Data::EntryList old = m_selectedEntries;
0427 
0428   GUI::CursorSaver cs;
0429   // need to create copies
0430   Data::EntryList entries;
0431   foreach(Data::EntryPtr it, m_selectedEntries) {
0432     entries.append(Data::EntryPtr(new Data::Entry(*it)));
0433   }
0434   Kernel::self()->addEntries(entries, false);
0435   slotUpdateSelection(old);
0436 }
0437 
0438 void Controller::blockAllSignals(bool block_) const {
0439 // sanity check
0440   if(!m_mainWindow->m_initialized) {
0441     return;
0442   }
0443   m_mainWindow->m_detailedView->blockSignals(block_);
0444   m_mainWindow->m_groupView->blockSignals(block_);
0445   m_mainWindow->m_quickFilter->blockSignals(block_);
0446   if(m_mainWindow->m_loanView) {
0447     m_mainWindow->m_loanView->blockSignals(block_);
0448   }
0449   if(m_mainWindow->m_filterView) {
0450     m_mainWindow->m_filterView->blockSignals(block_);
0451   }
0452   m_mainWindow->m_editDialog->blockSignals(block_);
0453   m_mainWindow->m_iconView->blockSignals(block_);
0454 }
0455 
0456 void Controller::slotUpdateFilter(Tellico::FilterPtr filter_) {
0457   blockAllSignals(true);
0458 
0459   // the view takes over ownership of the filter
0460   if(filter_ && !filter_->isEmpty()) {
0461     // clear the icon view selection only
0462     // the detailed view takes care of itself
0463     m_mainWindow->m_iconView->clearSelection();
0464     m_selectedEntries.clear();
0465   }
0466   updateActions();
0467 
0468   m_mainWindow->m_detailedView->setFilter(filter_); // takes ownership
0469   if(!filter_ && m_mainWindow->m_filterView && !m_mainWindow->m_dontQueueFilter) {
0470     // for example, when quick filter clears the selection
0471     // the check against m_dontQueueFilter is to prevent the situation when the FilterView has an Entry selected
0472     // which sends an empty filter selection, which would then clear the whole FilterView selection
0473     m_mainWindow->m_filterView->clearSelection();
0474   }
0475 
0476   blockAllSignals(false);
0477 
0478   m_mainWindow->slotEntryCount();
0479 }
0480 
0481 void Controller::clearFilter() {
0482   blockAllSignals(true);
0483   m_mainWindow->m_quickFilter->clear();
0484   m_mainWindow->m_detailedView->setFilter(Tellico::FilterPtr());
0485   blockAllSignals(false);
0486 }
0487 
0488 void Controller::editEntry(Tellico::Data::EntryPtr entry_) const {
0489   m_mainWindow->slotShowEntryEditor();
0490   m_mainWindow->m_editDialog->setContents(Data::EntryList() << entry_);
0491 }
0492 
0493 void Controller::plugCollectionActions(QMenu* popup_) {
0494   if(!popup_) {
0495     return;
0496   }
0497 
0498   popup_->addAction(m_mainWindow->action("coll_rename_collection"));
0499   popup_->addAction(m_mainWindow->action("coll_fields"));
0500   popup_->addAction(m_mainWindow->action("change_entry_grouping"));
0501 }
0502 
0503 void Controller::plugEntryActions(QMenu* popup_) {
0504   if(!popup_) {
0505     return;
0506   }
0507 
0508 //  m_mainWindow->m_newEntry->plug(popup_);
0509   popup_->addAction(m_mainWindow->m_editEntry);
0510   popup_->addAction(m_mainWindow->m_copyEntry);
0511   popup_->addAction(m_mainWindow->m_deleteEntry);
0512   popup_->addAction(m_mainWindow->m_mergeEntry);
0513   popup_->addMenu(m_mainWindow->m_updateEntryMenu->menu());
0514   // there's a bug in KActionMenu with KXMLGUIFactory::plugActionList
0515   // pluging the menu isn't enough to have the popup get populated
0516   plugUpdateMenu(popup_);
0517   popup_->addSeparator();
0518   popup_->addAction(m_mainWindow->m_checkOutEntry);
0519 }
0520 
0521 QMenu* Controller::plugSortActions(QMenu* popup_) {
0522   if(!popup_) {
0523     return nullptr;
0524   }
0525 
0526   QMenu* sortMenu = popup_->addMenu(i18n("&Sort By"));
0527   sortMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-sort"),
0528                                      QIcon::fromTheme(QStringLiteral("view-sort-ascending"))));
0529   foreach(Data::FieldPtr field, Data::Document::self()->collection()->fields()) {
0530     // not allowed to sort by Image, Table, Para, or URL
0531     if(field->type() == Data::Field::Image ||
0532        field->type() == Data::Field::Table ||
0533        field->type() == Data::Field::URL ||
0534        field->type() == Data::Field::Para) {
0535       continue;
0536     }
0537     sortMenu->addAction(field->title())->setData(QVariant::fromValue(field));
0538   }
0539   return sortMenu;
0540 }
0541 
0542 void Controller::plugUpdateMenu(QMenu* popup_) {
0543   QMenu* updatePopup = nullptr;
0544   foreach(QAction* action, popup_->actions()) {
0545     if(action && action->text() == m_mainWindow->m_updateEntryMenu->text()) {
0546       updatePopup = action->menu();
0547       break;
0548     }
0549   }
0550 
0551   if(!updatePopup) {
0552     return;
0553   }
0554 
0555   // I can't figure out why the actions get duplicated, but they do
0556   // so clear them all
0557   updatePopup->removeAction(m_mainWindow->m_updateAll);
0558   foreach(QAction* action, m_mainWindow->m_fetchActions) {
0559     updatePopup->removeAction(action);
0560   }
0561 
0562   // clear separator, too
0563   updatePopup->clear();
0564 
0565   updatePopup->addAction(m_mainWindow->m_updateAll);
0566   updatePopup->addSeparator();
0567   foreach(QAction* action, m_mainWindow->m_fetchActions) {
0568     updatePopup->addAction(action);
0569   }
0570 }
0571 
0572 void Controller::updateActions() const {
0573   const bool emptySelection = m_selectedEntries.isEmpty();
0574   m_mainWindow->stateChanged(QStringLiteral("empty_selection"),
0575                              emptySelection ? KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse);
0576   foreach(QAction* action, m_mainWindow->m_fetchActions) {
0577     action->setEnabled(!emptySelection);
0578   }
0579   //only enable citation items when it's a bibliography
0580   const bool isBibtex = Kernel::self()->collectionType() == Data::Collection::Bibtex;
0581   if(isBibtex) {
0582     m_mainWindow->action("cite_clipboard")->setEnabled(!emptySelection);
0583     m_mainWindow->action("cite_lyxpipe")->setEnabled(!emptySelection);
0584   }
0585   m_mainWindow->m_checkInEntry->setEnabled(canCheckIn());
0586 
0587   if(m_selectedEntries.count() < 2) {
0588     m_mainWindow->m_editEntry->setText(i18n("&Edit Entry..."));
0589     m_mainWindow->m_copyEntry->setText(i18n("D&uplicate Entry"));
0590     m_mainWindow->m_updateEntryMenu->setText(i18n("&Update Entry"));
0591     m_mainWindow->m_deleteEntry->setText(i18n("&Delete Entry"));
0592     m_mainWindow->m_mergeEntry->setEnabled(false);
0593   } else {
0594     m_mainWindow->m_editEntry->setText(i18n("&Edit Entries..."));
0595     m_mainWindow->m_copyEntry->setText(i18n("D&uplicate Entries"));
0596     m_mainWindow->m_updateEntryMenu->setText(i18n("&Update Entries"));
0597     m_mainWindow->m_deleteEntry->setText(i18n("&Delete Entries"));
0598     m_mainWindow->m_mergeEntry->setEnabled(true);
0599   }
0600 }
0601 
0602 void Controller::addedBorrower(Tellico::Data::BorrowerPtr borrower_) {
0603   m_mainWindow->addLoanView(); // just in case
0604   foreach(Observer* obs, m_observers) {
0605     obs->addBorrower(borrower_);
0606   }
0607   m_mainWindow->m_viewTabs->setTabBarHidden(false);
0608 }
0609 
0610 void Controller::modifiedBorrower(Tellico::Data::BorrowerPtr borrower_) {
0611   foreach(Observer* obs, m_observers) {
0612     if(borrower_->isEmpty()) {
0613       obs->removeBorrower(borrower_);
0614     } else {
0615       obs->modifyBorrower(borrower_);
0616     }
0617   }
0618   hideTabs();
0619 }
0620 
0621 void Controller::addedFilter(Tellico::FilterPtr filter_) {
0622   m_mainWindow->addFilterView(); // just in case
0623   foreach(Observer* obs, m_observers) {
0624     obs->addFilter(filter_);
0625   }
0626   m_mainWindow->m_viewTabs->setTabBarHidden(false);
0627 }
0628 
0629 void Controller::removedFilter(Tellico::FilterPtr filter_) {
0630   foreach(Observer* obs, m_observers) {
0631     obs->removeFilter(filter_);
0632   }
0633   hideTabs();
0634 }
0635 
0636 void Controller::slotCheckOut() {
0637   if(m_selectedEntries.isEmpty()) {
0638     return;
0639   }
0640 
0641   Data::EntryList loanedEntries = m_selectedEntries;
0642 
0643   // check to see if any of the entries are already on-loan, and warn user
0644   QMap<QString, Data::EntryPtr> alreadyLoaned;
0645   foreach(Data::BorrowerPtr borrower, Data::Document::self()->collection()->borrowers()) {
0646     foreach(Data::LoanPtr loan, borrower->loans()) {
0647       if(m_selectedEntries.contains(loan->entry())) {
0648         alreadyLoaned.insert(loan->entry()->title(), loan->entry());
0649       }
0650     }
0651   }
0652   if(!alreadyLoaned.isEmpty()) {
0653     KMessageBox::informationList(Kernel::self()->widget(),
0654                                  i18n("The following items are already loaned, but Tellico "
0655                                       "does not currently support lending an item multiple "
0656                                       "times. They will be removed from the list of items "
0657                                       "to lend."),
0658                                       alreadyLoaned.keys());
0659     QMap<QString, Data::EntryPtr>::const_iterator it = alreadyLoaned.constBegin();
0660     QMap<QString, Data::EntryPtr>::const_iterator end = alreadyLoaned.constEnd();
0661     for( ; it != end; ++it) {
0662       loanedEntries.removeAll(it.value());
0663     }
0664     if(loanedEntries.isEmpty()) {
0665       return;
0666     }
0667   }
0668 
0669   if(Kernel::self()->addLoans(loanedEntries)) {
0670     m_mainWindow->m_checkInEntry->setEnabled(true);
0671   }
0672 }
0673 
0674 void Controller::slotCheckIn() {
0675   slotCheckIn(m_selectedEntries);
0676 }
0677 
0678 void Controller::slotCheckIn(const Tellico::Data::EntryList& entries_) {
0679   if(entries_.isEmpty()) {
0680     return;
0681   }
0682 
0683   Data::LoanList loans;
0684   foreach(Data::EntryPtr entry, entries_) {
0685     // these have to be in the loop since if a borrower gets empty
0686     // it will be deleted, so the vector could change, for every entry iterator
0687     Data::BorrowerList vec = Data::Document::self()->collection()->borrowers();
0688     foreach(Data::BorrowerPtr borrower, vec) {
0689       Data::LoanPtr l = borrower->loan(entry);
0690       if(l) {
0691         loans.append(l);
0692         // assume it's only loaned once
0693         break;
0694       }
0695     }
0696   }
0697 
0698   if(Kernel::self()->removeLoans(loans)) {
0699     m_mainWindow->m_checkInEntry->setEnabled(false);
0700   }
0701   hideTabs();
0702 }
0703 
0704 void Controller::hideTabs() const {
0705   if((!m_mainWindow->m_filterView || m_mainWindow->m_filterView->isEmpty()) &&
0706      (!m_mainWindow->m_loanView || m_mainWindow->m_loanView->isEmpty())) {
0707     int idx = m_mainWindow->m_viewTabs->indexOf(m_mainWindow->m_groupView);
0708     m_mainWindow->m_viewTabs->setCurrentIndex(idx);
0709     m_mainWindow->m_viewTabs->setTabBarHidden(true);
0710   }
0711 }
0712 
0713 bool Controller::canCheckIn() const {
0714   foreach(Data::EntryPtr entry, m_selectedEntries) {
0715     if(entry->field(QStringLiteral("loaned")) == QLatin1String("true")) {
0716       return true;
0717     }
0718   }
0719   return false;
0720 }
0721 
0722 void Controller::updatedFetchers() {
0723   m_mainWindow->updateEntrySources();
0724 }