File indexing completed on 2024-04-28 16:31:54

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