diff --git a/src/borrowerdialog.h b/src/borrowerdialog.h index 6729f737..07a9bec3 100644 --- a/src/borrowerdialog.h +++ b/src/borrowerdialog.h @@ -1,89 +1,90 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #ifndef TELLICO_BORROWERDIALOG_H #define TELLICO_BORROWERDIALOG_H #include #include "borrower.h" #include #include #include class KLineEdit; class KJob; #ifdef HAVE_KABC namespace KContacts { class Addressee; } #endif namespace Tellico { /** * @author Robby Stephenson */ class BorrowerDialog : public QDialog { Q_OBJECT public: static Data::BorrowerPtr getBorrower(QWidget* parent); private Q_SLOTS: void selectItem(const QString& name); void updateEdit(QTreeWidgetItem* item); void akonadiSearchResult(KJob*); private: /** * The constructor sets up the dialog. * * @param parent A pointer to the parent widget */ BorrowerDialog(QWidget* parent); Data::BorrowerPtr borrower(); void populateBorrowerList(); QString m_uid; QTreeWidget* m_treeWidget; KLineEdit* m_lineEdit; QHash m_itemHash; class Item : public QTreeWidgetItem { public: #ifdef HAVE_KABC Item(QTreeWidget* parent, const KContacts::Addressee& addressee); #endif Item(QTreeWidget* parent, const Data::Borrower& borrower); const QString& uid() const { return m_uid; } private: + Q_DISABLE_COPY(Item) QString m_uid; }; }; } // end namespace #endif diff --git a/src/cite/actionmanager.h b/src/cite/actionmanager.h index ad78fbd2..acf0b002 100644 --- a/src/cite/actionmanager.h +++ b/src/cite/actionmanager.h @@ -1,75 +1,78 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #ifndef TELLICO_CITE_ACTIONMANAGER_H #define TELLICO_CITE_ACTIONMANAGER_H #include "../datavectors.h" namespace Tellico { namespace Cite { enum CiteAction { CiteClipboard, CiteLyxpipe }; /** * @author Robby Stephenson */ class Action { public: Action() {} virtual ~Action() {} virtual CiteAction type() const = 0; virtual bool connect() { return true; } virtual bool cite(Data::EntryList entries) = 0; virtual bool hasError() const { return false; } virtual QString errorString() const { return QString(); } + +private: + Q_DISABLE_COPY(Action) }; /** * @author Robby Stephenson */ class ActionManager { public: static ActionManager* self(); ~ActionManager(); bool cite(CiteAction action, Data::EntryList entries); bool hasError() const; QString errorString() const; private: ActionManager(); bool connect(CiteAction action); Action* m_action; }; } } #endif diff --git a/src/fetch/fetcherinfolistitem.h b/src/fetch/fetcherinfolistitem.h index b0dae53d..d20de6d3 100644 --- a/src/fetch/fetcherinfolistitem.h +++ b/src/fetch/fetcherinfolistitem.h @@ -1,72 +1,73 @@ /**************************************************************************** Copyright (C) 2001-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #ifndef TELLICO_FETCHERINFOLISTITEM_H #define TELLICO_FETCHERINFOLISTITEM_H #include "fetcher.h" #include namespace Tellico { namespace Fetch { class FetcherInfo { public: FetcherInfo(Type t, const QString& n, bool o, QString u=QString()) : type(t), name(n), updateOverwrite(o), uuid(u) {} Type type; QString name; bool updateOverwrite; QString uuid; }; } // end namespace class FetcherInfoListItem : public QListWidgetItem { public: explicit FetcherInfoListItem(const Fetch::FetcherInfo& info, const QString& groupName = QString()); FetcherInfoListItem(QListWidget* parent, const Fetch::FetcherInfo& info, const QString& groupName = QString()); void setConfigGroup(const QString& s); const QString& configGroup() const; Fetch::Type fetchType() const; void setUpdateOverwrite(bool b); bool updateOverwrite() const; void setNewSource(bool b); bool isNewSource() const; QString uuid() const; void setFetcher(Fetch::Fetcher::Ptr fetcher); Fetch::Fetcher::Ptr fetcher() const; private: + Q_DISABLE_COPY(FetcherInfoListItem) Fetch::FetcherInfo m_info; QString m_configGroup; bool m_newSource; Fetch::Fetcher::Ptr m_fetcher; }; } // end namespace #endif diff --git a/src/fetch/z3950connection.h b/src/fetch/z3950connection.h index 660a18ad..ae2f5153 100644 --- a/src/fetch/z3950connection.h +++ b/src/fetch/z3950connection.h @@ -1,135 +1,138 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #ifndef TELLICO_FETCH_Z3950CONNECTION_H #define TELLICO_FETCH_Z3950CONNECTION_H #include #include #include namespace Tellico { namespace Fetch { class Z3950Fetcher; class Z3950ResultFound : public QEvent { public: Z3950ResultFound(const QString& s); ~Z3950ResultFound(); const QString& result() const { return m_result; } static QEvent::Type uid() { return static_cast(QEvent::User + 11111); } private: + Q_DISABLE_COPY(Z3950ResultFound) QString m_result; }; class Z3950ConnectionDone : public QEvent { public: Z3950ConnectionDone(bool more) : QEvent(uid()), m_type(-1), m_hasMore(more) {} Z3950ConnectionDone(bool more, const QString& s, int t) : QEvent(uid()), m_msg(s), m_type(t), m_hasMore(more) {} const QString& message() const { return m_msg; } int messageType() const { return m_type; } bool hasMoreResults() const { return m_hasMore; } static QEvent::Type uid() { return static_cast(QEvent::User + 22222); } private: + Q_DISABLE_COPY(Z3950ConnectionDone) QString m_msg; int m_type; bool m_hasMore; }; class Z3950SyntaxChange : public QEvent { public: Z3950SyntaxChange(const QString& s) : QEvent(uid()), m_syntax(s) {} const QString& syntax() const { return m_syntax; } static QEvent::Type uid() { return static_cast(QEvent::User + 33333); } private: + Q_DISABLE_COPY(Z3950SyntaxChange) QString m_syntax; }; /** * @author Robby Stephenson */ class Z3950Connection : public QThread { public: Z3950Connection(Z3950Fetcher* fetcher, const QString& host, uint port, const QString& dbname, const QString& sourceCharSet, const QString& syntax, const QString& esn); ~Z3950Connection(); void reset(); void setQuery(const QString& query); void setUserPassword(const QString& user, const QString& pword); void run() Q_DECL_OVERRIDE; void abort() { m_aborted = true; } private: static QByteArray iconvRun(const QByteArray& text, const QString& fromCharSet, const QString& toCharSet); static QString toXML(const QByteArray& marc, const QString& fromCharSet); bool makeConnection(); void done(); void done(const QString& message, int type); QByteArray toByteArray(const QString& text); QString toString(const QByteArray& text); void checkPendingEvents(); class Private; Private* d; bool m_connected; bool m_aborted; QExplicitlySharedDataPointer m_fetcher; QString m_host; uint m_port; QString m_dbname; QString m_user; QString m_password; QString m_sourceCharSet; QString m_syntax; QString m_pqn; QString m_esn; size_t m_start; size_t m_limit; bool m_hasMore; friend class Z3950ResultFound; static int resultsLeft; }; } // end namespace } // end namespace #endif diff --git a/src/fetchdialog.cpp b/src/fetchdialog.cpp index b5a3b273..4a7f0501 100644 --- a/src/fetchdialog.cpp +++ b/src/fetchdialog.cpp @@ -1,948 +1,953 @@ /*************************************************************************** Copyright (C) 2003-2011 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include #include "fetchdialog.h" #include "fetch/fetchmanager.h" #include "fetch/fetcher.h" #include "fetch/fetchresult.h" #include "config/tellico_config.h" #include "entryview.h" #include "utils/isbnvalidator.h" #include "utils/upcvalidator.h" #include "tellico_kernel.h" #include "core/filehandler.h" #include "collection.h" #include "entry.h" #include "document.h" #include "field.h" #include "fieldformat.h" #include "gui/combobox.h" #include "utils/cursorsaver.h" #include "utils/stringset.h" #include "images/image.h" #include "tellico_debug.h" #ifdef ENABLE_WEBCAM #include "barcode/barcode.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { static const int FETCH_MIN_WIDTH = 600; static const char* FETCH_STRING_SEARCH = I18N_NOOP("&Search"); static const char* FETCH_STRING_STOP = I18N_NOOP("&Stop"); static const int StringDataType = QEvent::User; static const int ImageDataType = QEvent::User+1; class StringDataEvent : public QEvent { public: StringDataEvent(const QString& str) : QEvent(static_cast(StringDataType)), m_string(str) {} QString string() const { return m_string; } private: + Q_DISABLE_COPY(StringDataEvent) QString m_string; }; class ImageDataEvent : public QEvent { public: ImageDataEvent(const QImage& img) : QEvent(static_cast(ImageDataType)), m_image(img) {} QImage image() const { return m_image; } private: + Q_DISABLE_COPY(ImageDataEvent) QImage m_image; }; // class exists just to make sizeHintForColumn() public class TreeWidget : public QTreeWidget { public: TreeWidget(QWidget* p) : QTreeWidget(p) {} virtual int sizeHintForColumn(int c) const Q_DECL_OVERRIDE { return QTreeWidget::sizeHintForColumn(c); } }; } using Tellico::FetchDialog; using barcodeRecognition::barcodeRecognitionThread; class FetchDialog::FetchResultItem : public QTreeWidgetItem { friend class FetchDialog; // always add to end FetchResultItem(QTreeWidget* lv, Fetch::FetchResult* r) : QTreeWidgetItem(lv), m_result(r) { setData(1, Qt::DisplayRole, r->title); setData(2, Qt::DisplayRole, r->desc); setData(3, Qt::DisplayRole, r->fetcher->source()); setData(3, Qt::DecorationRole, Fetch::Manager::self()->fetcherIcon(r->fetcher)); } Fetch::FetchResult* m_result; + +private: + Q_DISABLE_COPY(FetchResultItem) }; FetchDialog::FetchDialog(QWidget* parent_) : QDialog(parent_) , m_timer(new QTimer(this)) , m_started(false) , m_resultCount(0) , m_treeWasResized(false) , m_barcodePreview(nullptr) , m_barcodeRecognitionThread(nullptr) { setModal(false); setWindowTitle(i18n("Internet Search")); QVBoxLayout* mainLayout = new QVBoxLayout(); setLayout(mainLayout); m_collType = Kernel::self()->collectionType(); QWidget* mainWidget = new QWidget(this); mainLayout->addWidget(mainWidget); QBoxLayout* topLayout = new QVBoxLayout(mainWidget); QGroupBox* queryBox = new QGroupBox(i18n("Search Query"), mainWidget); QBoxLayout* queryLayout = new QVBoxLayout(queryBox); topLayout->addWidget(queryBox); QWidget* box1 = new QWidget(queryBox); QHBoxLayout* box1HBoxLayout = new QHBoxLayout(box1); box1HBoxLayout->setMargin(0); queryLayout->addWidget(box1); QLabel* label = new QLabel(i18nc("Start the search", "S&earch:"), box1); box1HBoxLayout->addWidget(label); m_valueLineEdit = new QLineEdit(box1); box1HBoxLayout->addWidget(m_valueLineEdit); label->setBuddy(m_valueLineEdit); m_valueLineEdit->setWhatsThis(i18n("Enter a search value. An ISBN search must include the full ISBN.")); m_keyCombo = new GUI::ComboBox(box1); box1HBoxLayout->addWidget(m_keyCombo); Fetch::KeyMap map = Fetch::Manager::self()->keyMap(); for(Fetch::KeyMap::ConstIterator it = map.constBegin(); it != map.constEnd(); ++it) { m_keyCombo->addItem(it.value(), it.key()); } void (QComboBox::* activatedInt)(int) = &QComboBox::activated; connect(m_keyCombo, activatedInt, this, &FetchDialog::slotKeyChanged); m_keyCombo->setWhatsThis(i18n("Choose the type of search")); m_searchButton = new QPushButton(box1); box1HBoxLayout->addWidget(m_searchButton); KGuiItem::assign(m_searchButton, KGuiItem(i18n(FETCH_STRING_STOP), QIcon::fromTheme(QStringLiteral("dialog-cancel")))); connect(m_searchButton, &QAbstractButton::clicked, this, &FetchDialog::slotSearchClicked); m_searchButton->setWhatsThis(i18n("Click to start or stop the search")); // the search button's text changes from search to stop // I don't want it resizing, so figure out the maximum size and set that m_searchButton->ensurePolished(); int maxWidth = m_searchButton->sizeHint().width(); int maxHeight = m_searchButton->sizeHint().height(); KGuiItem::assign(m_searchButton, KGuiItem(i18n(FETCH_STRING_SEARCH), QIcon::fromTheme(QStringLiteral("edit-find")))); maxWidth = qMax(maxWidth, m_searchButton->sizeHint().width()); maxHeight = qMax(maxHeight, m_searchButton->sizeHint().height()); m_searchButton->setMinimumWidth(maxWidth); m_searchButton->setMinimumHeight(maxHeight); QWidget* box2 = new QWidget(queryBox); QHBoxLayout* box2HBoxLayout = new QHBoxLayout(box2); box2HBoxLayout->setMargin(0); queryLayout->addWidget(box2); m_multipleISBN = new QCheckBox(i18n("&Multiple ISBN/UPC search"), box2); box2HBoxLayout->addWidget(m_multipleISBN); m_multipleISBN->setWhatsThis(i18n("Check this box to search for multiple ISBN or UPC values.")); connect(m_multipleISBN, &QAbstractButton::toggled, this, &FetchDialog::slotMultipleISBN); m_editISBN = new QPushButton(box2); KGuiItem::assign(m_editISBN, KGuiItem(i18n("Edit ISBN/UPC values..."), QIcon::fromTheme(QStringLiteral("format-justify-fill")))); box2HBoxLayout->addWidget(m_editISBN); m_editISBN->setEnabled(false); m_editISBN->setWhatsThis(i18n("Click to open a text edit box for entering or editing multiple ISBN or UPC values.")); connect(m_editISBN, &QAbstractButton::clicked, this, &FetchDialog::slotEditMultipleISBN); // add for spacing box2HBoxLayout->setStretchFactor(new QWidget(box2), 10); label = new QLabel(i18n("Search s&ource:"), box2); box2HBoxLayout->addWidget(label); m_sourceCombo = new KComboBox(box2); box2HBoxLayout->addWidget(m_sourceCombo); label->setBuddy(m_sourceCombo); Fetch::FetcherVec sources = Fetch::Manager::self()->fetchers(m_collType); foreach(Fetch::Fetcher::Ptr fetcher, sources) { m_sourceCombo->addItem(Fetch::Manager::self()->fetcherIcon(fetcher), fetcher->source()); } void (QComboBox::* activatedString)(const QString&) = &QComboBox::activated; connect(m_sourceCombo, activatedString, this, &FetchDialog::slotSourceChanged); m_sourceCombo->setWhatsThis(i18n("Select the database to search")); // for whatever reason, the dialog window could get shrunk and truncate the text box2->setMinimumWidth(box2->minimumSizeHint().width()); QSplitter* split = new QSplitter(Qt::Vertical, mainWidget); topLayout->addWidget(split); split->setChildrenCollapsible(false); // using ::TreeWidget as a lazy step to make a protected method public m_treeWidget = new TreeWidget(split); m_treeWidget->sortItems(1, Qt::AscendingOrder); m_treeWidget->setAllColumnsShowFocus(true); m_treeWidget->setSortingEnabled(true); m_treeWidget->setRootIsDecorated(false); m_treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); m_treeWidget->setHeaderLabels(QStringList() << QString() << i18n("Title") << i18n("Description") << i18n("Source")); m_treeWidget->model()->setHeaderData(0, Qt::Horizontal, Qt::AlignHCenter, Qt::TextAlignmentRole); // align checkmark in middle m_treeWidget->viewport()->installEventFilter(this); m_treeWidget->header()->setSortIndicatorShown(true); m_treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // will show a check mark when added m_treeWidget->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); connect(m_treeWidget, &QTreeWidget::itemSelectionChanged, this, &FetchDialog::slotShowEntry); // double clicking should add the entry connect(m_treeWidget, &QTreeWidget::itemDoubleClicked, this, &FetchDialog::slotAddEntry); connect(m_treeWidget->header(), &QHeaderView::sectionResized, this, &FetchDialog::columnResized); m_treeWidget->setWhatsThis(i18n("As results are found, they are added to this list. Selecting one " "will fetch the complete entry and show it in the view below.")); m_entryView = new EntryView(split); // don't bother creating funky gradient images for compact view m_entryView->setUseGradientImages(false); // set the xslt file AFTER setting the gradient image option m_entryView->setXSLTFile(QStringLiteral("Compact.xsl")); m_entryView->addXSLTStringParam("skip-fields", "id,mdate,cdate"); m_entryView->view()->setWhatsThis(i18n("An entry may be shown here before adding it to the " "current collection by selecting it in the list above")); QWidget* box3 = new QWidget(mainWidget); QHBoxLayout* box3HBoxLayout = new QHBoxLayout(box3); box3HBoxLayout->setMargin(0); topLayout->addWidget(box3); m_addButton = new QPushButton(i18n("&Add Entry"), box3); box3HBoxLayout->addWidget(m_addButton); m_addButton->setEnabled(false); m_addButton->setIcon(QIcon::fromTheme(QLatin1String(":/icons/") + Kernel::self()->collectionTypeName())); connect(m_addButton, &QAbstractButton::clicked, this, &FetchDialog::slotAddEntry); m_addButton->setWhatsThis(i18n("Add the selected entry to the current collection")); m_moreButton = new QPushButton(box3); KGuiItem::assign(m_moreButton, KGuiItem(i18n("Get More Results"), QIcon::fromTheme(QStringLiteral("edit-find")))); box3HBoxLayout->addWidget(m_moreButton); m_moreButton->setEnabled(false); connect(m_moreButton, &QAbstractButton::clicked, this, &FetchDialog::slotMoreClicked); m_moreButton->setWhatsThis(i18n("Fetch more results from the current data source")); QPushButton* clearButton = new QPushButton(box3); KGuiItem::assign(clearButton, KStandardGuiItem::clear()); box3HBoxLayout->addWidget(clearButton); connect(clearButton, &QAbstractButton::clicked, this, &FetchDialog::slotClearClicked); clearButton->setWhatsThis(i18n("Clear all search fields and results")); QWidget* bottombox = new QWidget(mainWidget); QHBoxLayout* bottomboxHBoxLayout = new QHBoxLayout(bottombox); bottomboxHBoxLayout->setMargin(0); topLayout->addWidget(bottombox); m_statusBar = new QStatusBar(bottombox); bottomboxHBoxLayout->addWidget(m_statusBar); m_statusLabel = new QLabel(m_statusBar); m_statusBar->addPermanentWidget(m_statusLabel, 1); m_progress = new QProgressBar(m_statusBar); m_progress->setMaximum(0); m_progress->setFixedHeight(fontMetrics().height()+2); m_progress->hide(); m_statusBar->addPermanentWidget(m_progress); m_statusBar->setSizeGripEnabled(false); QPushButton* closeButton = new QPushButton(bottombox); KGuiItem::assign(closeButton, KStandardGuiItem::close()); bottomboxHBoxLayout->addWidget(closeButton); connect(closeButton, &QAbstractButton::clicked, this, &QDialog::accept); connect(m_timer, &QTimer::timeout, this, &FetchDialog::slotMoveProgress); setMinimumWidth(qMax(minimumWidth(), qMax(FETCH_MIN_WIDTH, minimumSizeHint().width()))); setStatus(i18n("Ready.")); KConfigGroup config(KSharedConfig::openConfig(), "Fetch Dialog Options"); QList splitList = config.readEntry("Splitter Sizes", QList()); if(!splitList.empty()) { split->setSizes(splitList); } connect(Fetch::Manager::self(), &Fetch::Manager::signalResultFound, this, &FetchDialog::slotResultFound); connect(Fetch::Manager::self(), &Fetch::Manager::signalStatus, this, &FetchDialog::slotStatus); connect(Fetch::Manager::self(), &Fetch::Manager::signalDone, this, &FetchDialog::slotFetchDone); KAcceleratorManager::manage(this); // initialize combos QTimer::singleShot(0, this, &FetchDialog::slotInit); } FetchDialog::~FetchDialog() { #ifdef ENABLE_WEBCAM if(m_barcodeRecognitionThread) { m_barcodeRecognitionThread->stop(); if(!m_barcodeRecognitionThread->wait( 1000 )) { m_barcodeRecognitionThread->terminate(); } delete m_barcodeRecognitionThread; m_barcodeRecognitionThread = nullptr; } if(m_barcodePreview) { delete m_barcodePreview; m_barcodePreview = nullptr; } #endif qDeleteAll(m_results); m_results.clear(); // we might have downloaded a lot of images we don't need to keep Data::EntryList entriesToCheck; foreach(Data::EntryPtr entry, m_entries) { entriesToCheck.append(entry); } // no additional entries to check images to keep though Data::Document::self()->removeImagesNotInCollection(entriesToCheck, Data::EntryList()); KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Fetch Dialog Options")); KWindowConfig::saveWindowSize(windowHandle(), config); config.writeEntry("Splitter Sizes", static_cast(m_treeWidget->parentWidget())->sizes()); config.writeEntry("Search Key", m_keyCombo->currentData().toInt()); config.writeEntry("Search Source", m_sourceCombo->currentText()); } void FetchDialog::closeEvent(QCloseEvent* event_) { // stop fetchers when the dialog is closed if(m_started) { QTimer::singleShot(0, Fetch::Manager::self(), &Fetch::Manager::stop); } QDialog::closeEvent(event_); } void FetchDialog::slotSearchClicked() { m_valueLineEdit->selectAll(); if(m_started) { setStatus(i18n("Cancelling the search...")); Fetch::Manager::self()->stop(); } else { const QString value = m_valueLineEdit->text().simplified(); if(value.isEmpty()) { return; } m_resultCount = 0; m_oldSearch = value; m_started = true; KGuiItem::assign(m_searchButton, KGuiItem(i18n(FETCH_STRING_STOP), QIcon::fromTheme(QStringLiteral("dialog-cancel")))); startProgress(); setStatus(i18n("Searching...")); qApp->processEvents(); Fetch::Manager::self()->startSearch(m_sourceCombo->currentText(), static_cast(m_keyCombo->currentData().toInt()), value); } } void FetchDialog::slotClearClicked() { fetchDone(false); m_treeWidget->clear(); m_entryView->clear(); Fetch::Manager::self()->stop(); m_multipleISBN->setChecked(false); m_valueLineEdit->clear(); m_valueLineEdit->setFocus(); m_addButton->setEnabled(false); m_moreButton->setEnabled(false); m_isbnList.clear(); m_statusMessages.clear(); setStatus(i18n("Ready.")); // because slotFetchDone() writes text } void FetchDialog::slotStatus(const QString& status_) { m_statusMessages.push_back(status_); // if the queue was empty, start the timer if(m_statusMessages.count() == 1) { // wait 2 seconds QTimer::singleShot(2000, this, &FetchDialog::slotUpdateStatus); } } void FetchDialog::slotUpdateStatus() { if(m_statusMessages.isEmpty()) { return; } setStatus(m_statusMessages.front()); m_statusMessages.pop_front(); if(!m_statusMessages.isEmpty()) { // wait 2 seconds QTimer::singleShot(2000, this, &FetchDialog::slotUpdateStatus); } } void FetchDialog::setStatus(const QString& text_) { m_statusLabel->setText(QLatin1Char(' ') + text_); } void FetchDialog::slotFetchDone() { fetchDone(true); } void FetchDialog::fetchDone(bool checkISBN_) { // myDebug() << "fetchDone"; m_started = false; KGuiItem::assign(m_searchButton, KGuiItem(i18n(FETCH_STRING_SEARCH), QIcon::fromTheme(QStringLiteral("edit-find")))); stopProgress(); if(m_resultCount == 0) { slotStatus(i18n("The search returned no items.")); } else { /* TRANSLATORS: This is a plural form, you need to translate both lines (except "_n: ") */ slotStatus(i18np("The search returned 1 item.", "The search returned %1 items.", m_resultCount)); } m_moreButton->setEnabled(Fetch::Manager::self()->hasMoreResults()); // if we're not checking isbn values, then, ok to return if(!checkISBN_) { return; } const Fetch::FetchKey key = static_cast(m_keyCombo->currentData().toInt()); // no way to currently check EAN/UPC values for non-book items if(m_collType & (Data::Collection::Book | Data::Collection::Bibtex) && m_multipleISBN->isChecked() && (key == Fetch::ISBN || key == Fetch::UPC)) { QStringList searchValues = FieldFormat::splitValue(m_oldSearch.simplified()); QStringList resultValues; resultValues.reserve(m_treeWidget->topLevelItemCount()); for(int i = 0; i < m_treeWidget->topLevelItemCount(); ++i) { resultValues << static_cast(m_treeWidget->topLevelItem(i))->m_result->isbn; } // Google Book Search can have an error, returning a different ISBN in the initial search // than the one returned by fetchEntryHook(). As a small workaround, if only a single ISBN value // is in the search term, then don't show if(searchValues.count() > 1) { const QStringList valuesNotFound = ISBNValidator::listDifference(searchValues, resultValues); if(!valuesNotFound.isEmpty()) { KMessageBox::informationList(this, i18n("No results were found for the following ISBN values:"), valuesNotFound, i18n("No Results")); } } } } void FetchDialog::slotResultFound(Tellico::Fetch::FetchResult* result_) { m_results.append(result_); (void) new FetchResultItem(m_treeWidget, result_); // resize final column to size of contents if the user has never resized anything before if(!m_treeWasResized) { m_treeWidget->header()->setStretchLastSection(false); // do math to try to make a nice resizing, emphasizing sections 1 and 2 over 3 const int w0 = m_treeWidget->columnWidth(0); const int w1 = m_treeWidget->columnWidth(1); const int w2 = m_treeWidget->columnWidth(2); const int w3 = m_treeWidget->columnWidth(3); const int wt = m_treeWidget->width(); // myDebug() << "OLD:" << w0 << w1 << w2 << w3 << wt << (w0+w1+w2+w3); // whatever is leftover from resizing 3, split between 1 and 2 if(wt > (w0 + w1 + w2 + w3)) { const int w1rec = static_cast(m_treeWidget)->sizeHintForColumn(1); const int w2rec = static_cast(m_treeWidget)->sizeHintForColumn(2); const int w3rec = static_cast(m_treeWidget)->sizeHintForColumn(3); if(w1 < w1rec || w2 < w2rec) { const int w3new = qMin(w3, w3rec); const int diff = wt - w0 - w1 - w2 - w3new; const int w1new = qBound(w1, w1rec, w1 + diff/2 - 4); const int w2new = qBound(w2, wt - w0 - w1new - w3new, w2rec); m_treeWidget->setColumnWidth(1, w1new); m_treeWidget->setColumnWidth(2, w2new); m_treeWidget->setColumnWidth(3, w3new); // myDebug() << "New:" << w0 << w1new << w2new << w3new << wt << (w0+w1new+w2new+w3new); } } m_treeWidget->header()->setStretchLastSection(true); // because calling setColumnWidth() will change this m_treeWasResized = false; } ++m_resultCount; } void FetchDialog::slotAddEntry() { GUI::CursorSaver cs; Data::EntryList vec; foreach(QTreeWidgetItem* item_, m_treeWidget->selectedItems()) { FetchResultItem* item = static_cast(item_); Fetch::FetchResult* r = item->m_result; Data::EntryPtr entry = m_entries.value(r->uid); if(!entry) { setStatus(i18n("Fetching %1...", r->title)); startProgress(); entry = r->fetchEntry(); if(!entry) { continue; } m_entries.insert(r->uid, entry); stopProgress(); setStatus(i18n("Ready.")); } if(entry->collection()->hasField(QStringLiteral("fetchdialog_source"))) { entry->collection()->removeField(QStringLiteral("fetchdialog_source")); } // add a copy, intentionally allowing multiple copies to be added vec.append(Data::EntryPtr(new Data::Entry(*entry))); item->setData(0, Qt::DecorationRole, QIcon::fromTheme(QStringLiteral("checkmark"), QIcon(QLatin1String(":/icons/checkmark")))); } if(!vec.isEmpty()) { Kernel::self()->addEntries(vec, true); } } void FetchDialog::slotMoreClicked() { if(m_started) { myDebug() << "can't continue while running"; return; } m_started = true; KGuiItem::assign(m_searchButton, KGuiItem(i18n(FETCH_STRING_STOP), QIcon::fromTheme(QStringLiteral("dialog-cancel")))); startProgress(); setStatus(i18n("Searching...")); qApp->processEvents(); Fetch::Manager::self()->continueSearch(); } void FetchDialog::slotShowEntry() { // just in case m_statusMessages.clear(); QList items = m_treeWidget->selectedItems(); if(items.isEmpty()) { m_addButton->setEnabled(false); return; } m_addButton->setEnabled(true); if(items.count() > 1) { m_entryView->clear(); return; } FetchResultItem* item = static_cast(items.first()); Fetch::FetchResult* r = item->m_result; setStatus(i18n("Fetching %1...", r->title)); Data::EntryPtr entry = m_entries.value(r->uid); if(!entry) { GUI::CursorSaver cs; startProgress(); entry = r->fetchEntry(); if(entry) { // might conceivably be null m_entries.insert(r->uid, entry); } stopProgress(); } if(!entry || !entry->collection()) { myDebug() << "no entry or collection pointer"; setStatus(i18n("Ready.")); return; } if(!entry->collection()->hasField(QStringLiteral("fetchdialog_source"))) { Data::FieldPtr f(new Data::Field(QStringLiteral("fetchdialog_source"), i18n("Attribution"), Data::Field::Para)); entry->collection()->addField(f); } const QPixmap sourceIcon = Fetch::Manager::self()->fetcherIcon(r->fetcher); const QByteArray ba = Data::Image::byteArray(sourceIcon.toImage(), "PNG"); QString text = QStringLiteral(" %2
%3
") .arg(QLatin1String(ba.toBase64()), r->fetcher->source(), r->fetcher->attribution()); entry->setField(QStringLiteral("fetchdialog_source"), text); setStatus(i18n("Ready.")); m_entryView->showEntry(entry); } void FetchDialog::startProgress() { m_progress->show(); m_timer->start(100); } void FetchDialog::slotMoveProgress() { m_progress->setValue(m_progress->value()+5); } void FetchDialog::stopProgress() { m_timer->stop(); m_progress->hide(); } void FetchDialog::slotInit() { // do this in the singleShot slot so it works // see note in entryeditdialog.cpp (Feb 2017) KConfigGroup config(KSharedConfig::openConfig(), "Fetch Dialog Options"); KWindowConfig::restoreWindowSize(windowHandle(), config); if(!Fetch::Manager::self()->canFetch()) { m_searchButton->setEnabled(false); Kernel::self()->sorry(i18n("No Internet sources are available for your current collection type."), this); } int key = config.readEntry("Search Key", int(Fetch::FetchFirst)); // only change key if valid if(key > Fetch::FetchFirst) { m_keyCombo->setCurrentData(key); } slotKeyChanged(m_keyCombo->currentIndex()); QString source = config.readEntry("Search Source"); if(!source.isEmpty()) { int idx = m_sourceCombo->findText(source); if(idx > -1) { m_sourceCombo->setCurrentIndex(idx); } } slotSourceChanged(m_sourceCombo->currentText()); m_valueLineEdit->setFocus(); m_searchButton->setDefault(true); } void FetchDialog::slotKeyChanged(int idx_) { int key = m_keyCombo->itemData(idx_).toInt(); if(key == Fetch::ISBN || key == Fetch::UPC || key == Fetch::LCCN) { m_multipleISBN->setEnabled(true); if(key == Fetch::ISBN) { m_valueLineEdit->setValidator(new ISBNValidator(this)); } else { UPCValidator* upc = new UPCValidator(this); connect(upc, &UPCValidator::signalISBN, this, &FetchDialog::slotUPC2ISBN); m_valueLineEdit->setValidator(upc); // only want to convert to ISBN if ISBN is accepted by the fetcher Fetch::KeyMap map = Fetch::Manager::self()->keyMap(m_sourceCombo->currentText()); upc->setCheckISBN(map.contains(Fetch::ISBN)); } } else { m_multipleISBN->setChecked(false); m_multipleISBN->setEnabled(false); // slotMultipleISBN(false); m_valueLineEdit->setValidator(nullptr); } if(key == Fetch::ISBN || key == Fetch::UPC) { openBarcodePreview(); } else { closeBarcodePreview(); } } void FetchDialog::slotSourceChanged(const QString& source_) { int curr = m_keyCombo->currentData().toInt(); m_keyCombo->clear(); Fetch::KeyMap map = Fetch::Manager::self()->keyMap(source_); for(Fetch::KeyMap::ConstIterator it = map.constBegin(); it != map.constEnd(); ++it) { m_keyCombo->addItem(it.value(), it.key()); } m_keyCombo->setCurrentData(curr); slotKeyChanged(m_keyCombo->currentIndex()); } void FetchDialog::slotMultipleISBN(bool toggle_) { bool wasEnabled = m_valueLineEdit->isEnabled(); m_valueLineEdit->setEnabled(!toggle_); if(!wasEnabled && m_valueLineEdit->isEnabled()) { // if we enable it, it probably had multiple isbn values // the validator doesn't like that, so only keep the first value QString val = m_valueLineEdit->text().section(QLatin1Char(';'), 0, 0); m_valueLineEdit->setText(val); } m_editISBN->setEnabled(toggle_); if(toggle_) { // if we're editing multiple values, it makes sense to popup the dialog now slotEditMultipleISBN(); } } void FetchDialog::slotEditMultipleISBN() { QDialog dlg(this); dlg.setModal(true); dlg.setWindowTitle(i18n("Edit ISBN/UPC Values")); QVBoxLayout* mainLayout = new QVBoxLayout(); dlg.setLayout(mainLayout); QWidget* box = new QWidget(&dlg); QVBoxLayout* boxVBoxLayout = new QVBoxLayout(box); boxVBoxLayout->setMargin(0); boxVBoxLayout->setSpacing(10); mainLayout->addWidget(box); QString s = i18n("Enter the ISBN or UPC values, one per line."); QLabel* l = new QLabel(s, box); boxVBoxLayout->addWidget(l); m_isbnTextEdit = new KTextEdit(box); boxVBoxLayout->addWidget(m_isbnTextEdit); if(m_isbnList.isEmpty()) { m_isbnTextEdit->setText(m_valueLineEdit->text()); } else { m_isbnTextEdit->setText(m_isbnList.join(QLatin1String("\n"))); } m_isbnTextEdit->setWhatsThis(s); connect(m_isbnTextEdit.data(), &QTextEdit::textChanged, this, &FetchDialog::slotISBNTextChanged); QPushButton* fromFileBtn = new QPushButton(box); boxVBoxLayout->addWidget(fromFileBtn); KGuiItem::assign(fromFileBtn, KStandardGuiItem::open()); fromFileBtn->setText(i18n("&Load From File...")); fromFileBtn->setWhatsThis(i18n("Load the list from a text file.")); connect(fromFileBtn, &QAbstractButton::clicked, this, &FetchDialog::slotLoadISBNList); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); boxVBoxLayout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); dlg.setMinimumWidth(qMax(dlg.minimumWidth(), FETCH_MIN_WIDTH*2/3)); if(dlg.exec() == QDialog::Accepted) { m_isbnList = m_isbnTextEdit->toPlainText().split(QStringLiteral("\n")); const QValidator* val = m_valueLineEdit->validator(); if(val) { for(QStringList::Iterator it = m_isbnList.begin(); it != m_isbnList.end(); ++it) { val->fixup(*it); if((*it).isEmpty()) { it = m_isbnList.erase(it); // this is next item, shift backward --it; } } } if(m_isbnList.count() > 100) { Kernel::self()->sorry(i18n("An ISBN search can contain a maximum of 100 ISBN values. Only the " "first 100 values in your list will be used."), this); m_isbnList = m_isbnList.mid(0, 100); } m_valueLineEdit->setText(m_isbnList.join(FieldFormat::delimiterString())); } m_isbnTextEdit = nullptr; // gets auto-deleted } void FetchDialog::slotLoadISBNList() { if(!m_isbnTextEdit) { return; } QUrl u = QUrl::fromLocalFile(QFileDialog::getOpenFileName(this, QString(), QString(), QString())); if(u.isValid()) { m_isbnTextEdit->setText(m_isbnTextEdit->toPlainText() + FileHandler::readTextFile(u)); m_isbnTextEdit->moveCursor(QTextCursor::End); m_isbnTextEdit->ensureCursorVisible(); } } void FetchDialog::slotISBNTextChanged() { if(!m_isbnTextEdit) { return; } const QValidator* val = m_valueLineEdit->validator(); if(!val) { return; } const QString text = m_isbnTextEdit->toPlainText(); if(text.isEmpty()) { return; } const QTextCursor cursor = m_isbnTextEdit->textCursor(); // only try to validate if char before cursor is an eol if(cursor.atStart() || text.at(cursor.position()-1) != QLatin1Char('\n')) { return; } QStringList lines = text.left(cursor.position()-1).split(QStringLiteral("\n")); QString newLine = lines.last(); int pos = 0; // validate() changes the input if(val->validate(newLine, pos) != QValidator::Acceptable) { return; } lines.replace(lines.count()-1, newLine); QString newText = lines.join(QLatin1String("\n")) + text.mid(cursor.position()-1); if(newText == text) { return; } if(newText.isEmpty()) { m_isbnTextEdit->clear(); } else { m_isbnTextEdit->blockSignals(true); m_isbnTextEdit->setPlainText(newText); m_isbnTextEdit->setTextCursor(cursor); m_isbnTextEdit->blockSignals(false); } } void FetchDialog::slotUPC2ISBN() { int key = m_keyCombo->currentData().toInt(); if(key == Fetch::UPC) { m_keyCombo->setCurrentData(Fetch::ISBN); slotKeyChanged(m_keyCombo->currentIndex()); } } void FetchDialog::columnResized(int column_) { // only care about the middle two. First is the checkmark icon, last is not resizeable if(column_ == 1 || column_ == 2) { m_treeWasResized = true; } } void FetchDialog::slotResetCollection() { if(m_collType == Kernel::self()->collectionType()) { return; } m_collType = Kernel::self()->collectionType(); m_sourceCombo->clear(); Fetch::FetcherVec sources = Fetch::Manager::self()->fetchers(m_collType); foreach(Fetch::Fetcher::Ptr fetcher, sources) { m_sourceCombo->addItem(Fetch::Manager::self()->fetcherIcon(fetcher), fetcher->source()); } m_addButton->setIcon(QIcon(QLatin1String(":/icons/") + Kernel::self()->collectionTypeName())); if(Fetch::Manager::self()->canFetch()) { m_searchButton->setEnabled(true); } else { m_searchButton->setEnabled(false); Kernel::self()->sorry(i18n("No Internet sources are available for your current collection type."), this); } } void FetchDialog::slotBarcodeRecognized(const QString& string_) { // attention: this slot is called in the context of another thread => do not use GUI-functions! StringDataEvent* e = new StringDataEvent(string_); qApp->postEvent(this, e); // the event loop will call FetchDialog::customEvent() in the context of the GUI thread } void FetchDialog::slotBarcodeGotImage(const QImage& img_) { // attention: this slot is called in the context of another thread => do not use GUI-functions! ImageDataEvent* e = new ImageDataEvent(img_); qApp->postEvent(this, e); // the event loop will call FetchDialog::customEvent() in the context of the GUI thread } void FetchDialog::openBarcodePreview() { if(!Config::enableWebcam()) { return; } #ifdef ENABLE_WEBCAM if(m_barcodePreview) { m_barcodePreview->show(); m_barcodeRecognitionThread->start(); return; } // barcode recognition m_barcodeRecognitionThread = new barcodeRecognitionThread(); if(m_barcodeRecognitionThread->isWebcamAvailable()) { m_barcodePreview = new QLabel(nullptr); m_barcodePreview->resize(m_barcodeRecognitionThread->getPreviewSize()); m_barcodePreview->move(QApplication::desktop()->screenGeometry(m_barcodePreview).width() - m_barcodePreview->frameGeometry().width(), 30); m_barcodePreview->show(); connect(m_barcodeRecognitionThread, &barcodeRecognition::barcodeRecognitionThread::recognized, this, &FetchDialog::slotBarcodeRecognized); connect(m_barcodeRecognitionThread, &barcodeRecognition::barcodeRecognitionThread::gotImage, this, &FetchDialog::slotBarcodeGotImage); // connect( m_barcodePreview, SIGNAL(destroyed(QObject *)), this, SLOT(slotBarcodeStop()) ); m_barcodeRecognitionThread->start(); } #endif } void FetchDialog::closeBarcodePreview() { #ifdef ENABLE_WEBCAM if(!m_barcodePreview || !m_barcodeRecognitionThread) { return; } m_barcodePreview->hide(); m_barcodeRecognitionThread->stop(); #endif } void FetchDialog::customEvent(QEvent* e) { if(!e) { return; } if(e->type() == StringDataType) { // slotBarcodeRecognized() queued call qApp->beep(); m_valueLineEdit->setText(static_cast(e)->string()); m_searchButton->animateClick(); } else if(e->type() == ImageDataType) { // slotBarcodegotImage() queued call m_barcodePreview->setPixmap(QPixmap::fromImage(static_cast(e)->image())); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d8348e80..8250c562 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,2387 +1,2388 @@ /*************************************************************************** Copyright (C) 2001-2014 Robby Stephenson Copyright (C) 2011 Pedro Miguel Carvalho ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "mainwindow.h" #include "tellico_kernel.h" #include "document.h" #include "detailedlistview.h" #include "entryeditdialog.h" #include "groupview.h" #include "viewstack.h" #include "collection.h" #include "collectionfactory.h" #include "entry.h" #include "configdialog.h" #include "filter.h" #include "filterdialog.h" #include "collectionfieldsdialog.h" #include "controller.h" #include "importdialog.h" #include "exportdialog.h" #include "core/filehandler.h" // needed so static mainWindow variable can be set #include "translators/htmlexporter.h" // for printing #include "entryview.h" #include "entryiconview.h" #include "images/imagefactory.h" // needed so tmp files can get cleaned #include "collections/collectioninitializer.h" #include "collections/bibtexcollection.h" // needed for bibtex string macro dialog #include "utils/bibtexhandler.h" // needed for bibtex options #include "utils/datafileregistry.h" #include "fetchdialog.h" #include "reportdialog.h" #include "bibtexkeydialog.h" #include "core/tellico_strings.h" #include "filterview.h" #include "loanview.h" #include "fetch/fetchmanager.h" #include "fetch/fetcherinitializer.h" #include "cite/actionmanager.h" #include "config/tellico_config.h" #include "core/netaccess.h" #include "dbusinterface.h" #include "models/models.h" #include "models/entryiconmodel.h" #include "models/entryselectionmodel.h" #include "newstuff/manager.h" #include "gui/drophandler.h" #include "gui/stringmapdialog.h" #include "gui/lineedit.h" #include "gui/statusbar.h" #include "gui/tabwidget.h" #include "gui/dockwidget.h" #include "utils/cursorsaver.h" #include "utils/guiproxy.h" #include "tellico_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // needed for copy, cut, paste slots #include #include #include #include +#include #include namespace { static const int MAIN_WINDOW_MIN_WIDTH = 600; static const int MAX_IMAGES_WARN_PERFORMANCE = 200; QIcon mimeIcon(const char* s) { QMimeDatabase db; QMimeType ptr = db.mimeTypeForName(QLatin1String(s)); if(!ptr.isValid()) { myDebug() << "*** no icon for" << s; } return ptr.isValid() ? QIcon::fromTheme(ptr.iconName()) : QIcon(); } QIcon mimeIcon(const char* s1, const char* s2) { QMimeDatabase db; QMimeType ptr = db.mimeTypeForName(QLatin1String(s1)); if(!ptr.isValid()) { ptr = db.mimeTypeForName(QLatin1String(s2)); if(!ptr.isValid()) { myDebug() << "*** no icon for" << s1 << "or" << s2; } } return ptr.isValid() ? QIcon::fromTheme(ptr.iconName()) : QIcon(); } } using namespace Tellico; using Tellico::MainWindow; MainWindow::MainWindow(QWidget* parent_/*=0*/) : KXmlGuiWindow(parent_), m_updateAll(nullptr), m_statusBar(nullptr), m_editDialog(nullptr), m_groupView(nullptr), m_filterView(nullptr), m_loanView(nullptr), m_configDlg(nullptr), m_filterDlg(nullptr), m_collFieldsDlg(nullptr), m_stringMacroDlg(nullptr), m_bibtexKeyDlg(nullptr), m_fetchDlg(nullptr), m_reportDlg(nullptr), m_queuedFilters(0), m_initialized(false), m_newDocument(true), m_dontQueueFilter(false), m_savingImageLocationChange(false) { Controller::init(this); // the only time this is ever called! // has to be after controller init Kernel::init(this); // the only time this is ever called! GUI::Proxy::setMainWidget(this); setWindowIcon(QIcon::fromTheme(QStringLiteral("tellico"), QIcon(QLatin1String(":/icons/tellico")))); // initialize the status bar and progress bar initStatusBar(); // initialize all the collection types // which must be done before the document is created CollectionInitializer initCollections; // register all the fetcher types Fetch::FetcherInitializer initFetchers; // create a document, which also creates an empty book collection // must be done before the different widgets are created initDocument(); // set up all the actions, some connect to the document, so this must be after initDocument() initActions(); // create the different widgets in the view, some widgets connect to actions, so must be after initActions() initView(); // The edit dialog is not created until after the main window is initialized, so it can be a child. // So don't make any connections, don't read options for it until initFileOpen readOptions(); setAcceptDrops(true); DropHandler* drophandler = new DropHandler(this); installEventFilter(drophandler); new ApplicationInterface(this); new CollectionInterface(this); MARK_LINE; QTimer::singleShot(0, this, &MainWindow::slotInit); } MainWindow::~MainWindow() { qDeleteAll(m_fetchActions); m_fetchActions.clear(); } void MainWindow::slotInit() { MARK; // if the edit dialog exists, we know we've already called this function if(m_editDialog) { return; } m_editDialog = new EntryEditDialog(this); Controller::self()->addObserver(m_editDialog); m_toggleEntryEditor->setChecked(Config::showEditWidget()); slotToggleEntryEditor(); m_lockLayout->setActive(Config::lockLayout()); initConnections(); connect(ImageFactory::self(), &ImageFactory::imageLocationMismatch, this, &MainWindow::slotImageLocationMismatch); // Init DBUS NewStuff::Manager::self(); } void MainWindow::initStatusBar() { MARK; m_statusBar = new Tellico::StatusBar(this); setStatusBar(m_statusBar); } void MainWindow::initActions() { MARK; /************************************************* * File->New menu *************************************************/ QSignalMapper* collectionMapper = new QSignalMapper(this); void (QSignalMapper::* mappedInt)(int) = &QSignalMapper::mapped; connect(collectionMapper, mappedInt, this, &MainWindow::slotFileNew); KActionMenu* fileNewMenu = new KActionMenu(i18n("New"), this); fileNewMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); fileNewMenu->setToolTip(i18n("Create a new collection")); fileNewMenu->setDelayed(false); actionCollection()->addAction(QStringLiteral("file_new_collection"), fileNewMenu); QAction* action; void (QSignalMapper::* mapVoid)() = &QSignalMapper::map; #define COLL_ACTION(TYPE, NAME, TEXT, TIP, ICON) \ action = actionCollection()->addAction(QStringLiteral(NAME), collectionMapper, mapVoid); \ action->setText(TEXT); \ action->setToolTip(TIP); \ action->setIcon(QIcon(QStringLiteral(":/icons/" ICON))); \ fileNewMenu->addAction(action); \ collectionMapper->setMapping(action, Data::Collection::TYPE); COLL_ACTION(Book, "new_book_collection", i18n("New &Book Collection"), i18n("Create a new book collection"), "book"); COLL_ACTION(Bibtex, "new_bibtex_collection", i18n("New B&ibliography"), i18n("Create a new bibtex bibliography"), "bibtex"); COLL_ACTION(ComicBook, "new_comic_book_collection", i18n("New &Comic Book Collection"), i18n("Create a new comic book collection"), "comic"); COLL_ACTION(Video, "new_video_collection", i18n("New &Video Collection"), i18n("Create a new video collection"), "video"); COLL_ACTION(Album, "new_music_collection", i18n("New &Music Collection"), i18n("Create a new music collection"), "album"); COLL_ACTION(Coin, "new_coin_collection", i18n("New C&oin Collection"), i18n("Create a new coin collection"), "coin"); COLL_ACTION(Stamp, "new_stamp_collection", i18n("New &Stamp Collection"), i18n("Create a new stamp collection"), "stamp"); COLL_ACTION(Card, "new_card_collection", i18n("New C&ard Collection"), i18n("Create a new trading card collection"), "card"); COLL_ACTION(Wine, "new_wine_collection", i18n("New &Wine Collection"), i18n("Create a new wine collection"), "wine"); COLL_ACTION(Game, "new_game_collection", i18n("New &Game Collection"), i18n("Create a new game collection"), "game"); COLL_ACTION(BoardGame, "new_boardgame_collection", i18n("New Boa&rd Game Collection"), i18n("Create a new board game collection"), "boardgame"); COLL_ACTION(File, "new_file_catalog", i18n("New &File Catalog"), i18n("Create a new file catalog"), "file"); action = actionCollection()->addAction(QStringLiteral("new_custom_collection"), collectionMapper, mapVoid); action->setText(i18n("New C&ustom Collection")); action->setToolTip(i18n("Create a new custom collection")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); fileNewMenu->addAction(action); collectionMapper->setMapping(action, Data::Collection::Base); #undef COLL_ACTION /************************************************* * File menu *************************************************/ action = KStandardAction::open(this, SLOT(slotFileOpen()), actionCollection()); action->setToolTip(i18n("Open an existing document")); m_fileOpenRecent = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(const QUrl&)), actionCollection()); m_fileOpenRecent->setToolTip(i18n("Open a recently used file")); m_fileSave = KStandardAction::save(this, SLOT(slotFileSave()), actionCollection()); m_fileSave->setToolTip(i18n("Save the document")); action = KStandardAction::saveAs(this, SLOT(slotFileSaveAs()), actionCollection()); action->setToolTip(i18n("Save the document as a different file...")); action = KStandardAction::print(this, SLOT(slotFilePrint()), actionCollection()); { KHTMLPart w; // KHTMLPart printing was broken in KDE until KHTML 5.16 const QString version = w.componentData().version(); const uint major = version.section(QLatin1Char('.'), 0, 0).toUInt(); const uint minor = version.section(QLatin1Char('.'), 1, 1).toUInt(); if(major == 5 && minor < 16) { myWarning() << "Printing is broken for KDE Frameworks < 5.16. Please upgrade"; action->setEnabled(false); } } action->setToolTip(i18n("Print the contents of the document...")); action = KStandardAction::quit(this, SLOT(slotFileQuit()), actionCollection()); action->setToolTip(i18n("Quit the application")); /**************** Import Menu ***************************/ QSignalMapper* importMapper = new QSignalMapper(this); connect(importMapper, mappedInt, this, &MainWindow::slotFileImport); KActionMenu* importMenu = new KActionMenu(i18n("&Import"), this); importMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); importMenu->setToolTip(i18n("Import the collection data from other formats")); importMenu->setDelayed(false); actionCollection()->addAction(QStringLiteral("file_import"), importMenu); #define IMPORT_ACTION(TYPE, NAME, TEXT, TIP, ICON) \ action = actionCollection()->addAction(QStringLiteral(NAME), importMapper, mapVoid); \ action->setText(TEXT); \ action->setToolTip(TIP); \ action->setIcon(ICON); \ importMenu->addAction(action); \ importMapper->setMapping(action, TYPE); IMPORT_ACTION(Import::TellicoXML, "file_import_tellico", i18n("Import Tellico Data..."), i18n("Import another Tellico data file"), QIcon::fromTheme(QStringLiteral("tellico"), QIcon(QLatin1String(":/icons/tellico")))); IMPORT_ACTION(Import::CSV, "file_import_csv", i18n("Import CSV Data..."), i18n("Import a CSV file"), mimeIcon("text/csv", "text/x-csv")); IMPORT_ACTION(Import::MODS, "file_import_mods", i18n("Import MODS Data..."), i18n("Import a MODS data file"), mimeIcon("text/xml")); IMPORT_ACTION(Import::Alexandria, "file_import_alexandria", i18n("Import Alexandria Data..."), i18n("Import data from the Alexandria book collection manager"), QIcon::fromTheme(QStringLiteral("alexandria"), QIcon(QLatin1String(":/icons/alexandria")))); IMPORT_ACTION(Import::Delicious, "file_import_delicious", i18n("Import Delicious Library Data..."), i18n("Import data from Delicious Library"), QIcon::fromTheme(QStringLiteral("deliciouslibrary"), QIcon(QLatin1String(":/icons/deliciouslibrary")))); IMPORT_ACTION(Import::Referencer, "file_import_referencer", i18n("Import Referencer Data..."), i18n("Import data from Referencer"), QIcon::fromTheme(QStringLiteral("referencer"), QIcon(QLatin1String(":/icons/referencer")))); IMPORT_ACTION(Import::Bibtex, "file_import_bibtex", i18n("Import Bibtex Data..."), i18n("Import a bibtex bibliography file"), mimeIcon("text/x-bibtex")); IMPORT_ACTION(Import::Bibtexml, "file_import_bibtexml", i18n("Import Bibtexml Data..."), i18n("Import a Bibtexml bibliography file"), mimeIcon("text/xml")); IMPORT_ACTION(Import::RIS, "file_import_ris", i18n("Import RIS Data..."), i18n("Import an RIS reference file"), QIcon::fromTheme(QStringLiteral(":/icons/cite"))); IMPORT_ACTION(Import::Goodreads, "file_import_goodreads", i18n("Import Goodreads Collection..."), i18n("Import a collection from Goodreads.com"), QIcon::fromTheme(QStringLiteral(":/icons/goodreads"))); IMPORT_ACTION(Import::PDF, "file_import_pdf", i18n("Import PDF File..."), i18n("Import a PDF file"), mimeIcon("application/pdf")); IMPORT_ACTION(Import::AudioFile, "file_import_audiofile", i18n("Import Audio File Metadata..."), i18n("Import meta-data from audio files"), mimeIcon("audio/mp3", "audio/x-mp3")); #ifndef HAVE_TAGLIB action->setEnabled(false); #endif IMPORT_ACTION(Import::FreeDB, "file_import_freedb", i18n("Import Audio CD Data..."), i18n("Import audio CD information"), mimeIcon("media/audiocd", "application/x-cda")); #if !defined (HAVE_KCDDB) && !defined (HAVE_KF5KCDDB) action->setEnabled(false); #endif IMPORT_ACTION(Import::GCstar, "file_import_gcstar", i18n("Import GCstar Data..."), i18n("Import a GCstar data file"), QIcon::fromTheme(QStringLiteral("gcstar"), QIcon(QLatin1String(":/icons/gcstar")))); IMPORT_ACTION(Import::Griffith, "file_import_griffith", i18n("Import Griffith Data..."), i18n("Import a Griffith database"), QIcon::fromTheme(QStringLiteral("griffith"), QIcon(QLatin1String(":/icons/griffith")))); IMPORT_ACTION(Import::AMC, "file_import_amc", i18n("Import Ant Movie Catalog Data..."), i18n("Import an Ant Movie Catalog data file"), QIcon::fromTheme(QStringLiteral("amc"), QIcon(QLatin1String(":/icons/amc")))); IMPORT_ACTION(Import::BoardGameGeek, "file_import_boardgamegeek", i18n("Import BoardGameGeek Collection..."), i18n("Import a collection from BoardGameGeek.com"), QIcon(QLatin1String(":/icons/boardgamegeek"))); IMPORT_ACTION(Import::VinoXML, "file_import_vinoxml", i18n("Import VinoXML..."), i18n("Import VinoXML data"), QIcon(QLatin1String(":/icons/vinoxml"))); IMPORT_ACTION(Import::FileListing, "file_import_filelisting", i18n("Import File Listing..."), i18n("Import information about files in a folder"), mimeIcon("inode/directory")); IMPORT_ACTION(Import::XSLT, "file_import_xslt", i18n("Import XSL Transform..."), i18n("Import using an XSL Transform"), mimeIcon("application/xslt+xml", "text/x-xslt")); #undef IMPORT_ACTION /**************** Export Menu ***************************/ QSignalMapper* exportMapper = new QSignalMapper(this); connect(exportMapper, mappedInt, this, &MainWindow::slotFileExport); KActionMenu* exportMenu = new KActionMenu(i18n("&Export"), this); exportMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); exportMenu->setToolTip(i18n("Export the collection data to other formats")); exportMenu->setDelayed(false); actionCollection()->addAction(QStringLiteral("file_export"), exportMenu); #define EXPORT_ACTION(TYPE, NAME, TEXT, TIP, ICON) \ action = actionCollection()->addAction(QStringLiteral(NAME), exportMapper, mapVoid); \ action->setText(TEXT); \ action->setToolTip(TIP); \ action->setIcon(ICON); \ exportMenu->addAction(action); \ exportMapper->setMapping(action, TYPE); EXPORT_ACTION(Export::TellicoXML, "file_export_xml", i18n("Export to XML..."), i18n("Export to a Tellico XML file"), QIcon::fromTheme(QStringLiteral("tellico"), QIcon(QStringLiteral(":/icons/tellico")))); EXPORT_ACTION(Export::TellicoZip, "file_export_zip", i18n("Export to Zip..."), i18n("Export to a Tellico Zip file"), QIcon::fromTheme(QStringLiteral("tellico"), QIcon(QStringLiteral(":/icons/tellico")))); EXPORT_ACTION(Export::HTML, "file_export_html", i18n("Export to HTML..."), i18n("Export to an HTML file"), mimeIcon("text/html")); EXPORT_ACTION(Export::CSV, "file_export_csv", i18n("Export to CSV..."), i18n("Export to a comma-separated values file"), mimeIcon("text/csv", "text/x-csv")); EXPORT_ACTION(Export::Alexandria, "file_export_alexandria", i18n("Export to Alexandria..."), i18n("Export to an Alexandria library"), QIcon::fromTheme(QStringLiteral("alexandria"), QIcon(QStringLiteral(":/icons/alexandria")))); EXPORT_ACTION(Export::Bibtex, "file_export_bibtex", i18n("Export to Bibtex..."), i18n("Export to a bibtex file"), mimeIcon("text/x-bibtex")); EXPORT_ACTION(Export::Bibtexml, "file_export_bibtexml", i18n("Export to Bibtexml..."), i18n("Export to a Bibtexml file"), mimeIcon("text/xml")); EXPORT_ACTION(Export::ONIX, "file_export_onix", i18n("Export to ONIX..."), i18n("Export to an ONIX file"), mimeIcon("text/xml")); EXPORT_ACTION(Export::GCstar, "file_export_gcstar", i18n("Export to GCstar..."), i18n("Export to a GCstar data file"), QIcon::fromTheme(QStringLiteral("gcstar"), QIcon(QStringLiteral(":/icons/gcstar")))); EXPORT_ACTION(Export::XSLT, "file_export_xslt", i18n("Export XSL Transform..."), i18n("Export using an XSL Transform"), mimeIcon("application/xslt+xml", "text/x-xslt")); #undef EXPORT_ACTION /************************************************* * Edit menu *************************************************/ KStandardAction::undo(Kernel::self()->commandHistory(), SLOT(undo()), actionCollection()); KStandardAction::redo(Kernel::self()->commandHistory(), SLOT(undo()), actionCollection()); action = KStandardAction::cut(this, SLOT(slotEditCut()), actionCollection()); action->setToolTip(i18n("Cut the selected text and puts it in the clipboard")); action = KStandardAction::copy(this, SLOT(slotEditCopy()), actionCollection()); action->setToolTip(i18n("Copy the selected text to the clipboard")); action = KStandardAction::paste(this, SLOT(slotEditPaste()), actionCollection()); action->setToolTip(i18n("Paste the clipboard contents")); action = KStandardAction::selectAll(this, SLOT(slotEditSelectAll()), actionCollection()); action->setToolTip(i18n("Select all the entries in the collection")); action = KStandardAction::deselect(this, SLOT(slotEditDeselect()), actionCollection()); action->setToolTip(i18n("Deselect all the entries in the collection")); action = actionCollection()->addAction(QStringLiteral("edit_search_internet"), this, SLOT(slotShowFetchDialog())); action->setText(i18n("Internet Search...")); action->setIconText(i18n("Search")); // find a better word for this? action->setIcon(QIcon::fromTheme(QStringLiteral("tools-wizard"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_I); action->setToolTip(i18n("Search the internet...")); action = actionCollection()->addAction(QStringLiteral("filter_dialog"), this, SLOT(slotShowFilterDialog())); action->setText(i18n("Advanced &Filter...")); action->setIconText(i18n("Filter")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-filter"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_J); action->setToolTip(i18n("Filter the collection")); /************************************************* * Collection menu *************************************************/ m_newEntry = actionCollection()->addAction(QStringLiteral("coll_new_entry"), this, SLOT(slotNewEntry())); m_newEntry->setText(i18n("&New Entry...")); m_newEntry->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); m_newEntry->setIconText(i18n("New")); actionCollection()->setDefaultShortcut(m_newEntry, Qt::CTRL + Qt::Key_N); m_newEntry->setToolTip(i18n("Create a new entry")); m_editEntry = actionCollection()->addAction(QStringLiteral("coll_edit_entry"), this, SLOT(slotShowEntryEditor())); m_editEntry->setText(i18n("&Edit Entry...")); m_editEntry->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); actionCollection()->setDefaultShortcut(m_editEntry, Qt::CTRL + Qt::Key_E); m_editEntry->setToolTip(i18n("Edit the selected entries")); m_copyEntry = actionCollection()->addAction(QStringLiteral("coll_copy_entry"), Controller::self(), SLOT(slotCopySelectedEntries())); m_copyEntry->setText(i18n("D&uplicate Entry")); m_copyEntry->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); actionCollection()->setDefaultShortcut(m_copyEntry, Qt::CTRL + Qt::Key_Y); m_copyEntry->setToolTip(i18n("Copy the selected entries")); m_deleteEntry = actionCollection()->addAction(QStringLiteral("coll_delete_entry"), Controller::self(), SLOT(slotDeleteSelectedEntries())); m_deleteEntry->setText(i18n("&Delete Entry")); m_deleteEntry->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); actionCollection()->setDefaultShortcut(m_deleteEntry, Qt::CTRL + Qt::Key_D); m_deleteEntry->setToolTip(i18n("Delete the selected entries")); m_mergeEntry = actionCollection()->addAction(QStringLiteral("coll_merge_entry"), Controller::self(), SLOT(slotMergeSelectedEntries())); m_mergeEntry->setText(i18n("&Merge Entries")); m_mergeEntry->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); // CTRL+G is ambiguous, pick another // actionCollection()->setDefaultShortcut(m_mergeEntry, Qt::CTRL + Qt::Key_G); m_mergeEntry->setToolTip(i18n("Merge the selected entries")); m_mergeEntry->setEnabled(false); // gets enabled when more than 1 entry is selected m_checkOutEntry = actionCollection()->addAction(QStringLiteral("coll_checkout"), Controller::self(), SLOT(slotCheckOut())); m_checkOutEntry->setText(i18n("Check-&out...")); m_checkOutEntry->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up-double"))); m_checkOutEntry->setToolTip(i18n("Check-out the selected items")); m_checkInEntry = actionCollection()->addAction(QStringLiteral("coll_checkin"), Controller::self(), SLOT(slotCheckIn())); m_checkInEntry->setText(i18n("Check-&in")); m_checkInEntry->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down-double"))); m_checkInEntry->setToolTip(i18n("Check-in the selected items")); action = actionCollection()->addAction(QStringLiteral("coll_rename_collection"), this, SLOT(slotRenameCollection())); action->setText(i18n("&Rename Collection...")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_R); action->setToolTip(i18n("Rename the collection")); action = actionCollection()->addAction(QStringLiteral("coll_fields"), this, SLOT(slotShowCollectionFieldsDialog())); action->setText(i18n("Collection &Fields...")); action->setIconText(i18n("Fields")); action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_U); action->setToolTip(i18n("Modify the collection fields")); action = actionCollection()->addAction(QStringLiteral("coll_reports"), this, SLOT(slotShowReportDialog())); action->setText(i18n("&Generate Reports...")); action->setIconText(i18n("Reports")); action->setIcon(QIcon::fromTheme(QStringLiteral("text-rdf"))); action->setToolTip(i18n("Generate collection reports")); action = actionCollection()->addAction(QStringLiteral("coll_convert_bibliography"), this, SLOT(slotConvertToBibliography())); action->setText(i18n("Convert to &Bibliography")); action->setIcon(QIcon(QLatin1String(":/icons/bibtex"))); action->setToolTip(i18n("Convert a book collection to a bibliography")); action = actionCollection()->addAction(QStringLiteral("coll_string_macros"), this, SLOT(slotShowStringMacroDialog())); action->setText(i18n("String &Macros...")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-list-text"))); action->setToolTip(i18n("Edit the bibtex string macros")); action = actionCollection()->addAction(QStringLiteral("coll_key_manager"), this, SLOT(slotShowBibtexKeyDialog())); action->setText(i18n("Check for Duplicate Keys...")); action->setIcon(mimeIcon("text/x-bibtex")); action->setToolTip(i18n("Check for duplicate citation keys")); QSignalMapper* citeMapper = new QSignalMapper(this); connect(citeMapper, mappedInt, this, &MainWindow::slotCiteEntry); action = actionCollection()->addAction(QStringLiteral("cite_clipboard"), citeMapper, mapVoid); action->setText(i18n("Copy Bibtex to Cli&pboard")); action->setToolTip(i18n("Copy bibtex citations to the clipboard")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste"))); citeMapper->setMapping(action, Cite::CiteClipboard); action = actionCollection()->addAction(QStringLiteral("cite_lyxpipe"), citeMapper, mapVoid); action->setText(i18n("Cite Entry in &LyX")); action->setToolTip(i18n("Cite the selected entries in LyX")); action->setIcon(QIcon::fromTheme(QStringLiteral("lyx"), QIcon(QLatin1String(":/icons/lyx")))); citeMapper->setMapping(action, Cite::CiteLyxpipe); m_updateMapper = new QSignalMapper(this); void (QSignalMapper::* mappedString)(const QString&) = &QSignalMapper::mapped; connect(m_updateMapper, mappedString, Controller::self(), &Controller::slotUpdateSelectedEntries); m_updateEntryMenu = new KActionMenu(i18n("&Update Entry"), this); m_updateEntryMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); m_updateEntryMenu->setIconText(i18nc("Update Entry", "Update")); m_updateEntryMenu->setDelayed(false); actionCollection()->addAction(QStringLiteral("coll_update_entry"), m_updateEntryMenu); m_updateAll = actionCollection()->addAction(QStringLiteral("update_entry_all"), m_updateMapper, mapVoid); m_updateAll->setText(i18n("All Sources")); m_updateAll->setToolTip(i18n("Update entry data from all available sources")); m_updateMapper->setMapping(m_updateAll, QStringLiteral("_all")); /************************************************* * Settings menu *************************************************/ setStandardToolBarMenuEnabled(true); createStandardStatusBarAction(); m_lockLayout = new KDualAction(this); connect(m_lockLayout, &KDualAction::activeChanged, this, &MainWindow::slotToggleLayoutLock); m_lockLayout->setActiveText(i18n("Unlock Layout")); m_lockLayout->setActiveToolTip(i18n("Unlock the window's layout")); m_lockLayout->setActiveIcon(QIcon::fromTheme(QStringLiteral("object-unlocked"))); m_lockLayout->setInactiveText(i18n("Lock Layout")); m_lockLayout->setInactiveToolTip(i18n("Lock the window's layout")); m_lockLayout->setInactiveIcon(QIcon::fromTheme(QStringLiteral("object-locked"))); actionCollection()->addAction(QStringLiteral("lock_layout"), m_lockLayout); action = actionCollection()->addAction(QStringLiteral("reset_layout"), this, SLOT(slotResetLayout())); action->setText(i18n("Reset Layout")); action->setToolTip(i18n("Reset the window's layout")); action->setIcon(QIcon::fromTheme(QStringLiteral("resetview"))); m_toggleEntryEditor = new KToggleAction(i18n("Entry &Editor"), this); connect(m_toggleEntryEditor, &QAction::triggered, this, &MainWindow::slotToggleEntryEditor); m_toggleEntryEditor->setToolTip(i18n("Enable/disable the editor")); actionCollection()->addAction(QStringLiteral("toggle_edit_widget"), m_toggleEntryEditor); KStandardAction::preferences(this, SLOT(slotShowConfigDialog()), actionCollection()); /************************************************* * Help menu *************************************************/ KStandardAction::tipOfDay(this, SLOT(slotShowTipOfDay()), actionCollection()); /************************************************* * Short cuts *************************************************/ KStandardAction::fullScreen(this, SLOT(slotToggleFullScreen()), this, actionCollection()); KStandardAction::showMenubar(this, SLOT(slotToggleMenuBarVisibility()), actionCollection()); /************************************************* * Collection Toolbar *************************************************/ action = actionCollection()->addAction(QStringLiteral("change_entry_grouping_accel"), this, SLOT(slotGroupLabelActivated())); action->setText(i18n("Change Grouping")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_G); m_entryGrouping = new KSelectAction(i18n("&Group Selection"), this); m_entryGrouping->setToolTip(i18n("Change the grouping of the collection")); void (KSelectAction::* triggeredInt)(int) = &KSelectAction::triggered; connect(m_entryGrouping, triggeredInt, this, &MainWindow::slotChangeGrouping); actionCollection()->addAction(QStringLiteral("change_entry_grouping"), m_entryGrouping); action = actionCollection()->addAction(QStringLiteral("quick_filter_accel"), this, SLOT(slotFilterLabelActivated())); action->setText(i18n("Filter")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_F); m_quickFilter = new GUI::LineEdit(this); m_quickFilter->setPlaceholderText(i18n("Filter here...")); // same text as kdepim and amarok m_quickFilter->setClearButtonEnabled(true); // same as Dolphin text edit m_quickFilter->setMinimumWidth(150); m_quickFilter->setMaximumWidth(300); // want to update every time the filter text changes connect(m_quickFilter, &QLineEdit::textChanged, this, &MainWindow::slotQueueFilter); connect(m_quickFilter, &KLineEdit::clearButtonClicked, this, &MainWindow::slotClearFilter); m_quickFilter->installEventFilter(this); // intercept keyEvents QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(m_quickFilter); widgetAction->setText(i18n("Filter")); widgetAction->setToolTip(i18n("Filter the collection")); widgetAction->setProperty("isShortcutConfigurable", false); actionCollection()->addAction(QStringLiteral("quick_filter"), widgetAction); // final GUI setup is in initView() } #undef mimeIcon void MainWindow::initDocument() { MARK; Data::Document* doc = Data::Document::self(); Kernel::self()->resetHistory(); KConfigGroup config(KSharedConfig::openConfig(), "General Options"); doc->setLoadAllImages(config.readEntry("Load All Images", false)); // allow status messages from the document connect(doc, &Data::Document::signalStatusMsg, this, &MainWindow::slotStatusMsg); // do stuff that changes when the doc is modified connect(doc, &Data::Document::signalModified, this, &MainWindow::slotEnableModifiedActions); connect(doc, &Data::Document::signalCollectionAdded, Controller::self(), &Controller::slotCollectionAdded); connect(doc, &Data::Document::signalCollectionDeleted, Controller::self(), &Controller::slotCollectionDeleted); connect(Kernel::self()->commandHistory(), &QUndoStack::cleanChanged, doc, &Data::Document::slotSetClean); } void MainWindow::initView() { MARK; // initialize the image factory before the entry models are created ImageFactory::init(); m_entryView = new EntryView(this); connect(m_entryView, &EntryView::signalAction, this, &MainWindow::slotURLAction); m_entryView->view()->setWhatsThis(i18n("The Entry View shows a formatted view of the entry's contents.")); // trick to make sure the group views always extend along the entire left or right side // using QMainWindow::setCorner does not seem to work // https://wiki.qt.io/Technical_FAQ#Is_it_possible_for_either_the_left_or_right_dock_areas_to_have_full_height_of_their_side_rather_than_having_the_bottom_take_the_full_width.3F m_dummyWindow = new QMainWindow(this); m_dummyWindow->setCentralWidget(m_entryView->view()); m_dummyWindow->setWindowFlags(Qt::Widget); setCentralWidget(m_dummyWindow); m_collectionViewDock = new GUI::DockWidget(i18n("Collection View"), m_dummyWindow); m_collectionViewDock->setObjectName(QStringLiteral("collection_dock")); m_viewStack = new ViewStack(this); m_detailedView = m_viewStack->listView(); Controller::self()->addObserver(m_detailedView); m_detailedView->setWhatsThis(i18n("The Column View shows the value of multiple fields " "for each entry.")); connect(Data::Document::self(), &Data::Document::signalCollectionImagesLoaded, m_detailedView, &DetailedListView::slotRefreshImages); m_iconView = m_viewStack->iconView(); EntryIconModel* iconModel = new EntryIconModel(m_iconView); iconModel->setSourceModel(m_detailedView->model()); m_iconView->setModel(iconModel); Controller::self()->addObserver(m_iconView); m_iconView->setWhatsThis(i18n("The Icon View shows each entry in the collection or group using " "an icon, which may be an image in the entry.")); m_collectionViewDock->setWidget(m_viewStack); m_dummyWindow->addDockWidget(Qt::TopDockWidgetArea, m_collectionViewDock); actionCollection()->addAction(QStringLiteral("toggle_column_widget"), m_collectionViewDock->toggleViewAction()); m_groupViewDock = new GUI::DockWidget(i18n("Group View"), this); m_groupViewDock->setObjectName(QStringLiteral("group_dock")); m_groupViewDock->setAllowedAreas(Qt::DockWidgetAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea)); m_viewTabs = new GUI::TabWidget(this); m_viewTabs->setTabBarHidden(true); m_viewTabs->setDocumentMode(true); m_groupView = new GroupView(m_viewTabs); Controller::self()->addObserver(m_groupView); m_viewTabs->addTab(m_groupView, QIcon::fromTheme(QStringLiteral("folder")), i18n("Groups")); m_groupView->setWhatsThis(i18n("The Group View sorts the entries into groupings " "based on a selected field.")); m_groupViewDock->setWidget(m_viewTabs); addDockWidget(Qt::LeftDockWidgetArea, m_groupViewDock); actionCollection()->addAction(QStringLiteral("toggle_group_widget"), m_groupViewDock->toggleViewAction()); EntrySelectionModel* proxySelect = new EntrySelectionModel(m_iconView->model(), m_detailedView->selectionModel(), this); m_iconView->setSelectionModel(proxySelect); setMinimumWidth(MAIN_WINDOW_MIN_WIDTH); // setting up GUI now rather than in initActions setupGUI(Keys | ToolBar); createGUI(); } void MainWindow::initConnections() { // have to toggle the menu item if the dialog gets closed connect(m_editDialog, &QDialog::finished, this, &MainWindow::slotEditDialogFinished); EntrySelectionModel* proxySelect = static_cast(m_iconView->selectionModel()); connect(proxySelect, &EntrySelectionModel::entriesSelected, Controller::self(), &Controller::slotUpdateSelection); connect(proxySelect, &EntrySelectionModel::entriesSelected, m_editDialog, &EntryEditDialog::setContents); connect(proxySelect, &EntrySelectionModel::entriesSelected, m_entryView, &EntryView::showEntries); // let the group view call filters, too connect(m_groupView, &GroupView::signalUpdateFilter, this, &MainWindow::slotUpdateFilter); // use the EntrySelectionModel as a proxy so when entries get selected in the group view // the edit dialog and entry view are updated proxySelect->addSelectionProxy(m_groupView->selectionModel()); } void MainWindow::initFileOpen(bool nofile_) { MARK; slotInit(); // check to see if most recent file should be opened bool happyStart = false; if(!nofile_ && Config::reopenLastFile()) { // Config::lastOpenFile() is the full URL, protocol included QUrl lastFile(Config::lastOpenFile()); // empty string is actually ok, it gets handled if(!lastFile.isEmpty() && lastFile.isValid()) { slotFileOpen(lastFile); happyStart = true; } } if(!happyStart) { // the document is created with an initial book collection, continue with that Controller::self()->slotCollectionAdded(Data::Document::self()->collection()); m_fileSave->setEnabled(false); slotEnableOpenedActions(); slotEnableModifiedActions(false); slotEntryCount(); // tell the entry views and models that there are no images to load m_detailedView->slotRefreshImages(); } // show welcome text, even when opening an existing collection const int type = Kernel::self()->collectionType(); QString welcomeFile = DataFileRegistry::self()->locate(QStringLiteral("welcome.html")); QString text = FileHandler::readTextFile(QUrl::fromLocalFile(welcomeFile)); text.replace(QLatin1String("$FGCOLOR$"), Config::templateTextColor(type).name()); text.replace(QLatin1String("$BGCOLOR$"), Config::templateBaseColor(type).name()); text.replace(QLatin1String("$COLOR1$"), Config::templateHighlightedTextColor(type).name()); text.replace(QLatin1String("$COLOR2$"), Config::templateHighlightedBaseColor(type).name()); text.replace(QLatin1String("$IMGDIR$"), QUrl::fromLocalFile(ImageFactory::tempDir()).url()); text.replace(QLatin1String("$BANNER$"), i18n("Welcome to the Tellico Collection Manager")); text.replace(QLatin1String("$WELCOMETEXT$"), i18n("

Tellico is a tool for managing collections of books, " "videos, music, and whatever else you want to catalog.

" "

New entries can be added to your collection by " "entering data manually or by " "downloading data from " "various Internet sources.

")); m_entryView->showText(text); m_initialized = true; } // These are general options. // The options that can be changed in the "Configuration..." dialog // are taken care of by the ConfigDialog object. void MainWindow::saveOptions() { KConfigGroup config(KSharedConfig::openConfig(), "Main Window Options"); saveMainWindowSettings(config); config.writeEntry(QStringLiteral("Central Dock State"), m_dummyWindow->saveState()); Config::setShowEditWidget(m_toggleEntryEditor->isChecked()); // check any single dock widget, they all get locked together Config::setLockLayout(m_groupViewDock->isLocked()); KConfigGroup filesConfig(KSharedConfig::openConfig(), "Recent Files"); m_fileOpenRecent->saveEntries(filesConfig); if(!isNewDocument()) { Config::setLastOpenFile(Data::Document::self()->URL().url()); } Config::setViewWidget(m_viewStack->currentWidget()); // historical reasons // sorting by count was faked by sorting by phantom second column const int sortColumn = m_groupView->sortRole() == RowCountRole ? 1 : 0; Config::setGroupViewSortColumn(sortColumn); // ok to use SortColumn key, save semantics Config::setGroupViewSortAscending(m_groupView->sortOrder() == Qt::AscendingOrder); if(m_loanView) { const int sortColumn = m_loanView->sortRole() == RowCountRole ? 1 : 0; Config::setLoanViewSortAscending(sortColumn); // ok to use SortColumn key, save semantics Config::setLoanViewSortAscending(m_loanView->sortOrder() == Qt::AscendingOrder); } if(m_filterView) { const int sortColumn = m_filterView->sortRole() == RowCountRole ? 1 : 0; Config::setFilterViewSortAscending(sortColumn); // ok to use SortColumn key, save semantics Config::setFilterViewSortAscending(m_filterView->sortOrder() == Qt::AscendingOrder); } // this is used in the EntryEditDialog constructor, too KConfigGroup editDialogConfig(KSharedConfig::openConfig(), "Edit Dialog Options"); KWindowConfig::saveWindowSize(m_editDialog->windowHandle(), editDialogConfig); saveCollectionOptions(Data::Document::self()->collection()); Config::self()->save(); } void MainWindow::readCollectionOptions(Tellico::Data::CollPtr coll_) { if(!coll_) { myDebug() << "Bad, no collection in MainWindow::readCollectionOptions()"; return; } const QString configGroup = QStringLiteral("Options - %1").arg(CollectionFactory::typeName(coll_)); KConfigGroup group(KSharedConfig::openConfig(), configGroup); QString defaultGroup = coll_->defaultGroupField(); QString entryGroup; if(coll_->type() != Data::Collection::Base) { entryGroup = group.readEntry("Group By", defaultGroup); } else { QUrl url = Kernel::self()->URL(); for(int i = 0; i < Config::maxCustomURLSettings(); ++i) { QUrl u(group.readEntry(QStringLiteral("URL_%1").arg(i))); if(url == u) { entryGroup = group.readEntry(QStringLiteral("Group By_%1").arg(i), defaultGroup); break; } } // fall back to old setting if(entryGroup.isEmpty()) { entryGroup = group.readEntry("Group By", defaultGroup); } } if(entryGroup.isEmpty() || (!coll_->entryGroups().contains(entryGroup) && entryGroup != Data::Collection::s_peopleGroupName)) { entryGroup = defaultGroup; } m_groupView->setGroupField(entryGroup); QString entryXSLTFile = Config::templateName(coll_->type()); if(entryXSLTFile.isEmpty()) { entryXSLTFile = QStringLiteral("Fancy"); // should never happen, but just in case } m_entryView->setXSLTFile(entryXSLTFile + QLatin1String(".xsl")); // make sure the right combo element is selected slotUpdateCollectionToolBar(coll_); } void MainWindow::saveCollectionOptions(Tellico::Data::CollPtr coll_) { // don't save initial collection options, or empty collections if(!coll_ || coll_->entryCount() == 0 || isNewDocument()) { return; } int configIndex = -1; QString configGroup = QStringLiteral("Options - %1").arg(CollectionFactory::typeName(coll_)); KConfigGroup config(KSharedConfig::openConfig(), configGroup); QString groupName; if(m_entryGrouping->currentItem() > -1 && static_cast(coll_->entryGroups().count()) > m_entryGrouping->currentItem()) { if(m_entryGrouping->currentText() == (QLatin1Char('<') + i18n("People") + QLatin1Char('>'))) { groupName = Data::Collection::s_peopleGroupName; } else { groupName = Kernel::self()->fieldNameByTitle(m_entryGrouping->currentText()); } if(coll_->type() != Data::Collection::Base) { config.writeEntry("Group By", groupName); } } if(coll_->type() == Data::Collection::Base) { // all of this is to have custom settings on a per file basis QUrl url = Kernel::self()->URL(); QList urls = QList() << url; QStringList groupBys = QStringList() << groupName; for(int i = 0; i < Config::maxCustomURLSettings(); ++i) { QUrl u = config.readEntry(QStringLiteral("URL_%1").arg(i), QUrl()); QString g = config.readEntry(QStringLiteral("Group By_%1").arg(i), QString()); if(!u.isEmpty() && url != u) { urls.append(u); groupBys.append(g); } else if(!u.isEmpty()) { configIndex = i; } } int limit = qMin(urls.count(), Config::maxCustomURLSettings()); for(int i = 0; i < limit; ++i) { config.writeEntry(QStringLiteral("URL_%1").arg(i), urls[i].url()); config.writeEntry(QStringLiteral("Group By_%1").arg(i), groupBys[i]); } } m_detailedView->saveConfig(coll_, configIndex); } void MainWindow::readOptions() { KConfigGroup mainWindowConfig(KSharedConfig::openConfig(), "Main Window Options"); applyMainWindowSettings(mainWindowConfig); m_dummyWindow->restoreState(mainWindowConfig.readEntry(QStringLiteral("Central Dock State"), QByteArray())); m_viewStack->setCurrentWidget(Config::viewWidget()); m_iconView->setMaxAllowedIconWidth(Config::maxIconSize()); connect(toolBar(QStringLiteral("collectionToolBar")), &QToolBar::iconSizeChanged, this, &MainWindow::slotUpdateToolbarIcons); // initialize the recent file list KConfigGroup filesConfig(KSharedConfig::openConfig(), "Recent Files"); m_fileOpenRecent->loadEntries(filesConfig); // sort by count if column = 1 int sortRole = Config::groupViewSortColumn() == 0 ? static_cast(Qt::DisplayRole) : static_cast(RowCountRole); Qt::SortOrder sortOrder = Config::groupViewSortAscending() ? Qt::AscendingOrder : Qt::DescendingOrder; m_groupView->setSorting(sortOrder, sortRole); BibtexHandler::s_quoteStyle = Config::useBraces() ? BibtexHandler::BRACES : BibtexHandler::QUOTES; // Don't read any options for the edit dialog here, since it's not yet initialized. // Put them in init() } bool MainWindow::querySaveModified() { bool completed = true; if(Data::Document::self()->isModified()) { QString str = i18n("The current file has been modified.\n" "Do you want to save it?"); int want_save = KMessageBox::warningYesNoCancel(this, str, i18n("Unsaved Changes"), KStandardGuiItem::save(), KStandardGuiItem::discard()); switch(want_save) { case KMessageBox::Yes: completed = fileSave(); break; case KMessageBox::No: Data::Document::self()->setModified(false); completed = true; break; case KMessageBox::Cancel: default: completed = false; break; } } return completed; } bool MainWindow::queryClose() { // in case we're still loading the images, cancel that Data::Document::self()->cancelImageWriting(); const bool willClose = m_editDialog->queryModified() && querySaveModified(); if (willClose) { ImageFactory::clean(true); saveOptions(); } return willClose; } void MainWindow::slotFileNew(int type_) { slotStatusMsg(i18n("Creating new document...")); // close the fields dialog slotHideCollectionFieldsDialog(); if(m_editDialog->queryModified() && querySaveModified()) { // remove filter and loan tabs, they'll get re-added if needed if(m_filterView) { m_viewTabs->removeTab(m_viewTabs->indexOf(m_filterView)); Controller::self()->removeObserver(m_filterView); delete m_filterView; m_filterView = nullptr; } if(m_loanView) { m_viewTabs->removeTab(m_viewTabs->indexOf(m_loanView)); Controller::self()->removeObserver(m_loanView); delete m_loanView; m_loanView = nullptr; } m_viewTabs->setTabBarHidden(true); Data::Document::self()->newDocument(type_); Kernel::self()->resetHistory(); m_fileOpenRecent->setCurrentItem(-1); slotEnableOpenedActions(); slotEnableModifiedActions(false); m_newDocument = true; ImageFactory::clean(false); } StatusBar::self()->clearStatus(); } void MainWindow::slotFileOpen() { slotStatusMsg(i18n("Opening file...")); if(m_editDialog->queryModified() && querySaveModified()) { QString filter = i18n("Tellico Files") + QLatin1String(" (*.tc *.bc)"); filter += QLatin1String(";;"); filter += i18n("XML Files") + QLatin1String(" (*.xml)"); filter += QLatin1String(";;"); filter += i18n("All Files") + QLatin1String(" (*)"); // keyword 'open' QString fileClass; const QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///open")), fileClass); QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Open File"), startUrl, filter); if(!url.isEmpty() && url.isValid()) { slotFileOpen(url); if(url.isLocalFile()) { KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); } } } StatusBar::self()->clearStatus(); } void MainWindow::slotFileOpen(const QUrl& url_) { slotStatusMsg(i18n("Opening file...")); // close the fields dialog slotHideCollectionFieldsDialog(); // there seems to be a race condition at start between slotInit() and initFileOpen() // which means the edit dialog might not have been created yet if((!m_editDialog || m_editDialog->queryModified()) && querySaveModified()) { if(openURL(url_)) { m_fileOpenRecent->addUrl(url_); m_fileOpenRecent->setCurrentItem(-1); } } StatusBar::self()->clearStatus(); } void MainWindow::slotFileOpenRecent(const QUrl& url_) { slotStatusMsg(i18n("Opening file...")); // close the fields dialog slotHideCollectionFieldsDialog(); if(m_editDialog->queryModified() && querySaveModified()) { if(!openURL(url_)) { m_fileOpenRecent->removeUrl(url_); m_fileOpenRecent->setCurrentItem(-1); } } else { // the QAction shouldn't be checked now m_fileOpenRecent->setCurrentItem(-1); } StatusBar::self()->clearStatus(); } void MainWindow::openFile(const QString& file_) { QUrl url(file_); if(!url.isEmpty() && url.isValid()) { slotFileOpen(url); } } bool MainWindow::openURL(const QUrl& url_) { MARK; // try to open document GUI::CursorSaver cs(Qt::WaitCursor); bool success = Data::Document::self()->openDocument(url_); if(success) { Kernel::self()->resetHistory(); m_quickFilter->clear(); slotEnableOpenedActions(); m_newDocument = false; slotEnableModifiedActions(Data::Document::self()->isModified()); // doc might add some stuff } else if(!m_initialized) { // special case on startup when openURL() is called with a command line argument // and that URL can't be opened. The window still needs to be initialized // the doc object is created with an initial book collection, continue with that Controller::self()->slotCollectionAdded(Data::Document::self()->collection()); m_fileSave->setEnabled(false); slotEnableOpenedActions(); slotEnableModifiedActions(false); slotEntryCount(); } // slotFileOpen(URL) gets called when opening files on the command line // so go ahead and make sure m_initialized is set. m_initialized = true; // remove filter and loan tabs, they'll get re-added if needed if(m_filterView && m_filterView->isEmpty()) { m_viewTabs->removeTab(m_viewTabs->indexOf(m_filterView)); Controller::self()->removeObserver(m_filterView); delete m_filterView; m_filterView = nullptr; } if(m_loanView && m_loanView->isEmpty()) { m_viewTabs->removeTab(m_viewTabs->indexOf(m_loanView)); Controller::self()->removeObserver(m_loanView); delete m_loanView; m_loanView = nullptr; } Controller::self()->hideTabs(); // does conditional check return success; } void MainWindow::slotFileSave() { fileSave(); } bool MainWindow::fileSave() { if(!m_editDialog->queryModified()) { return false; } slotStatusMsg(i18n("Saving file...")); bool ret = true; if(isNewDocument()) { ret = fileSaveAs(); } else { // special check: if there are more than 200 images AND the "Write Images In File" config key // is set, then warn user that performance may suffer, and write result if(Config::imageLocation() == Config::ImagesInFile && Config::askWriteImagesInFile() && Data::Document::self()->imageCount() > MAX_IMAGES_WARN_PERFORMANCE) { QString msg = i18n("

You are saving a file with many images, which causes Tellico to " "slow down significantly. Do you want to save the images separately in " "Tellico's data directory to improve performance?

Your choice can " "always be changed in the configuration dialog.

"); KGuiItem yes(i18n("Save Images Separately")); KGuiItem no(i18n("Save Images in File")); int res = KMessageBox::warningYesNo(this, msg, QString() /* caption */, yes, no); if(res == KMessageBox::No) { Config::setImageLocation(Config::ImagesInAppDir); } Config::setAskWriteImagesInFile(false); } GUI::CursorSaver cs(Qt::WaitCursor); if(Data::Document::self()->saveDocument(Data::Document::self()->URL())) { Kernel::self()->resetHistory(); m_newDocument = false; updateCaption(false); m_fileSave->setEnabled(false); // TODO: call a method of the model instead of the view here m_detailedView->resetEntryStatus(); } else { ret = false; } } StatusBar::self()->clearStatus(); return ret; } void MainWindow::slotFileSaveAs() { fileSaveAs(); } bool MainWindow::fileSaveAs() { if(!m_editDialog->queryModified()) { return false; } slotStatusMsg(i18n("Saving file with a new filename...")); QString filter = i18n("Tellico Files") + QLatin1String(" (*.tc *.bc)"); filter += QLatin1String(";;"); filter += i18n("All Files") + QLatin1String(" (*)"); // keyword 'open' QString fileClass; const QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///open")), fileClass); const QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Save As"), startUrl, filter); if(url.isEmpty()) { StatusBar::self()->clearStatus(); return false; } if(url.isLocalFile()) { KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); } bool ret = true; if(url.isValid()) { GUI::CursorSaver cs(Qt::WaitCursor); m_savingImageLocationChange = true; // Overwriting an existing file was already confirmed in QFileDialog::getSaveFileUrl() if(Data::Document::self()->saveDocument(url, true /* force */)) { Kernel::self()->resetHistory(); KRecentDocument::add(url); m_fileOpenRecent->addUrl(url); updateCaption(false); m_newDocument = false; m_fileSave->setEnabled(false); m_detailedView->resetEntryStatus(); } else { ret = false; } m_savingImageLocationChange = false; } StatusBar::self()->clearStatus(); return ret; } void MainWindow::slotFilePrint() { slotStatusMsg(i18n("Printing...")); bool printGrouped = Config::printGrouped(); bool printHeaders = Config::printFieldHeaders(); int imageWidth = Config::maxImageWidth(); int imageHeight = Config::maxImageHeight(); // If the collection is being filtered, warn the user if(m_detailedView->filter()) { QString str = i18n("The collection is currently being filtered to show a limited subset of " "the entries. Only the visible entries will be printed. Continue?"); int ret = KMessageBox::warningContinueCancel(this, str, QString(), KStandardGuiItem::print(), KStandardGuiItem::cancel(), QStringLiteral("WarnPrintVisible")); if(ret == KMessageBox::Cancel) { StatusBar::self()->clearStatus(); return; } } GUI::CursorSaver cs(Qt::WaitCursor); Export::HTMLExporter exporter(Data::Document::self()->collection()); // only print visible entries exporter.setEntries(m_detailedView->visibleEntries()); exporter.setXSLTFile(QStringLiteral("tellico-printing.xsl")); exporter.setPrintHeaders(printHeaders); exporter.setPrintGrouped(printGrouped); exporter.setGroupBy(Controller::self()->expandedGroupBy()); if(!printGrouped) { // the sort titles are only used if the entries are not grouped exporter.setSortTitles(Controller::self()->sortTitles()); } exporter.setColumns(m_detailedView->visibleColumns()); exporter.setMaxImageSize(imageWidth, imageHeight); slotStatusMsg(i18n("Processing document...")); if(Config::printFormatted()) { exporter.setOptions(Export::ExportUTF8 | Export::ExportFormatted); } else { exporter.setOptions(Export::ExportUTF8); } QString html = exporter.text(); if(html.isEmpty()) { XSLTError(); StatusBar::self()->clearStatus(); return; } // don't have busy cursor when showing the print dialog cs.restore(); // myDebug() << html; slotStatusMsg(i18n("Printing...")); doPrint(html); StatusBar::self()->clearStatus(); } void MainWindow::slotFileQuit() { slotStatusMsg(i18n("Exiting...")); close(); // will call queryClose() StatusBar::self()->clearStatus(); } void MainWindow::slotEditCut() { activateEditSlot("cut()"); } void MainWindow::slotEditCopy() { activateEditSlot("copy()"); } void MainWindow::slotEditPaste() { activateEditSlot("paste()"); } void MainWindow::activateEditSlot(const char* slot_) { // the edit widget is the only one that copies, cuts, and pastes QWidget* w; if(m_editDialog->isVisible()) { w = m_editDialog->focusWidget(); } else { w = qApp->focusWidget(); } if(w && w->isVisible()) { const QMetaObject* meta = w->metaObject(); const int idx = meta->indexOfSlot(slot_); if(idx > -1) { //myDebug() << "MainWindow invoking" << meta->method(idx).signature(); meta->method(idx).invoke(w, Qt::DirectConnection); } } } void MainWindow::slotEditSelectAll() { m_detailedView->selectAllVisible(); } void MainWindow::slotEditDeselect() { Controller::self()->slotUpdateSelection(Data::EntryList()); } void MainWindow::slotToggleEntryEditor() { if(m_toggleEntryEditor->isChecked()) { m_editDialog->show(); } else { m_editDialog->hide(); } } void MainWindow::slotShowConfigDialog() { if(!m_configDlg) { m_configDlg = new ConfigDialog(this); connect(m_configDlg, &ConfigDialog::signalConfigChanged, this, &MainWindow::slotHandleConfigChange); connect(m_configDlg, &QDialog::finished, this, &MainWindow::slotHideConfigDialog); } else { KWindowSystem::activateWindow(m_configDlg->winId()); } m_configDlg->show(); } void MainWindow::slotHideConfigDialog() { if(m_configDlg) { m_configDlg->hide(); m_configDlg->deleteLater(); m_configDlg = nullptr; } } void MainWindow::slotShowTipOfDay(bool force_/*=true*/) { KTipDialog::showTip(this, QStringLiteral("tellico/tellico.tips"), force_); } void MainWindow::slotStatusMsg(const QString& text_) { m_statusBar->setStatus(text_); } void MainWindow::slotClearStatus() { StatusBar::self()->clearStatus(); } void MainWindow::slotEntryCount() { Data::CollPtr coll = Data::Document::self()->collection(); if(!coll) { return; } int count = coll->entryCount(); QString text = i18n("Total entries: %1", count); int selectCount = Controller::self()->selectedEntries().count(); int filterCount = m_detailedView->visibleItems(); // if more than one book is selected, add the number of selected books if(filterCount < count && selectCount > 1) { text += QLatin1Char(' '); text += i18n("(%1 filtered; %2 selected)", filterCount, selectCount); } else if(filterCount < count) { text += QLatin1Char(' '); text += i18n("(%1 filtered)", filterCount); } else if(selectCount > 1) { text += QLatin1Char(' '); text += i18n("(%1 selected)", selectCount); } m_statusBar->setCount(text); } void MainWindow::slotEnableOpenedActions() { slotUpdateToolbarIcons(); updateCollectionActions(); // close the filter dialog when a new collection is opened slotHideFilterDialog(); slotStringMacroDialogFinished(); } void MainWindow::slotEnableModifiedActions(bool modified_ /*= true*/) { updateCaption(modified_); updateCollectionActions(); m_fileSave->setEnabled(modified_); } void MainWindow::slotHandleConfigChange() { const int imageLocation = Config::imageLocation(); const bool autoCapitalize = Config::autoCapitalization(); const bool autoFormat = Config::autoFormat(); const QStringList articles = Config::articleList(); const QStringList nocaps = Config::noCapitalizationList(); const QStringList suffixes = Config::nameSuffixList(); const QStringList prefixes = Config::surnamePrefixList(); m_configDlg->saveConfiguration(); // only modified if there are entries and image location is changed if(imageLocation != Config::imageLocation() && !Data::Document::self()->isEmpty()) { slotImageLocationChanged(); } if(autoCapitalize != Config::autoCapitalization() || autoFormat != Config::autoFormat() || articles != Config::articleList() || nocaps != Config::noCapitalizationList() || suffixes != Config::nameSuffixList() || prefixes != Config::surnamePrefixList()) { // invalidate all groups Data::Document::self()->collection()->invalidateGroups(); // refreshing the title causes the group view to refresh Controller::self()->slotRefreshField(Data::Document::self()->collection()->fieldByName(QStringLiteral("title"))); } QString entryXSLTFile = Config::templateName(Kernel::self()->collectionType()); m_entryView->setXSLTFile(entryXSLTFile + QLatin1String(".xsl")); } void MainWindow::slotUpdateCollectionToolBar(Tellico::Data::CollPtr coll_) { if(!coll_) { myWarning() << "no collection pointer!"; return; } QString current = m_groupView->groupBy(); if(current.isEmpty() || !coll_->entryGroups().contains(current)) { current = coll_->defaultGroupField(); } const QStringList groups = coll_->entryGroups(); if(groups.isEmpty()) { m_entryGrouping->clear(); return; } QMap groupMap; // use a map so they get sorted foreach(const QString& groupName, groups) { // special case for people "pseudo-group" if(groupName == Data::Collection::s_peopleGroupName) { groupMap.insert(groupName, QLatin1Char('<') + i18n("People") + QLatin1Char('>')); } else { groupMap.insert(groupName, coll_->fieldTitleByName(groupName)); } } const QStringList titles = groupMap.values(); if(titles == m_entryGrouping->items()) { // no need to update anything return; } const QStringList names = groupMap.keys(); int index = names.indexOf(current); if(index == -1) { current = names[0]; index = 0; } m_entryGrouping->setItems(titles); m_entryGrouping->setCurrentItem(index); // in case the current grouping field get modified to be non-grouping... m_groupView->setGroupField(current); // don't call slotChangeGrouping() since it adds an undo item // TODO::I have no idea how to get the combobox to update its size // this is the hackiest of hacks, taken from KXmlGuiWindow::saveNewToolbarConfig() // the window flickers as toolbar resizes, unavoidable? // crashes if removeCLient//addClient is called here, need to do later in event loop QTimer::singleShot(0, this, &MainWindow::guiFactoryReset); } void MainWindow::slotChangeGrouping() { const QString title = m_entryGrouping->currentText(); QString groupName = Kernel::self()->fieldNameByTitle(title); if(groupName.isEmpty()) { if(title == (QLatin1Char('<') + i18n("People") + QLatin1Char('>'))) { groupName = Data::Collection::s_peopleGroupName; } else { groupName = Data::Document::self()->collection()->defaultGroupField(); } } m_groupView->setGroupField(groupName); m_viewTabs->setCurrentWidget(m_groupView); } void MainWindow::slotShowReportDialog() { if(!m_reportDlg) { m_reportDlg = new ReportDialog(this); connect(m_reportDlg, &QDialog::finished, this, &MainWindow::slotHideReportDialog); } else { KWindowSystem::activateWindow(m_reportDlg->winId()); } m_reportDlg->show(); } void MainWindow::slotHideReportDialog() { if(m_reportDlg) { m_reportDlg->hide(); m_reportDlg->deleteLater(); m_reportDlg = nullptr; } } void MainWindow::doPrint(const QString& html_) { KHTMLPart w; // KHTMLPart printing was broken in KDE until KHTML 5.16 // see https://git.reviewboard.kde.org/r/125681/ const QString version = w.componentData().version(); const uint major = version.section(QLatin1Char('.'), 0, 0).toUInt(); const uint minor = version.section(QLatin1Char('.'), 1, 1).toUInt(); if(major == 5 && minor < 16) { myWarning() << "Printing is broken for KDE Frameworks < 5.16. Please upgrade"; return; } w.setJScriptEnabled(false); w.setJavaEnabled(false); w.setMetaRefreshEnabled(false); w.setPluginsEnabled(false); w.begin(Data::Document::self()->URL()); w.write(html_); w.end(); // the problem with doing my own layout is that the text gets truncated, both at the // top and at the bottom. Even adding the overlap parameter, there were problems. // KHTMLView takes care of that with a truncatedAt() parameter, but that's hidden in // the khtml::render_root class. So for now, just use the KHTMLView::print() method. w.view()->print(); } void MainWindow::XSLTError() { QString str = i18n("Tellico encountered an error in XSLT processing.") + QLatin1Char('\n'); str += i18n("Please check your installation."); Kernel::self()->sorry(str); } void MainWindow::slotShowFilterDialog() { if(!m_filterDlg) { m_filterDlg = new FilterDialog(FilterDialog::CreateFilter, this); // allow saving m_quickFilter->setEnabled(false); connect(m_filterDlg, &FilterDialog::signalCollectionModified, Data::Document::self(), &Data::Document::slotSetModified); connect(m_filterDlg, &FilterDialog::signalUpdateFilter, this, &MainWindow::slotUpdateFilter); connect(m_filterDlg, &QDialog::finished, this, &MainWindow::slotHideFilterDialog); } else { KWindowSystem::activateWindow(m_filterDlg->winId()); } m_filterDlg->setFilter(m_detailedView->filter()); m_filterDlg->show(); } void MainWindow::slotHideFilterDialog() { // m_quickFilter->blockSignals(false); m_quickFilter->setEnabled(true); if(m_filterDlg) { m_filterDlg->hide(); m_filterDlg->deleteLater(); m_filterDlg = nullptr; } } void MainWindow::slotQueueFilter() { if(m_dontQueueFilter) { return; } m_queuedFilters++; QTimer::singleShot(200, this, &MainWindow::slotCheckFilterQueue); } void MainWindow::slotCheckFilterQueue() { m_queuedFilters--; if(m_queuedFilters > 0) { return; } setFilter(m_quickFilter->text()); } void MainWindow::slotUpdateFilter(FilterPtr filter_) { // Can't just block signals because clear button won't show then m_dontQueueFilter = true; m_quickFilter->setText(QStringLiteral(" ")); // To be able to clear custom filter Controller::self()->slotUpdateFilter(filter_); m_dontQueueFilter = false; } void MainWindow::setFilter(const QString& text_) { QString text = text_.trimmed(); FilterPtr filter; if(!text.isEmpty()) { filter = new Filter(Filter::MatchAll); QString fieldName; // empty field name means match on any field // if the text contains '=' assume it's a field name or title if(text.indexOf(QLatin1Char('=')) > -1) { fieldName = text.section(QLatin1Char('='), 0, 0).trimmed(); text = text.section(QLatin1Char('='), 1).trimmed(); // check that the field name might be a title if(!Data::Document::self()->collection()->hasField(fieldName)) { fieldName = Data::Document::self()->collection()->fieldNameByTitle(fieldName); } } // if the text contains any non-word characters, assume it's a regexp // but \W in qt is letter, number, or '_', I want to be a bit less strict QRegExp rx(QLatin1String("[^\\w\\s-']")); if(rx.indexIn(text) == -1) { // split by whitespace, and add rules for each word const QStringList tokens = text.split(QRegExp(QLatin1String("\\s"))); foreach(const QString& token, tokens) { // an empty field string means check every field filter->append(new FilterRule(fieldName, token, FilterRule::FuncContains)); } } else { // if it isn't valid, hold off on applying the filter QRegExp tx(text); if(!tx.isValid()) { text = QRegExp::escape(text); tx.setPattern(text); } if(!tx.isValid()) { myDebug() << "invalid regexp:" << text; return; } filter->append(new FilterRule(fieldName, text, FilterRule::FuncRegExp)); } // also want to update the line edit in case the filter was set by DBUS if(m_quickFilter->text() != text_) { m_quickFilter->setText(text_); } } // only update filter if one exists or did exist if(filter || m_detailedView->filter()) { Controller::self()->slotUpdateFilter(filter); } } void MainWindow::slotShowCollectionFieldsDialog() { if(!m_collFieldsDlg) { m_collFieldsDlg = new CollectionFieldsDialog(Data::Document::self()->collection(), this); connect(m_collFieldsDlg, &QDialog::finished, this, &MainWindow::slotHideCollectionFieldsDialog); } else { KWindowSystem::activateWindow(m_collFieldsDlg->winId()); } m_collFieldsDlg->show(); } void MainWindow::slotHideCollectionFieldsDialog() { if(m_collFieldsDlg) { m_collFieldsDlg->hide(); m_collFieldsDlg->deleteLater(); m_collFieldsDlg = nullptr; } } void MainWindow::slotFileImport(int format_) { slotStatusMsg(i18n("Importing data...")); m_quickFilter->clear(); Import::Format format = static_cast(format_); bool checkURL = true; QUrl url; switch(ImportDialog::importTarget(format)) { case Import::File: { QString fileClass; const QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///import")), fileClass); url = QFileDialog::getOpenFileUrl(this, i18n("Import File"), startUrl, ImportDialog::fileFilter(format)); KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); } break; case Import::Dir: // TODO: allow remote audiofile importing { const QString fileClass(QStringLiteral("ImportDir")); QString dirName = ImportDialog::startDir(format); if(dirName.isEmpty()) { dirName = KRecentDirs::dir(fileClass); } url = QUrl::fromLocalFile(QFileDialog::getExistingDirectory(this, i18n("Import Directory"), dirName)); KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); } break; case Import::None: default: checkURL = false; break; } if(checkURL) { bool ok = !url.isEmpty() && url.isValid() && QFile::exists(url.toLocalFile()); if(!ok) { StatusBar::self()->clearStatus(); return; } } importFile(format, QList() << url); StatusBar::self()->clearStatus(); } void MainWindow::slotFileExport(int format_) { slotStatusMsg(i18n("Exporting data...")); Export::Format format = static_cast(format_); ExportDialog dlg(format, Data::Document::self()->collection(), this); if(dlg.exec() == QDialog::Rejected) { StatusBar::self()->clearStatus(); return; } switch(ExportDialog::exportTarget(format)) { case Export::None: dlg.exportURL(); break; case Export::Dir: myDebug() << "ExportDir not implemented!"; break; case Export::File: { QString fileClass; const QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///export")), fileClass); QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Export As"), startUrl, dlg.fileFilter()); if(url.isEmpty()) { StatusBar::self()->clearStatus(); return; } if(url.isValid()) { if(url.isLocalFile()) { KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); } GUI::CursorSaver cs(Qt::WaitCursor); dlg.exportURL(url); } } break; } StatusBar::self()->clearStatus(); } void MainWindow::slotShowStringMacroDialog() { if(Data::Document::self()->collection()->type() != Data::Collection::Bibtex) { return; } if(!m_stringMacroDlg) { const Data::BibtexCollection* c = static_cast(Data::Document::self()->collection().data()); m_stringMacroDlg = new StringMapDialog(c->macroList(), this, false); m_stringMacroDlg->setWindowTitle(i18n("String Macros")); m_stringMacroDlg->setLabels(i18n("Macro"), i18n("String")); connect(m_stringMacroDlg, &QDialog::finished, this, &MainWindow::slotStringMacroDialogFinished); } else { KWindowSystem::activateWindow(m_stringMacroDlg->winId()); } m_stringMacroDlg->show(); } void MainWindow::slotStringMacroDialogFinished(int result_) { // no point in checking if collection is bibtex, as dialog would never have been created if(!m_stringMacroDlg) { return; } if(result_ == QDialog::Accepted) { static_cast(Data::Document::self()->collection().data())->setMacroList(m_stringMacroDlg->stringMap()); Data::Document::self()->setModified(true); } m_stringMacroDlg->hide(); m_stringMacroDlg->deleteLater(); m_stringMacroDlg = nullptr; } void MainWindow::slotShowBibtexKeyDialog() { if(Data::Document::self()->collection()->type() != Data::Collection::Bibtex) { return; } if(!m_bibtexKeyDlg) { m_bibtexKeyDlg = new BibtexKeyDialog(Data::Document::self()->collection(), this); connect(m_bibtexKeyDlg, &QDialog::finished, this, &MainWindow::slotHideBibtexKeyDialog); connect(m_bibtexKeyDlg, &BibtexKeyDialog::signalUpdateFilter, this, &MainWindow::slotUpdateFilter); } else { KWindowSystem::activateWindow(m_bibtexKeyDlg->winId()); } m_bibtexKeyDlg->show(); } void MainWindow::slotHideBibtexKeyDialog() { if(m_bibtexKeyDlg) { m_bibtexKeyDlg->deleteLater(); m_bibtexKeyDlg = nullptr; } } void MainWindow::slotNewEntry() { m_toggleEntryEditor->setChecked(true); slotToggleEntryEditor(); m_editDialog->slotHandleNew(); } void MainWindow::slotEditDialogFinished() { m_toggleEntryEditor->setChecked(false); } void MainWindow::slotShowEntryEditor() { m_toggleEntryEditor->setChecked(true); m_editDialog->show(); KWindowSystem::activateWindow(m_editDialog->winId()); } void MainWindow::slotConvertToBibliography() { // only book collections can be converted to bibtex Data::CollPtr coll = Data::Document::self()->collection(); if(!coll || coll->type() != Data::Collection::Book) { return; } GUI::CursorSaver cs; // need to make sure all images are transferred Data::Document::self()->loadAllImagesNow(); Data::CollPtr newColl = Data::BibtexCollection::convertBookCollection(coll); if(newColl) { m_newDocument = true; Kernel::self()->replaceCollection(newColl); m_fileOpenRecent->setCurrentItem(-1); slotUpdateToolbarIcons(); updateCollectionActions(); } else { myWarning() << "ERROR: no bibliography created!"; } } void MainWindow::slotCiteEntry(int action_) { StatusBar::self()->setStatus(i18n("Creating citations...")); Cite::ActionManager* man = Cite::ActionManager::self(); man->cite(static_cast(action_), Controller::self()->selectedEntries()); if(man->hasError()) { Kernel::self()->sorry(man->errorString()); } StatusBar::self()->clearStatus(); } void MainWindow::slotShowFetchDialog() { if(!m_fetchDlg) { m_fetchDlg = new FetchDialog(this); connect(m_fetchDlg, &QDialog::finished, this, &MainWindow::slotHideFetchDialog); connect(Controller::self(), &Controller::collectionAdded, m_fetchDlg, &FetchDialog::slotResetCollection); } else { KWindowSystem::activateWindow(m_fetchDlg->winId()); } m_fetchDlg->show(); } void MainWindow::slotHideFetchDialog() { if(m_fetchDlg) { m_fetchDlg->hide(); m_fetchDlg->deleteLater(); m_fetchDlg = nullptr; } } bool MainWindow::importFile(Tellico::Import::Format format_, const QUrl& url_, Tellico::Import::Action action_) { // try to open document GUI::CursorSaver cs(Qt::WaitCursor); bool failed = false; Data::CollPtr coll; if(!url_.isEmpty() && url_.isValid() && NetAccess::exists(url_, true, this)) { coll = ImportDialog::importURL(format_, url_); } else { Kernel::self()->sorry(i18n(errorLoad, url_.fileName())); failed = true; } if(!coll && !m_initialized) { // special case on startup when openURL() is called with a command line argument // and that URL can't be opened. The window still needs to be initialized // the doc object is created with an initial book collection, continue with that Controller::self()->slotCollectionAdded(Data::Document::self()->collection()); m_fileSave->setEnabled(false); slotEnableOpenedActions(); slotEnableModifiedActions(false); slotEntryCount(); m_fileOpenRecent->setCurrentItem(-1); m_initialized = true; failed = true; } else if(coll) { // this is rather dumb, but I'm too lazy to find the bug // if the document isn't initialized, then Tellico crashes // since Document::replaceCollection() ends up calling lots of stuff that isn't initialized if(!m_initialized) { Controller::self()->slotCollectionAdded(Data::Document::self()->collection()); m_initialized = true; } failed = !importCollection(coll, action_); } StatusBar::self()->clearStatus(); return !failed; // return true means success } bool MainWindow::exportCollection(Tellico::Export::Format format_, const QUrl& url_, bool filtered_) { if(!url_.isValid()) { myDebug() << "invalid URL:" << url_; return false; } GUI::CursorSaver cs; const Data::CollPtr coll = Data::Document::self()->collection(); if(!coll) { return false; } // only bibliographies can export to bibtex or bibtexml const bool isBibtex = (coll->type() == Data::Collection::Bibtex); if(!isBibtex && (format_ == Export::Bibtex || format_ == Export::Bibtexml)) { return false; } // only books and bibliographies can export to alexandria const bool isBook = (coll->type() == Data::Collection::Book); if(!isBibtex && !isBook && format_ == Export::Alexandria) { return false; } return ExportDialog::exportCollection(coll, filtered_ ? Controller::self()->visibleEntries() : coll->entries(), format_, url_); } bool MainWindow::showEntry(Data::ID id) { Data::EntryPtr entry = Data::Document::self()->collection()->entryById(id); if(entry) { m_entryView->showEntry(entry); } return entry; } void MainWindow::addFilterView() { if(m_filterView) { return; } m_filterView = new FilterView(m_viewTabs); Controller::self()->addObserver(m_filterView); m_viewTabs->insertTab(1, m_filterView, QIcon::fromTheme(QStringLiteral("view-filter")), i18n("Filters")); m_filterView->setWhatsThis(i18n("The Filter View shows the entries which meet certain " "filter rules.")); connect(m_filterView, &FilterView::signalUpdateFilter, this, &MainWindow::slotUpdateFilter); // use the EntrySelectionModel as a proxy so when entries get selected in the filter view // the edit dialog and entry view are updated // TODO: consider using KSelectionProxyModel static_cast(m_iconView->selectionModel())->addSelectionProxy(m_filterView->selectionModel()); // sort by count if column = 1 int sortRole = Config::filterViewSortColumn() == 0 ? static_cast(Qt::DisplayRole) : static_cast(RowCountRole); Qt::SortOrder sortOrder = Config::filterViewSortAscending() ? Qt::AscendingOrder : Qt::DescendingOrder; m_filterView->setSorting(sortOrder, sortRole); } void MainWindow::addLoanView() { if(m_loanView) { return; } m_loanView = new LoanView(m_viewTabs); Controller::self()->addObserver(m_loanView); m_viewTabs->insertTab(2, m_loanView, QIcon::fromTheme(QStringLiteral("kaddressbook")), i18n("Loans")); m_loanView->setWhatsThis(i18n("The Loan View shows a list of all the people who " "have borrowed items from your collection.")); // use the EntrySelectionModel as a proxy so when entries get selected in the loan view // the edit dialog and entry view are updated // TODO: consider using KSelectionProxyModel static_cast(m_iconView->selectionModel())->addSelectionProxy(m_loanView->selectionModel()); // sort by count if column = 1 int sortRole = Config::loanViewSortColumn() == 0 ? static_cast(Qt::DisplayRole) : static_cast(RowCountRole); Qt::SortOrder sortOrder = Config::loanViewSortAscending() ? Qt::AscendingOrder : Qt::DescendingOrder; m_loanView->setSorting(sortOrder, sortRole); } void MainWindow::updateCaption(bool modified_) { QString caption; if(Data::Document::self()->collection()) { caption = Data::Document::self()->collection()->title(); } if(!m_newDocument) { if(!caption.isEmpty()) { caption += QLatin1String(" - "); } QUrl u = Data::Document::self()->URL(); if(u.isLocalFile()) { // for new files, the filename is set to Untitled in Data::Document if(u.fileName() == i18n(Tellico::untitledFilename)) { caption += u.fileName(); } else { caption += u.path(); } } else { caption += u.toDisplayString(); } } setCaption(caption, modified_); } void MainWindow::slotUpdateToolbarIcons() { // first change the icon for the menu item if(Kernel::self()->collectionType() == Data::Collection::Base) { m_newEntry->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); } else { m_newEntry->setIcon(QIcon(QLatin1String(":/icons/") + Kernel::self()->collectionTypeName())); } } void MainWindow::slotGroupLabelActivated() { // need entry grouping combo id foreach(QWidget* widget, m_entryGrouping->associatedWidgets()) { if(::qobject_cast(widget)) { QWidget* container = m_entryGrouping->requestWidget(widget); QComboBox* combo = ::qobject_cast(container); //krazy:exclude=qclasses if(combo) { combo->showPopup(); break; } } } } void MainWindow::slotFilterLabelActivated() { m_quickFilter->setFocus(); m_quickFilter->selectAll(); } void MainWindow::slotClearFilter() { m_quickFilter->clear(); slotQueueFilter(); } void MainWindow::slotRenameCollection() { Kernel::self()->renameCollection(); } void MainWindow::slotImageLocationMismatch() { // TODO: having a single image location mismatch should not be reason to completely save the whole document QTimer::singleShot(0, this, &MainWindow::slotImageLocationChanged); } void MainWindow::slotImageLocationChanged() { if(m_savingImageLocationChange) { return; } m_savingImageLocationChange = true; Data::Document::self()->slotSetModified(); KMessageBox::information(this, QLatin1String("") + i18n("Some images are not saved in the configured location. The current file " "must be saved and the images will be transferred to the new location.") + QLatin1String("")); fileSave(); m_savingImageLocationChange = false; } void MainWindow::updateCollectionActions() { if(!Data::Document::self()->collection()) { return; } stateChanged(QStringLiteral("collection_reset")); Data::Collection::Type type = Data::Document::self()->collection()->type(); stateChanged(QLatin1String("is_") + CollectionFactory::typeName(type)); Controller::self()->updateActions(); // special case when there are no available data sources if(m_fetchActions.isEmpty() && m_updateAll) { m_updateAll->setEnabled(false); } } void MainWindow::updateEntrySources() { unplugActionList(QStringLiteral("update_entry_actions")); foreach(QAction* action, m_fetchActions) { foreach(QWidget* widget, action->associatedWidgets()) { widget->removeAction(action); } m_updateMapper->removeMappings(action); } qDeleteAll(m_fetchActions); m_fetchActions.clear(); Fetch::FetcherVec vec = Fetch::Manager::self()->fetchers(Kernel::self()->collectionType()); foreach(Fetch::Fetcher::Ptr fetcher, vec) { QAction* action = new QAction(Fetch::Manager::fetcherIcon(fetcher), fetcher->source(), actionCollection()); action->setToolTip(i18n("Update entry data from %1", fetcher->source())); void (QAction::* triggeredBool)(bool) = &QAction::triggered; void (QSignalMapper::* mapVoid)() = &QSignalMapper::map; connect(action, triggeredBool, m_updateMapper, mapVoid); m_updateMapper->setMapping(action, fetcher->source()); m_fetchActions.append(action); } plugActionList(QStringLiteral("update_entry_actions"), m_fetchActions); } void MainWindow::importFile(Tellico::Import::Format format_, const QList& urls_) { QList urls = urls_; // update as DropHandler and Importer classes are updated if(urls_.count() > 1 && format_ != Import::Bibtex && format_ != Import::RIS && format_ != Import::CIW && format_ != Import::PDF) { QUrl u = urls_.front(); QString url = u.isLocalFile() ? u.path() : u.toDisplayString(); Kernel::self()->sorry(i18n("Tellico can only import one file of this type at a time. " "Only %1 will be imported.", url)); urls.clear(); urls += u; } ImportDialog dlg(format_, urls, this); if(dlg.exec() != QDialog::Accepted) { return; } // if edit dialog is saved ok and if replacing, then the doc is saved ok if(m_editDialog->queryModified() && (dlg.action() != Import::Replace || querySaveModified())) { GUI::CursorSaver cs(Qt::WaitCursor); Data::CollPtr coll = dlg.collection(); if(!coll) { if(!dlg.statusMessage().isEmpty()) { Kernel::self()->sorry(dlg.statusMessage()); } return; } importCollection(coll, dlg.action()); } } void MainWindow::importText(Tellico::Import::Format format_, const QString& text_) { if(text_.isEmpty()) { return; } Data::CollPtr coll = ImportDialog::importText(format_, text_); if(coll) { importCollection(coll, Import::Merge); } } bool MainWindow::importCollection(Tellico::Data::CollPtr coll_, Tellico::Import::Action action_) { bool failed = false; switch(action_) { case Import::Append: { // only append if match, but special case importing books into bibliographies Data::CollPtr c = Data::Document::self()->collection(); if(c->type() == coll_->type() || (c->type() == Data::Collection::Bibtex && coll_->type() == Data::Collection::Book)) { Kernel::self()->appendCollection(coll_); slotEnableModifiedActions(true); } else { Kernel::self()->sorry(i18n(errorAppendType)); failed = true; } } break; case Import::Merge: { // only merge if match, but special case importing books into bibliographies Data::CollPtr c = Data::Document::self()->collection(); if(c->type() == coll_->type() || (c->type() == Data::Collection::Bibtex && coll_->type() == Data::Collection::Book)) { Kernel::self()->mergeCollection(coll_); slotEnableModifiedActions(true); } else { Kernel::self()->sorry(i18n(errorMergeType)); failed = true; } } break; default: // replace Kernel::self()->replaceCollection(coll_); m_fileOpenRecent->setCurrentItem(-1); m_newDocument = true; slotEnableOpenedActions(); slotEnableModifiedActions(false); break; } // tell the entry views and models that there are no further images to load m_detailedView->slotRefreshImages(); return !failed; } void MainWindow::slotURLAction(const QUrl& url_) { Q_ASSERT(url_.scheme() == QLatin1String("tc")); QString actionName = url_.fileName(); QAction* action = this->action(actionName.toLatin1().constData()); if(action) { action->activate(QAction::Trigger); } else { myWarning() << "unknown action: " << actionName; } } bool MainWindow::eventFilter(QObject* obj_, QEvent* ev_) { if(ev_->type() == QEvent::KeyPress && obj_ == m_quickFilter) { switch(static_cast(ev_)->key()) { case Qt::Key_Escape: m_quickFilter->clear(); return true; } } return KXmlGuiWindow::eventFilter(obj_, ev_); } void MainWindow::slotToggleFullScreen() { Qt::WindowStates ws = windowState(); setWindowState((ws & Qt::WindowFullScreen) ? (ws & ~Qt::WindowFullScreen) : (ws | Qt::WindowFullScreen)); } void MainWindow::slotToggleMenuBarVisibility() { QMenuBar* mb = menuBar(); mb->isHidden() ? mb->show() : mb->hide(); } void MainWindow::slotToggleLayoutLock(bool lock_) { m_groupViewDock->setLocked(lock_); m_collectionViewDock->setLocked(lock_); } void MainWindow::slotResetLayout() { removeDockWidget(m_groupViewDock); addDockWidget(Qt::LeftDockWidgetArea, m_groupViewDock); m_groupViewDock->show(); m_dummyWindow->removeDockWidget(m_collectionViewDock); m_dummyWindow->addDockWidget(Qt::TopDockWidgetArea, m_collectionViewDock); m_collectionViewDock->show(); } void MainWindow::guiFactoryReset() { guiFactory()->removeClient(this); guiFactory()->reset(); guiFactory()->addClient(this); } diff --git a/src/observer.h b/src/observer.h index b0668c91..dd57aec4 100644 --- a/src/observer.h +++ b/src/observer.h @@ -1,61 +1,65 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #ifndef TELLICO_OBSERVER_H #define TELLICO_OBSERVER_H #include "datavectors.h" namespace Tellico { class Filter; /** * @author Robby Stephenson */ class Observer { public: + Observer() {} virtual ~Observer() {} virtual void addBorrower(Data::BorrowerPtr) {} virtual void modifyBorrower(Data::BorrowerPtr) {} virtual void removeBorrower(Data::BorrowerPtr) {} virtual void addEntries(Data::EntryList) {} virtual void modifyEntries(Data::EntryList) {} virtual void removeEntries(Data::EntryList) {} virtual void addField(Data::CollPtr, Data::FieldPtr) {} // coll, oldfield, newfield virtual void modifyField(Data::CollPtr, Data::FieldPtr, Data::FieldPtr) {} virtual void removeField(Data::CollPtr, Data::FieldPtr) {} virtual void addFilter(FilterPtr) {} virtual void modifyFilter(FilterPtr) {} virtual void removeFilter(FilterPtr) {} + +private: + Q_DISABLE_COPY(Observer) }; } #endif diff --git a/src/tests/alexandriatest.cpp b/src/tests/alexandriatest.cpp index f3639e4f..d5dbc6a0 100644 --- a/src/tests/alexandriatest.cpp +++ b/src/tests/alexandriatest.cpp @@ -1,104 +1,102 @@ /*************************************************************************** Copyright (C) 2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ -#undef QT_NO_CAST_FROM_ASCII - #include "alexandriatest.h" #include "../translators/alexandriaimporter.h" #include "../translators/alexandriaexporter.h" #include "../collections/bookcollection.h" #include "../images/imagefactory.h" #include #include QTEST_GUILESS_MAIN( AlexandriaTest ) -#define QL1(x) QStringLiteral(x) +#define QSL(x) QStringLiteral(x) void AlexandriaTest::initTestCase() { Tellico::ImageFactory::init(); } void AlexandriaTest::testImport() { Tellico::Import::AlexandriaImporter importer; importer.setLibraryPath(QFINDTESTDATA("/data/alexandria/")); // shut the importer up about current collection Tellico::Data::CollPtr tmpColl(new Tellico::Data::BookCollection(true)); importer.setCurrentCollection(tmpColl); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Book); QCOMPARE(coll->entryCount(), 2); // should be translated somehow - QCOMPARE(coll->title(), QL1("My Books")); + QCOMPARE(coll->title(), QSL("My Books")); Tellico::Data::EntryPtr entry = coll->entryById(1); - QCOMPARE(entry->field("title"), QL1("The Hallowed Hunt")); - QCOMPARE(entry->field("comments"), QL1("first line
second line")); + QCOMPARE(entry->field(QSL("title")), QSL("The Hallowed Hunt")); + QCOMPARE(entry->field(QSL("comments")), QSL("first line
second line")); entry = coll->entryById(2); - QCOMPARE(entry->field("title"), QL1("Life Together")); - QCOMPARE(entry->field("author"), QL1("Dietrich Bonhoeffer; My Other Author")); + QCOMPARE(entry->field(QSL("title")), QSL("Life Together")); + QCOMPARE(entry->field(QSL("author")), QSL("Dietrich Bonhoeffer; My Other Author")); // translated - QCOMPARE(entry->field("binding"), QL1("Hardback")); - QCOMPARE(entry->field("isbn"), QL1("0-06-060853-6")); - QCOMPARE(entry->field("pub_year"), QL1("1993")); - QCOMPARE(entry->field("publisher"), QL1("Harper Collins")); - QCOMPARE(entry->field("rating"), QL1("3")); - QCOMPARE(entry->field("read"), QL1("true")); - QCOMPARE(entry->field("loaned"), QL1("")); - QVERIFY(!entry->field("comments").isEmpty()); + QCOMPARE(entry->field(QSL("binding")), QSL("Hardback")); + QCOMPARE(entry->field(QSL("isbn")), QSL("0-06-060853-6")); + QCOMPARE(entry->field(QSL("pub_year")), QSL("1993")); + QCOMPARE(entry->field(QSL("publisher")), QSL("Harper Collins")); + QCOMPARE(entry->field(QSL("rating")), QSL("3")); + QCOMPARE(entry->field(QSL("read")), QSL("true")); + QCOMPARE(entry->field(QSL("loaned")), QString()); + QVERIFY(!entry->field(QSL("comments")).isEmpty()); QTemporaryDir outputDir; Tellico::Export::AlexandriaExporter exporter(coll); exporter.setEntries(coll->entries()); exporter.setURL(QUrl::fromLocalFile(outputDir.path())); QVERIFY(exporter.exec()); - importer.setLibraryPath(outputDir.path() + "/.alexandria/" + coll->title()); + importer.setLibraryPath(outputDir.path() + QSL("/.alexandria/") + coll->title()); Tellico::Data::CollPtr coll2 = importer.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->title(), coll->title()); QCOMPARE(coll2->entryCount(), coll->entryCount()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { // assume IDs stay the same Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images if(f->type() != Tellico::Data::Field::Image) { QCOMPARE(f->name() + e1->field(f), f->name() + e2->field(f)); } } } } diff --git a/src/tests/ciwtest.cpp b/src/tests/ciwtest.cpp index 1eb242ad..f876ab19 100644 --- a/src/tests/ciwtest.cpp +++ b/src/tests/ciwtest.cpp @@ -1,77 +1,77 @@ /*************************************************************************** Copyright (C) 2012 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ -#undef QT_NO_CAST_FROM_ASCII - #include "ciwtest.h" #include "../translators/ciwimporter.h" #include "../collections/bibtexcollection.h" #include "../fieldformat.h" #include QTEST_APPLESS_MAIN( CiwTest ) +#define QSL(x) QStringLiteral(x) + void CiwTest::testImport() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/test.ciw")); QList urls; urls << url; Tellico::Import::CIWImporter importer(urls); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Bibtex); QCOMPARE(coll->entryCount(), 6); - QCOMPARE(coll->title(), QStringLiteral("Bibliography")); + QCOMPARE(coll->title(), QSL("Bibliography")); Tellico::Data::EntryPtr entry = coll->entryById(3); QVERIFY(entry); - QCOMPARE(entry->field("entry-type"), QStringLiteral("article")); - QCOMPARE(entry->field("title"), QStringLiteral("Key Process Conditions for Production of C(4) Dicarboxylic Acids in " + QCOMPARE(entry->field(QSL("entry-type")), QSL("article")); + QCOMPARE(entry->field(QSL("title")), QSL("Key Process Conditions for Production of C(4) Dicarboxylic Acids in " "Bioreactor Batch Cultures of an Engineered Saccharomyces cerevisiae Strain")); - QCOMPARE(entry->field("year"), QStringLiteral("2010")); - QCOMPARE(entry->field("pages"), QStringLiteral("744-750")); - QCOMPARE(entry->field("volume"), QStringLiteral("76")); - QCOMPARE(entry->field("journal"), QStringLiteral("APPLIED AND ENVIRONMENTAL MICROBIOLOGY")); - QCOMPARE(entry->field("doi"), QStringLiteral("10.1128/AEM.02396-09")); - QCOMPARE(Tellico::FieldFormat::splitValue(entry->field("author")).count(), 5); - QCOMPARE(Tellico::FieldFormat::splitValue(entry->field("author")).first(), QStringLiteral("Zelle, Rintze M.")); - QVERIFY(!entry->field("abstract").isEmpty()); + QCOMPARE(entry->field(QSL("year")), QSL("2010")); + QCOMPARE(entry->field(QSL("pages")), QSL("744-750")); + QCOMPARE(entry->field(QSL("volume")), QSL("76")); + QCOMPARE(entry->field(QSL("journal")), QSL("APPLIED AND ENVIRONMENTAL MICROBIOLOGY")); + QCOMPARE(entry->field(QSL("doi")), QSL("10.1128/AEM.02396-09")); + QCOMPARE(Tellico::FieldFormat::splitValue(entry->field(QSL("author"))).count(), 5); + QCOMPARE(Tellico::FieldFormat::splitValue(entry->field(QSL("author"))).first(), QSL("Zelle, Rintze M.")); + QVERIFY(!entry->field(QSL("abstract")).isEmpty()); entry = coll->entryById(6); QVERIFY(entry); - QCOMPARE(entry->field("entry-type"), QStringLiteral("article")); - QCOMPARE(entry->field("title"), QStringLiteral("Prematurity: An Overview and Public Health Implications")); - QCOMPARE(entry->field("booktitle"), QStringLiteral("ANNUAL REVIEW OF PUBLIC HEALTH, VOL 32")); - QCOMPARE(entry->field("isbn"), QStringLiteral("978-0-8243-2732-3")); - QCOMPARE(Tellico::FieldFormat::splitValue(entry->field("author")).count(), 4); - QCOMPARE(Tellico::FieldFormat::splitValue(entry->field("author")).first(), QStringLiteral("McCormick, Marie C.")); - QCOMPARE(Tellico::FieldFormat::splitValue(entry->field("editor")).count(), 3); - QCOMPARE(Tellico::FieldFormat::splitValue(entry->field("editor")).first(), QStringLiteral("Fielding, JE")); + QCOMPARE(entry->field(QSL("entry-type")), QSL("article")); + QCOMPARE(entry->field(QSL("title")), QSL("Prematurity: An Overview and Public Health Implications")); + QCOMPARE(entry->field(QSL("booktitle")), QSL("ANNUAL REVIEW OF PUBLIC HEALTH, VOL 32")); + QCOMPARE(entry->field(QSL("isbn")), QSL("978-0-8243-2732-3")); + QCOMPARE(Tellico::FieldFormat::splitValue(entry->field(QSL("author"))).count(), 4); + QCOMPARE(Tellico::FieldFormat::splitValue(entry->field(QSL("author"))).first(), QSL("McCormick, Marie C.")); + QCOMPARE(Tellico::FieldFormat::splitValue(entry->field(QSL("editor"))).count(), 3); + QCOMPARE(Tellico::FieldFormat::splitValue(entry->field(QSL("editor"))).first(), QSL("Fielding, JE")); Tellico::Data::BibtexCollection* bColl = dynamic_cast(coll.data()); QVERIFY(bColl); - QCOMPARE(bColl->fieldByBibtexName("entry-type")->name(), QStringLiteral("entry-type")); + QCOMPARE(bColl->fieldByBibtexName(QSL("entry-type"))->name(), QSL("entry-type")); } diff --git a/src/tests/darkhorsefetchertest.cpp b/src/tests/darkhorsefetchertest.cpp index 2ec29c23..bcc59905 100644 --- a/src/tests/darkhorsefetchertest.cpp +++ b/src/tests/darkhorsefetchertest.cpp @@ -1,87 +1,87 @@ /*************************************************************************** Copyright (C) 2011 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ -#undef QT_NO_CAST_FROM_ASCII - #include "darkhorsefetchertest.h" #include "../fetch/execexternalfetcher.h" #include "../entry.h" #include "../collections/comicbookcollection.h" #include "../collectionfactory.h" #include "../images/imagefactory.h" #include "../images/image.h" #include "../fieldformat.h" #include #include #include #include QTEST_GUILESS_MAIN( DarkHorseFetcherTest ) +#define QSL(x) QStringLiteral(x) + DarkHorseFetcherTest::DarkHorseFetcherTest() : AbstractFetcherTest() { } void DarkHorseFetcherTest::initTestCase() { - const QString python = QStandardPaths::findExecutable(QStringLiteral("python")); + const QString python = QStandardPaths::findExecutable(QSL("python")); if(python.isEmpty()) { QSKIP("This test requires python", SkipAll); } Tellico::ImageFactory::init(); Tellico::RegisterCollection registerComic(Tellico::Data::Collection::ComicBook, "comicbook"); } void DarkHorseFetcherTest::testComic() { Tellico::Fetch::FetchRequest request(Tellico::Data::Collection::ComicBook, Tellico::Fetch::Title, - QStringLiteral("axe cop: bad guy earth #1")); + QSL("axe cop: bad guy earth #1")); Tellico::Fetch::Fetcher::Ptr fetcher(new Tellico::Fetch::ExecExternalFetcher(this)); KConfig config(QFINDTESTDATA("../fetch/scripts/dark_horse_comics.py.spec"), KConfig::SimpleConfig); - KConfigGroup cg = config.group(QStringLiteral("")); + KConfigGroup cg = config.group(QSL("")); cg.writeEntry("ExecPath", QFINDTESTDATA("../fetch/scripts/dark_horse_comics.py")); // don't sync() and save the new path cg.markAsClean(); fetcher->readConfig(cg, cg.name()); Tellico::Data::EntryList results = DO_FETCH1(fetcher, request, 1); QCOMPARE(results.size(), 1); // the first entry had better be the right one Tellico::Data::EntryPtr entry = results.at(0); - QCOMPARE(entry->field("title"), QStringLiteral("Axe Cop: Bad Guy Earth #1")); - QCOMPARE(entry->field("pub_year"), QStringLiteral("2011")); - QCOMPARE(entry->field("genre"), QStringLiteral("Humor")); - QCOMPARE(entry->field("pages"), QStringLiteral("32")); - QCOMPARE(entry->field("publisher"), QStringLiteral("Dark Horse Comics")); - QCOMPARE(entry->field("writer"), QStringLiteral("Malachai Nicolle")); - QCOMPARE(entry->field("artist"), QStringLiteral("Ethan Nicolle")); - QVERIFY(!entry->field("comments").isEmpty()); - QVERIFY(!entry->field("cover").isEmpty()); - QVERIFY(!entry->field(QStringLiteral("cover")).contains(QLatin1Char('/'))); - QVERIFY(!Tellico::ImageFactory::imageById(entry->field("cover")).isNull()); + QCOMPARE(entry->field(QSL("title")), QSL("Axe Cop: Bad Guy Earth #1")); + QCOMPARE(entry->field(QSL("pub_year")), QSL("2011")); + QCOMPARE(entry->field(QSL("genre")), QSL("Humor")); + QCOMPARE(entry->field(QSL("pages")), QSL("32")); + QCOMPARE(entry->field(QSL("publisher")), QSL("Dark Horse Comics")); + QCOMPARE(entry->field(QSL("writer")), QSL("Malachai Nicolle")); + QCOMPARE(entry->field(QSL("artist")), QSL("Ethan Nicolle")); + QVERIFY(!entry->field(QSL("comments")).isEmpty()); + QVERIFY(!entry->field(QSL("cover")).isEmpty()); + QVERIFY(!entry->field(QSL("cover")).contains(QLatin1Char('/'))); + QVERIFY(!Tellico::ImageFactory::imageById(entry->field(QSL("cover"))).isNull()); } diff --git a/src/tests/filtertest.cpp b/src/tests/filtertest.cpp index 762d0f6f..da5faf80 100644 --- a/src/tests/filtertest.cpp +++ b/src/tests/filtertest.cpp @@ -1,252 +1,252 @@ /*************************************************************************** Copyright (C) 2011 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #undef QT_NO_CAST_FROM_ASCII #include "filtertest.h" #include "../filter.h" #include "../entry.h" #include "../collections/bookcollection.h" #include QTEST_GUILESS_MAIN( FilterTest ) void FilterTest::initTestCase() { } void FilterTest::testFilter() { Tellico::Data::CollPtr coll(new Tellico::Data::Collection(true, QStringLiteral("TestCollection"))); Tellico::Data::EntryPtr entry(new Tellico::Data::Entry(coll)); entry->setField(QStringLiteral("title"), QStringLiteral("Star Wars")); Tellico::FilterRule* rule1 = new Tellico::FilterRule(QStringLiteral("title"), QStringLiteral("Star Wars"), Tellico::FilterRule::FuncEquals); QCOMPARE(rule1->fieldName(), QStringLiteral("title")); QCOMPARE(rule1->pattern(), QStringLiteral("Star Wars")); QCOMPARE(rule1->function(), Tellico::FilterRule::FuncEquals); Tellico::Filter filter(Tellico::Filter::MatchAny); filter.append(rule1); QVERIFY(filter.matches(entry)); rule1->setFunction(Tellico::FilterRule::FuncNotEquals); QVERIFY(!filter.matches(entry)); rule1->setFunction(Tellico::FilterRule::FuncContains); QVERIFY(filter.matches(entry)); rule1->setFieldName(QStringLiteral("author")); QVERIFY(!filter.matches(entry)); rule1->setFunction(Tellico::FilterRule::FuncNotContains); QVERIFY(filter.matches(entry)); rule1->setFieldName(QString()); rule1->setFunction(Tellico::FilterRule::FuncEquals); QVERIFY(filter.matches(entry)); Tellico::FilterRule* rule2 = new Tellico::FilterRule(QStringLiteral("title"), QStringLiteral("Star"), Tellico::FilterRule::FuncEquals); filter.clear(); filter.append(rule2); QVERIFY(!filter.matches(entry)); rule2->setFunction(Tellico::FilterRule::FuncContains); QVERIFY(filter.matches(entry)); rule2->setFunction(Tellico::FilterRule::FuncNotContains); QVERIFY(!filter.matches(entry)); rule2->setFieldName(QStringLiteral("author")); rule2->setFunction(Tellico::FilterRule::FuncContains); QVERIFY(!filter.matches(entry)); rule2->setFieldName(QString()); QVERIFY(filter.matches(entry)); Tellico::FilterRule* rule3 = new Tellico::FilterRule(QStringLiteral("title"), QStringLiteral("Sta[rt]"), Tellico::FilterRule::FuncRegExp); QCOMPARE(rule3->pattern(), QStringLiteral("Sta[rt]")); filter.clear(); filter.append(rule3); QVERIFY(filter.matches(entry)); rule3->setFunction(Tellico::FilterRule::FuncNotRegExp); QVERIFY(!filter.matches(entry)); rule3->setFieldName(QStringLiteral("author")); QVERIFY(filter.matches(entry)); rule3->setFieldName(QString()); rule3->setFunction(Tellico::FilterRule::FuncRegExp); QVERIFY(filter.matches(entry)); entry->setField(QStringLiteral("title"), QStringLiteral("Tmavomodrý Svět")); Tellico::FilterRule* rule4 = new Tellico::FilterRule(QStringLiteral("title"), QStringLiteral("Tmavomodrý Svět"), Tellico::FilterRule::FuncEquals); filter.clear(); filter.append(rule4); QVERIFY(filter.matches(entry)); rule4->setFunction(Tellico::FilterRule::FuncContains); QVERIFY(filter.matches(entry)); rule4->setFunction(Tellico::FilterRule::FuncRegExp); QVERIFY(filter.matches(entry)); Tellico::FilterRule* rule5 = new Tellico::FilterRule(QStringLiteral("title"), - QLatin1String("Tmavomodry Svet"), + QStringLiteral("Tmavomodry Svet"), Tellico::FilterRule::FuncEquals); filter.clear(); filter.append(rule5); QVERIFY(!filter.matches(entry)); rule5->setFunction(Tellico::FilterRule::FuncContains); QVERIFY(filter.matches(entry)); rule5->setFunction(Tellico::FilterRule::FuncRegExp); QVERIFY(!filter.matches(entry)); Tellico::Data::FieldPtr date(new Tellico::Data::Field(QStringLiteral("date"), QStringLiteral("Date"), Tellico::Data::Field::Date)); coll->addField(date); Tellico::FilterRule* rule6 = new Tellico::FilterRule(QStringLiteral("date"), QStringLiteral("2011-01-24"), Tellico::FilterRule::FuncAfter); QCOMPARE(rule6->pattern(), QStringLiteral("2011-01-24")); filter.clear(); filter.append(rule6); // test Bug 361625 entry->setField(QStringLiteral("date"), QStringLiteral("2011-1-25")); QVERIFY(filter.matches(entry)); entry->setField(QStringLiteral("date"), QStringLiteral("2011-01-25")); QVERIFY(filter.matches(entry)); rule6->setFunction(Tellico::FilterRule::FuncBefore); QVERIFY(!filter.matches(entry)); // check that a date match is neither before or after entry->setField(QStringLiteral("date"), rule6->pattern()); rule6->setFunction(Tellico::FilterRule::FuncAfter); QVERIFY(!filter.matches(entry)); rule6->setFunction(Tellico::FilterRule::FuncBefore); QVERIFY(!filter.matches(entry)); // check that an invalid date never matches entry->setField(QStringLiteral("date"), QStringLiteral("test")); rule6->setFunction(Tellico::FilterRule::FuncAfter); QVERIFY(!filter.matches(entry)); rule6->setFunction(Tellico::FilterRule::FuncBefore); QVERIFY(!filter.matches(entry)); Tellico::Data::FieldPtr number(new Tellico::Data::Field(QStringLiteral("number"), QStringLiteral("Number"), Tellico::Data::Field::Number)); coll->addField(number); entry->setField(QStringLiteral("number"), QStringLiteral("3")); Tellico::FilterRule* rule7 = new Tellico::FilterRule(QStringLiteral("number"), QStringLiteral("5.0"), Tellico::FilterRule::FuncLess); QCOMPARE(rule7->pattern(), QStringLiteral("5.0")); filter.clear(); filter.append(rule7); QVERIFY(filter.matches(entry)); rule7->setFunction(Tellico::FilterRule::FuncGreater); QVERIFY(!filter.matches(entry)); entry->setField(QStringLiteral("number"), QStringLiteral("6")); QVERIFY(filter.matches(entry)); // check that a rating can use greater than Tellico::Data::FieldPtr rating(new Tellico::Data::Field(QStringLiteral("rating"), QStringLiteral("Rating"), Tellico::Data::Field::Rating)); coll->addField(rating); entry->setField(QStringLiteral("rating"), QStringLiteral("3")); Tellico::FilterRule* rule8 = new Tellico::FilterRule(QStringLiteral("rating"), QStringLiteral("2.0"), Tellico::FilterRule::FuncGreater); QCOMPARE(rule8->pattern(), QStringLiteral("2.0")); filter.clear(); filter.append(rule8); QVERIFY(filter.matches(entry)); rule8->setFunction(Tellico::FilterRule::FuncLess); QVERIFY(!filter.matches(entry)); entry->setField(QStringLiteral("rating"), QStringLiteral("1")); QVERIFY(filter.matches(entry)); } void FilterTest::testGroupViewFilter() { // ideally, I'd instantiate a GroupView object and test that, but it's tough with all the dependencies // so this code is identical to what is in Tellico::GroupView::slotFilterGroup() Tellico::Data::CollPtr coll(new Tellico::Data::BookCollection(true, QStringLiteral("TestCollection"))); Tellico::Data::EntryPtr entry1(new Tellico::Data::Entry(coll)); entry1->setField(QStringLiteral("author"), QStringLiteral("John Author")); Tellico::Data::EntryPtr entry2(new Tellico::Data::Entry(coll)); entry2->setField(QStringLiteral("author"), QStringLiteral("John Q. Author")); Tellico::Data::EntryPtr entry3(new Tellico::Data::Entry(coll)); entry3->setField(QStringLiteral("author"), QStringLiteral("John Author") + Tellico::FieldFormat::delimiterString() + QStringLiteral("James Author")); Tellico::Data::EntryPtr entry4(new Tellico::Data::Entry(coll)); entry4->setField(QStringLiteral("author"), QStringLiteral("James Author") + Tellico::FieldFormat::delimiterString() + QStringLiteral("John Author")); Tellico::Data::EntryPtr entry5(new Tellico::Data::Entry(coll)); entry5->setField(QStringLiteral("author"), QStringLiteral("James Author") + Tellico::FieldFormat::delimiterString() + QStringLiteral("John Q. Author")); QString pattern(entry1->formattedField(QStringLiteral("author"))); // the filter should match all since it was the initial way the group view filter was constructed Tellico::Filter filter1(Tellico::Filter::MatchAny); filter1.append(new Tellico::FilterRule(QStringLiteral("author"), pattern, Tellico::FilterRule::FuncContains)); QVERIFY(filter1.matches(entry1)); QVERIFY(filter1.matches(entry2)); QVERIFY(filter1.matches(entry3)); QVERIFY(filter1.matches(entry4)); QVERIFY(filter1.matches(entry5)); QString rxPattern(QStringLiteral("(^|;\\s)") + pattern + QStringLiteral("($|;)")); // the filter should match entry1, entry3, and entry 4 but not entry2 or entry5 Tellico::Filter filter2(Tellico::Filter::MatchAny); filter2.append(new Tellico::FilterRule(QStringLiteral("author"), rxPattern, Tellico::FilterRule::FuncRegExp)); QVERIFY(filter2.matches(entry1)); QVERIFY(!filter2.matches(entry2)); // does not match QVERIFY(filter2.matches(entry3)); QVERIFY(filter2.matches(entry4)); QVERIFY(!filter2.matches(entry5)); } diff --git a/src/tests/gcstartest.cpp b/src/tests/gcstartest.cpp index 47ef934a..1065c0b9 100644 --- a/src/tests/gcstartest.cpp +++ b/src/tests/gcstartest.cpp @@ -1,592 +1,592 @@ /*************************************************************************** Copyright (C) 2009-2010 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #undef QT_NO_CAST_FROM_ASCII #include "gcstartest.h" #include "../translators/gcstarimporter.h" #include "../translators/gcstarexporter.h" #include "../collections/collectioninitializer.h" #include "../collectionfactory.h" #include "../images/imagefactory.h" #include "../utils/datafileregistry.h" #include "../fieldformat.h" #include -#define FIELDS(entry, fieldName) Tellico::FieldFormat::splitValue(entry->field(fieldName)) -#define TABLES(entry, fieldName) Tellico::FieldFormat::splitTable(entry->field(fieldName)) +#define FIELDS(entry, fieldName) Tellico::FieldFormat::splitValue(entry->field(QStringLiteral(fieldName))) +#define TABLES(entry, fieldName) Tellico::FieldFormat::splitTable(entry->field(QStringLiteral(fieldName))) QTEST_GUILESS_MAIN( GCstarTest ) void GCstarTest::initTestCase() { Tellico::ImageFactory::init(); Tellico::DataFileRegistry::self()->addDataLocation(QFINDTESTDATA("../../xslt/gcstar2tellico.xsl")); // need to register the collection types Tellico::CollectionInitializer ci; } void GCstarTest::testBook() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/test-book.gcs")); Tellico::Import::GCstarImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Book); QCOMPARE(coll->entryCount(), 2); // should be translated somehow QCOMPARE(coll->title(), QStringLiteral("GCstar Import")); Tellico::Data::EntryPtr entry = coll->entryById(1); QVERIFY(entry); QCOMPARE(entry->field("title"), QStringLiteral("The Reason for God")); QCOMPARE(entry->field("pub_year"), QStringLiteral("2008")); QCOMPARE(FIELDS(entry, "author").count(), 2); QCOMPARE(FIELDS(entry, "author").first(), QStringLiteral("Timothy Keller")); QCOMPARE(entry->field("isbn"), QStringLiteral("978-0-525-95049-3")); QCOMPARE(entry->field("publisher"), QStringLiteral("Dutton Adult")); QCOMPARE(FIELDS(entry, "genre").count(), 2); QCOMPARE(FIELDS(entry, "genre").at(0), QStringLiteral("non-fiction")); QCOMPARE(FIELDS(entry, "keyword").count(), 2); QCOMPARE(FIELDS(entry, "keyword").at(0), QStringLiteral("tag1")); QCOMPARE(FIELDS(entry, "keyword").at(1), QStringLiteral("tag2")); // file has rating of 4, Tellico uses half the rating of GCstar, so it should be 2 QCOMPARE(entry->field("rating"), QStringLiteral("2")); QCOMPARE(FIELDS(entry, "language").count(), 1); QCOMPARE(FIELDS(entry, "language").at(0), QStringLiteral("English")); Tellico::Export::GCstarExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::GCstarImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->entryCount(), coll->entryCount()); QCOMPARE(coll2->title(), coll->title()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images if(f->type() != Tellico::Data::Field::Image) { QCOMPARE(f->name() + e2->field(f), f->name() + e1->field(f)); } } } } void GCstarTest::testComicBook() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/test-comicbook.gcs")); Tellico::Import::GCstarImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::ComicBook); QCOMPARE(coll->entryCount(), 1); // should be translated somehow QCOMPARE(coll->title(), QStringLiteral("GCstar Import")); Tellico::Data::EntryPtr entry = coll->entryById(1); QVERIFY(entry); QCOMPARE(entry->field("title"), QStringLiteral("title")); QCOMPARE(entry->field("pub_year"), QStringLiteral("2010")); QCOMPARE(entry->field("series"), QStringLiteral("series")); QCOMPARE(entry->field("issue"), QStringLiteral("1")); QCOMPARE(FIELDS(entry, "writer").count(), 2); QCOMPARE(FIELDS(entry, "writer").first(), QStringLiteral("writer1")); QCOMPARE(entry->field("isbn"), QStringLiteral("1234567890")); QCOMPARE(entry->field("artist"), QStringLiteral("illustrator")); QCOMPARE(entry->field("publisher"), QStringLiteral("publisher")); QCOMPARE(entry->field("colorist"), QStringLiteral("colourist")); QCOMPARE(entry->field("category"), QStringLiteral("category")); QCOMPARE(entry->field("format"), QStringLiteral("format")); QCOMPARE(entry->field("collection"), QStringLiteral("collection")); QCOMPARE(entry->field("pur_date"), QStringLiteral("29/08/2010")); QCOMPARE(entry->field("pur_price"), QStringLiteral("12.99")); QCOMPARE(entry->field("numberboards"), QStringLiteral("1")); QCOMPARE(entry->field("signed"), QStringLiteral("true")); // file has rating of 4, Tellico uses half the rating of GCstar, so it should be 2 QCOMPARE(entry->field("rating"), QStringLiteral("2")); QVERIFY(!entry->field("plot").isEmpty()); QVERIFY(!entry->field("comments").isEmpty()); Tellico::Export::GCstarExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::GCstarImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->entryCount(), coll->entryCount()); QCOMPARE(coll2->title(), coll->title()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images if(f->type() != Tellico::Data::Field::Image) { QCOMPARE(f->name() + e2->field(f), f->name() + e1->field(f)); } } } } void GCstarTest::testVideo() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/test-video.gcs")); Tellico::Import::GCstarImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Video); QCOMPARE(coll->entryCount(), 3); Tellico::Data::EntryPtr entry = coll->entryById(2); QVERIFY(entry); QCOMPARE(entry->field("title"), QStringLiteral("The Man from Snowy River")); QCOMPARE(entry->field("year"), QStringLiteral("1982")); QCOMPARE(FIELDS(entry, "director").count(), 1); QCOMPARE(FIELDS(entry, "director").first(), QStringLiteral("George Miller")); QCOMPARE(FIELDS(entry, "nationality").count(), 1); QCOMPARE(FIELDS(entry, "nationality").first(), QStringLiteral("Australia")); QCOMPARE(entry->field("medium"), QStringLiteral("DVD")); QCOMPARE(entry->field("running-time"), QStringLiteral("102")); QCOMPARE(FIELDS(entry, "genre").count(), 4); QCOMPARE(FIELDS(entry, "genre").at(0), QStringLiteral("Drama")); QStringList castList = Tellico::FieldFormat::splitTable(entry->field(QStringLiteral("cast"))); QCOMPARE(castList.count(), 10); QCOMPARE(castList.at(0), QStringLiteral("Tom Burlinson::Jim Craig")); QCOMPARE(castList.at(2), QStringLiteral("Kirk Douglas::Harrison / Spur")); QCOMPARE(FIELDS(entry, "keyword").count(), 2); QCOMPARE(FIELDS(entry, "keyword").at(0), QStringLiteral("tag2")); QCOMPARE(FIELDS(entry, "keyword").at(1), QStringLiteral("tag1")); QCOMPARE(entry->field("rating"), QStringLiteral("3")); QVERIFY(!entry->field("plot").isEmpty()); QVERIFY(!entry->field("comments").isEmpty()); entry = coll->entryById(4); QVERIFY(entry); castList = Tellico::FieldFormat::splitTable(entry->field(QStringLiteral("cast"))); QCOMPARE(castList.count(), 11); QCOMPARE(castList.at(0), QStringLiteral("Famke Janssen::Marnie Watson")); QCOMPARE(entry->field("location"), QStringLiteral("On Hard Drive")); Tellico::Export::GCstarExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::GCstarImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->entryCount(), coll->entryCount()); QCOMPARE(coll2->title(), coll->title()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images and tables if(f->type() != Tellico::Data::Field::Image && f->type() != Tellico::Data::Field::Table) { QCOMPARE(f->name() + e2->field(f), f->name() + e1->field(f)); } else if(f->type() == Tellico::Data::Field::Table) { QStringList rows1 = Tellico::FieldFormat::splitTable(e1->field(f)); QStringList rows2 = Tellico::FieldFormat::splitTable(e2->field(f)); QCOMPARE(rows1.count(), rows2.count()); for(int i = 0; i < rows1.count(); ++i) { QCOMPARE(f->name() + rows2.at(i), f->name() + rows1.at(i)); } } } } } void GCstarTest::testMusic() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/test-music.gcs")); Tellico::Import::GCstarImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Album); QCOMPARE(coll->entryCount(), 1); Tellico::Data::EntryPtr entry = coll->entryById(1); QVERIFY(entry); QCOMPARE(entry->field("title"), QStringLiteral("Lifesong")); QCOMPARE(entry->field("year"), QStringLiteral("2005")); QCOMPARE(FIELDS(entry, "artist").count(), 1); QCOMPARE(FIELDS(entry, "artist").first(), QStringLiteral("Casting Crowns")); QCOMPARE(FIELDS(entry, "label").count(), 1); QCOMPARE(FIELDS(entry, "label").first(), QStringLiteral("Beach Street Records")); QCOMPARE(entry->field("medium"), QStringLiteral("Compact Disc")); QCOMPARE(FIELDS(entry, "genre").count(), 2); QCOMPARE(FIELDS(entry, "genre").at(0), QStringLiteral("Electronic")); QStringList trackList = Tellico::FieldFormat::splitTable(entry->field(QStringLiteral("track"))); QCOMPARE(trackList.count(), 11); QCOMPARE(trackList.at(1), QStringLiteral("Praise You In This Storm::Casting Crowns::4:59")); QCOMPARE(FIELDS(entry, "producer").count(), 1); QCOMPARE(FIELDS(entry, "producer").at(0), QStringLiteral("Mark A. Miller")); QCOMPARE(FIELDS(entry, "composer").count(), 4); QCOMPARE(FIELDS(entry, "composer").at(1), QStringLiteral("David Hunt")); QCOMPARE(entry->field("cdate"), QStringLiteral("2009-09-22")); Tellico::Export::GCstarExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::GCstarImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->entryCount(), coll->entryCount()); QCOMPARE(coll2->title(), coll->title()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images if(f->type() != Tellico::Data::Field::Image) { QCOMPARE(f->name() + e2->field(f), f->name() + e1->field(f)); } } } } void GCstarTest::testVideoGame() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/test-videogame.gcs")); Tellico::Import::GCstarImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Game); QCOMPARE(coll->entryCount(), 2); Tellico::Data::EntryPtr entry = coll->entryById(2); QVERIFY(entry); QCOMPARE(entry->field("title"), QStringLiteral("Halo 3")); QCOMPARE(entry->field("year"), QStringLiteral("2007")); QCOMPARE(entry->field("platform"), QStringLiteral("Xbox 360")); QCOMPARE(FIELDS(entry, "developer").count(), 1); QCOMPARE(FIELDS(entry, "developer").first(), QStringLiteral("Bungie Studios")); QCOMPARE(FIELDS(entry, "publisher").count(), 1); QCOMPARE(FIELDS(entry, "publisher").first(), QStringLiteral("Microsoft Games Studios")); QCOMPARE(FIELDS(entry, "genre").count(), 3); QCOMPARE(FIELDS(entry, "genre").at(0), QStringLiteral("Action")); QCOMPARE(entry->field("cdate"), QStringLiteral("2009-09-24")); QVERIFY(!entry->field("description").isEmpty()); Tellico::Export::GCstarExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::GCstarImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->entryCount(), coll->entryCount()); QCOMPARE(coll2->title(), coll->title()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images if(f->type() != Tellico::Data::Field::Image) { QCOMPARE(f->name() + e2->field(f), f->name() + e1->field(f)); } } } } void GCstarTest::testBoardGame() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/test-boardgame.gcs")); Tellico::Import::GCstarImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::BoardGame); QCOMPARE(coll->entryCount(), 2); Tellico::Data::EntryPtr entry = coll->entryById(1); QVERIFY(entry); QCOMPARE(entry->field("title"), QStringLiteral("Risk")); QCOMPARE(entry->field("year"), QStringLiteral("1959")); QCOMPARE(FIELDS(entry, "designer").count(), 2); QCOMPARE(FIELDS(entry, "designer").at(1), QStringLiteral("Michael I. Levin")); QCOMPARE(FIELDS(entry, "publisher").count(), 11); QCOMPARE(FIELDS(entry, "publisher").at(1), QStringLiteral("Borras Plana S.A.")); QCOMPARE(FIELDS(entry, "mechanism").count(), 3); QCOMPARE(FIELDS(entry, "mechanism").at(1), QStringLiteral("Dice Rolling")); QCOMPARE(FIELDS(entry, "genre").count(), 1); QCOMPARE(FIELDS(entry, "genre").at(0), QStringLiteral("Wargame")); QVERIFY(!entry->field("description").isEmpty()); QVERIFY(!entry->field("comments").isEmpty()); Tellico::Export::GCstarExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::GCstarImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->entryCount(), coll->entryCount()); QCOMPARE(coll2->title(), coll->title()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images if(f->type() != Tellico::Data::Field::Image) { QCOMPARE(f->name() + e2->field(f), f->name() + e1->field(f)); } } } } void GCstarTest::testWine() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/test-wine.gcs")); Tellico::Import::GCstarImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Wine); QCOMPARE(coll->entryCount(), 1); Tellico::Data::EntryPtr entry = coll->entryById(1); QVERIFY(entry); QCOMPARE(entry->field("vintage"), QStringLiteral("1990")); QCOMPARE(entry->field("producer"), QStringLiteral("producer")); QCOMPARE(entry->field("type"), QStringLiteral("Red Wine")); QCOMPARE(entry->field("country"), QStringLiteral("australia")); QCOMPARE(entry->field("quantity"), QStringLiteral("1")); QCOMPARE(FIELDS(entry, "varietal").count(), 2); QCOMPARE(FIELDS(entry, "varietal").at(1), QStringLiteral("grape2")); QCOMPARE(entry->field("pur_date"), QStringLiteral("28/08/2010")); QCOMPARE(entry->field("pur_price"), QStringLiteral("12.99")); QCOMPARE(entry->field("appellation"), QStringLiteral("designation")); QCOMPARE(entry->field("distinction"), QStringLiteral("distinction")); QCOMPARE(entry->field("soil"), QStringLiteral("soil")); QCOMPARE(entry->field("alcohol"), QStringLiteral("12")); QCOMPARE(entry->field("volume"), QStringLiteral("750")); QCOMPARE(entry->field("rating"), QStringLiteral("3")); QCOMPARE(entry->field("gift"), QStringLiteral("true")); QCOMPARE(entry->field("tasted"), QStringLiteral("true")); QVERIFY(!entry->field("description").isEmpty()); QVERIFY(!entry->field("comments").isEmpty()); Tellico::Export::GCstarExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::GCstarImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->entryCount(), coll->entryCount()); QCOMPARE(coll2->title(), coll->title()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images if(f->type() != Tellico::Data::Field::Image) { QCOMPARE(f->name() + e2->field(f), f->name() + e1->field(f)); } } } } void GCstarTest::testCoin() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/test-coin.gcs")); Tellico::Import::GCstarImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Coin); QCOMPARE(coll->entryCount(), 1); Tellico::Data::EntryPtr entry = coll->entryById(1); QVERIFY(entry); QCOMPARE(entry->field("denomination"), QStringLiteral("0.05")); QCOMPARE(entry->field("year"), QStringLiteral("1974")); QCOMPARE(entry->field("currency"), QStringLiteral("USD")); QCOMPARE(entry->field("diameter"), QStringLiteral("12.7")); QCOMPARE(entry->field("estimate"), QStringLiteral("5")); QCOMPARE(entry->field("grade"), QStringLiteral("Mint State-65")); QCOMPARE(entry->field("country"), QStringLiteral("australia")); QCOMPARE(entry->field("location"), QStringLiteral("current")); QCOMPARE(entry->field("service"), QStringLiteral("PCGS")); QCOMPARE(TABLES(entry, "metal").count(), 2); QCOMPARE(TABLES(entry, "metal").at(1), QStringLiteral("metal2")); QVERIFY(!entry->field("comments").isEmpty()); Tellico::Export::GCstarExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::GCstarImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->entryCount(), coll->entryCount()); QCOMPARE(coll2->title(), coll->title()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images if(f->type() != Tellico::Data::Field::Image) { QCOMPARE(f->name() + e2->field(f), f->name() + e1->field(f)); } } } } void GCstarTest::testCustomFields() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/test-book.gcs")); Tellico::Import::GCstarImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Book); QCOMPARE(coll->entryCount(), 2); // should be translated somehow QCOMPARE(coll->title(), QStringLiteral("GCstar Import")); // test custom fields Tellico::Data::FieldPtr field = coll->fieldByName(QStringLiteral("gcsfield1")); QVERIFY(field); QCOMPARE(field->name(), QStringLiteral("gcsfield1")); QCOMPARE(field->title(), QStringLiteral("New boolean")); QCOMPARE(field->category(), QStringLiteral("User fields")); QCOMPARE(field->type(), Tellico::Data::Field::Bool); field = coll->fieldByName(QStringLiteral("gcsfield2")); QVERIFY(field); QCOMPARE(field->title(), QStringLiteral("New choice")); QCOMPARE(field->type(), Tellico::Data::Field::Choice); QCOMPARE(field->allowed(), QStringList() << QStringLiteral("yes") << QStringLiteral("no") << QStringLiteral("maybe")); field = coll->fieldByName(QStringLiteral("gcsfield3")); QVERIFY(field); QCOMPARE(field->title(), QStringLiteral("New rating")); QCOMPARE(field->type(), Tellico::Data::Field::Rating); QCOMPARE(field->property(QStringLiteral("minimum")), QStringLiteral("1")); QCOMPARE(field->property(QStringLiteral("maximum")), QStringLiteral("5")); field = coll->fieldByName(QStringLiteral("gcsfield4")); QVERIFY(field); QCOMPARE(field->title(), QStringLiteral("New field")); QCOMPARE(field->type(), Tellico::Data::Field::Line); field = coll->fieldByName(QStringLiteral("gcsfield5")); QVERIFY(field); QCOMPARE(field->title(), QStringLiteral("New image")); QCOMPARE(field->type(), Tellico::Data::Field::Image); field = coll->fieldByName(QStringLiteral("gcsfield6")); QVERIFY(field); QCOMPARE(field->title(), QStringLiteral("New long field")); QCOMPARE(field->type(), Tellico::Data::Field::Para); field = coll->fieldByName(QStringLiteral("gcsfield7")); QVERIFY(field); QCOMPARE(field->title(), QStringLiteral("New date")); QCOMPARE(field->type(), Tellico::Data::Field::Date); field = coll->fieldByName(QStringLiteral("gcsfield8")); QVERIFY(field); QCOMPARE(field->title(), QStringLiteral("New number")); QCOMPARE(field->type(), Tellico::Data::Field::Number); QCOMPARE(field->defaultValue(), QStringLiteral("2")); field = coll->fieldByName(QStringLiteral("gcsfield9")); QVERIFY(field); QCOMPARE(field->title(), QStringLiteral("dependency")); QCOMPARE(field->type(), Tellico::Data::Field::Line); QCOMPARE(field->property(QStringLiteral("template")), QStringLiteral("%{gcsfield1},%{gcsfield2}")); field = coll->fieldByName(QStringLiteral("gcsfield10")); QVERIFY(field); QCOMPARE(field->title(), QStringLiteral("list")); QCOMPARE(field->type(), Tellico::Data::Field::Table); QCOMPARE(field->property(QStringLiteral("columns")), QStringLiteral("1")); Tellico::Data::EntryPtr entry = coll->entryById(2); QVERIFY(entry); QCOMPARE(entry->field("gcsfield1"), QStringLiteral("true")); QCOMPARE(entry->field("gcsfield2"), QStringLiteral("maybe")); QCOMPARE(entry->field("gcsfield3"), QStringLiteral("3")); QCOMPARE(entry->field("gcsfield4"), QStringLiteral("random value")); QCOMPARE(entry->field("gcsfield6"), QStringLiteral("all\nthe best \nstuff")); QCOMPARE(entry->field("gcsfield7"), QStringLiteral("2013-03-31")); QCOMPARE(entry->field("gcsfield9"), QStringLiteral("true,maybe")); QCOMPARE(TABLES(entry, "gcsfield10").count(), 2); QCOMPARE(TABLES(entry, "gcsfield10").at(1), QStringLiteral("list2")); Tellico::Export::GCstarExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::GCstarImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); foreach(Tellico::Data::FieldPtr f1, coll->fields()) { Tellico::Data::FieldPtr f2 = coll2->fieldByName(f1->name()); QVERIFY2(f2, qPrintable(f1->name())); QCOMPARE(f1->name(), f2->name()); QCOMPARE(f1->title(), f2->title()); QCOMPARE(f1->category(), f2->category()); QCOMPARE(f1->allowed(), f2->allowed()); QCOMPARE(f1->type(), f2->type()); QCOMPARE(f1->flags(), f2->flags()); QCOMPARE(f1->formatType(), f2->formatType()); QCOMPARE(f1->description(), f2->description()); QCOMPARE(f1->defaultValue(), f2->defaultValue()); QCOMPARE(f1->property(QStringLiteral("minimum")), f2->property(QStringLiteral("minimum"))); QCOMPARE(f1->property(QStringLiteral("maximum")), f2->property(QStringLiteral("maximum"))); QCOMPARE(f1->property(QStringLiteral("columns")), f2->property(QStringLiteral("columns"))); QCOMPARE(f1->property(QStringLiteral("template")), f2->property(QStringLiteral("template"))); } foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); QCOMPARE(TABLES(e2, "gcsfield10").count(), 2); QCOMPARE(TABLES(e2, "gcsfield10").at(1), QStringLiteral("list2")); foreach(Tellico::Data::FieldPtr f, coll->fields()) { // skip images if(f->type() != Tellico::Data::Field::Image) { QCOMPARE(f->name() + e1->field(f), f->name() + e2->field(f)); } } } } diff --git a/src/tests/griffithtest.cpp b/src/tests/griffithtest.cpp index 0d0325da..c1dfb503 100644 --- a/src/tests/griffithtest.cpp +++ b/src/tests/griffithtest.cpp @@ -1,78 +1,76 @@ /*************************************************************************** Copyright (C) 2012 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ -#undef QT_NO_CAST_FROM_ASCII - #include "griffithtest.h" #include "../translators/griffithimporter.h" #include "../collections/videocollection.h" #include "../collectionfactory.h" #include "../fieldformat.h" #include "../images/imagefactory.h" #include "../utils/datafileregistry.h" #include -#define FIELDS(entry, fieldName) Tellico::FieldFormat::splitValue(entry->field(fieldName)) -#define ROWS(entry, fieldName) Tellico::FieldFormat::splitTable(entry->field(fieldName)) +#define QSL(x) QStringLiteral(x) +#define ROWS(entry, fieldName) Tellico::FieldFormat::splitTable(entry->field(QStringLiteral(fieldName))) QTEST_GUILESS_MAIN( GriffithTest ) void GriffithTest::initTestCase() { Tellico::DataFileRegistry::self()->addDataLocation(QFINDTESTDATA("../../xslt/griffith2tellico.xsl")); Tellico::ImageFactory::init(); // need to register the collection type Tellico::RegisterCollection registerVideo(Tellico::Data::Collection::Video, "video"); } void GriffithTest::testMovies() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/griffith.xml")); Tellico::Import::GriffithImporter importer(url); // can't import images for local test importer.setOptions(importer.options() & ~Tellico::Import::ImportShowImageErrors); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Video); QCOMPARE(coll->entryCount(), 5); Tellico::Data::EntryPtr entry = coll->entryById(1); QVERIFY(entry); - QCOMPARE(entry->field("title"), QStringLiteral("Serendipity")); - QCOMPARE(entry->field("origtitle"), QStringLiteral("Serendipity")); - QCOMPARE(entry->field("director"), QStringLiteral("Peter Chelsom")); - QCOMPARE(entry->field("year"), QStringLiteral("2001")); - QCOMPARE(entry->field("certification"), QStringLiteral("PG-13 (USA)")); - QCOMPARE(entry->field("nationality"), QStringLiteral("USA")); - QCOMPARE(entry->field("genre"), QStringLiteral("Comedy; Romance; Fantasy")); - QCOMPARE(entry->field("rating"), QStringLiteral("6")); - QCOMPARE(entry->field("running-time"), QStringLiteral("90")); - QCOMPARE(entry->field("studio"), QStringLiteral("studio")); - QCOMPARE(entry->field("seen"), QStringLiteral("true")); - QCOMPARE(entry->field("medium"), QStringLiteral("DVD")); - QCOMPARE(ROWS(entry, "cast").first(), QStringLiteral("John Cusack::Jonathan Trager")); - QVERIFY(!entry->field("plot").isEmpty()); + QCOMPARE(entry->field(QSL("title")), QSL("Serendipity")); + QCOMPARE(entry->field(QSL("origtitle")), QSL("Serendipity")); + QCOMPARE(entry->field(QSL("director")), QSL("Peter Chelsom")); + QCOMPARE(entry->field(QSL("year")), QSL("2001")); + QCOMPARE(entry->field(QSL("certification")), QSL("PG-13 (USA)")); + QCOMPARE(entry->field(QSL("nationality")), QSL("USA")); + QCOMPARE(entry->field(QSL("genre")), QSL("Comedy; Romance; Fantasy")); + QCOMPARE(entry->field(QSL("rating")), QSL("6")); + QCOMPARE(entry->field(QSL("running-time")), QSL("90")); + QCOMPARE(entry->field(QSL("studio")), QSL("studio")); + QCOMPARE(entry->field(QSL("seen")), QSL("true")); + QCOMPARE(entry->field(QSL("medium")), QSL("DVD")); + QCOMPARE(ROWS(entry, "cast").first(), QSL("John Cusack::Jonathan Trager")); + QVERIFY(!entry->field(QSL("plot")).isEmpty()); // cover will be empty since local images don't exist } diff --git a/src/tests/tellicoreadtest.cpp b/src/tests/tellicoreadtest.cpp index 7f0d53f2..425150c7 100644 --- a/src/tests/tellicoreadtest.cpp +++ b/src/tests/tellicoreadtest.cpp @@ -1,341 +1,339 @@ /*************************************************************************** Copyright (C) 2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ -#undef QT_NO_CAST_FROM_ASCII - #include "tellicoreadtest.h" #include "../translators/tellicoimporter.h" #include "../collections/bookcollection.h" #include "../collections/coincollection.h" #include "../collectionfactory.h" #include "../translators/tellicoxmlexporter.h" #include "../translators/tellico_xml.h" #include "../images/imagefactory.h" #include "../images/image.h" #include "../fieldformat.h" #include "../entry.h" #include "../utils/xmlhandler.h" #include QTEST_GUILESS_MAIN( TellicoReadTest ) #define QSL(x) QStringLiteral(x) #define TELLICOREAD_NUMBER_OF_CASES 10 void TellicoReadTest::initTestCase() { // need to register this first Tellico::RegisterCollection registerBook(Tellico::Data::Collection::Book, "book"); Tellico::RegisterCollection registerCoin(Tellico::Data::Collection::Coin, "coin"); Tellico::RegisterCollection registerBase(Tellico::Data::Collection::Base, "entry"); for(int i = 1; i < TELLICOREAD_NUMBER_OF_CASES; ++i) { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA(QSL("data/books-format%1.bc").arg(i))); Tellico::Import::TellicoImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); m_collections.append(coll); } Tellico::ImageFactory::init(); } void TellicoReadTest::init() { Tellico::ImageFactory::clean(true); } void TellicoReadTest::testBookCollection() { Tellico::Data::CollPtr coll1 = m_collections[0]; // skip the first one for(int i = 1; i < m_collections.count(); ++i) { Tellico::Data::CollPtr coll2 = m_collections[i]; QVERIFY(coll2); QCOMPARE(coll1->type(), coll2->type()); QCOMPARE(coll1->title(), coll2->title()); QCOMPARE(coll1->entryCount(), coll2->entryCount()); } } void TellicoReadTest::testEntries() { QFETCH(QString, fieldName); Tellico::Data::FieldPtr field1 = m_collections[0]->fieldByName(fieldName); // skip the first one for(int i = 1; i < m_collections.count(); ++i) { Tellico::Data::FieldPtr field2 = m_collections[i]->fieldByName(fieldName); if(field1 && field2) { QCOMPARE(field1->name(), field2->name()); QCOMPARE(field1->title(), field2->title()); QCOMPARE(field1->category(), field2->category()); QCOMPARE(field1->type(), field2->type()); QCOMPARE(field1->flags(), field2->flags()); QCOMPARE(field1->propertyList(), field2->propertyList()); } for(int j = 0; j < m_collections[0]->entryCount(); ++j) { // don't test id values since the initial value has changed from 0 to 1 Tellico::Data::EntryPtr entry1 = m_collections[0]->entries().at(j); Tellico::Data::EntryPtr entry2 = m_collections[i]->entries().at(j); QVERIFY(entry1); QVERIFY(entry2); QCOMPARE(entry1->field(fieldName), entry2->field(fieldName)); } } } void TellicoReadTest::testEntries_data() { QTest::addColumn("fieldName"); QTest::newRow("title") << QSL("title"); QTest::newRow("author") << QSL("author"); QTest::newRow("publisher") << QSL("publisher"); QTest::newRow("keywords") << QSL("keywords"); QTest::newRow("keyword") << QSL("keyword"); QTest::newRow("genre") << QSL("genre"); QTest::newRow("isbn") << QSL("isbn"); QTest::newRow("pub_year") << QSL("pub_year"); QTest::newRow("rating") << QSL("rating"); QTest::newRow("comments") << QSL("comments"); } void TellicoReadTest::testCoinCollection() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/coins-format9.tc")); Tellico::Import::TellicoImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->type(), Tellico::Data::Collection::Coin); Tellico::Data::FieldPtr field = coll->fieldByName(QStringLiteral("title")); // old field has Dependent value, now is Line QVERIFY(field); QCOMPARE(field->type(), Tellico::Data::Field::Line); QCOMPARE(field->title(), QSL("Title")); QVERIFY(field->hasFlag(Tellico::Data::Field::Derived)); Tellico::Data::EntryPtr entry = coll->entries().at(0); // test creating the derived title QCOMPARE(entry->title(), QSL("1974D Jefferson Nickel 0.05")); } void TellicoReadTest::testTableData() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/tabletest.tc")); Tellico::Import::TellicoImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->entryCount(), 3); Tellico::Export::TellicoXMLExporter exporter(coll); exporter.setEntries(coll->entries()); Tellico::Import::TellicoImporter importer2(exporter.text()); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); QCOMPARE(coll2->type(), coll->type()); QCOMPARE(coll2->entryCount(), coll->entryCount()); foreach(Tellico::Data::EntryPtr e1, coll->entries()) { Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id()); QVERIFY(e2); foreach(Tellico::Data::FieldPtr f, coll->fields()) { QCOMPARE(f->name() + e1->field(f), f->name() + e2->field(f)); } } // test table value concatenation Tellico::Data::EntryPtr e3(new Tellico::Data::Entry(coll)); coll->addEntries(e3); - QString value = "11a" + Tellico::FieldFormat::delimiterString() + "11b" - + Tellico::FieldFormat::columnDelimiterString() + "12" - + Tellico::FieldFormat::columnDelimiterString() + "13" - + Tellico::FieldFormat::rowDelimiterString() + "21" - + Tellico::FieldFormat::columnDelimiterString() + "22" - + Tellico::FieldFormat::columnDelimiterString() + "23"; + QString value = QSL("11a") + Tellico::FieldFormat::delimiterString() + QSL("11b") + + Tellico::FieldFormat::columnDelimiterString() + QSL("12") + + Tellico::FieldFormat::columnDelimiterString() + QSL("13") + + Tellico::FieldFormat::rowDelimiterString() + QSL("21") + + Tellico::FieldFormat::columnDelimiterString() + QSL("22") + + Tellico::FieldFormat::columnDelimiterString() + QSL("23"); e3->setField(QSL("table"), value); QStringList groups = e3->groupNamesByFieldName(QStringLiteral("table")); QCOMPARE(groups.count(), 3); // the order of the group names is not stable (it uses QSet::toList) QCOMPARE(groups.toSet(), QSet() << QSL("11a") << QSL("11b") << QSL("21")); // test having empty value in table Tellico::Data::EntryPtr e = coll2->entryById(2); QVERIFY(e); - const QStringList rows = Tellico::FieldFormat::splitTable(e->field(QStringLiteral("table"))); + const QStringList rows = Tellico::FieldFormat::splitTable(e->field(QSL("table"))); QCOMPARE(rows.count(), 1); const QStringList cols = Tellico::FieldFormat::splitRow(rows.at(0)); QCOMPARE(cols.count(), 3); } void TellicoReadTest::testDuplicateLoans() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/duplicate_loan.xml")); Tellico::Import::TellicoImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->borrowers().count(), 1); Tellico::Data::BorrowerPtr bor = coll->borrowers().first(); QVERIFY(bor); QCOMPARE(bor->loans().count(), 1); } void TellicoReadTest::testDuplicateBorrowers() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/duplicate_borrower.xml")); Tellico::Import::TellicoImporter importer(url); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->borrowers().count(), 1); Tellico::Data::BorrowerPtr bor = coll->borrowers().first(); QVERIFY(bor); QCOMPARE(bor->loans().count(), 2); } void TellicoReadTest::testLocalImage() { // this is the md5 hash of the tellico.png icon, used as an image id const QString imageId(QSL("dde5bf2cbd90fad8635a26dfb362e0ff.png")); // not yet loaded QVERIFY(!Tellico::ImageFactory::self()->hasImageInMemory(imageId)); QVERIFY(!Tellico::ImageFactory::self()->hasImageInfo(imageId)); QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/local_image.xml")); QFile f(url.toLocalFile()); QVERIFY(f.exists()); QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text)); QTextStream in(&f); QString fileText = in.readAll(); // replace %COVER% with image file location fileText.replace(QSL("%COVER%"), QFINDTESTDATA("../../icons/tellico.png")); Tellico::Import::TellicoImporter importer(fileText); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->entries().count(), 1); Tellico::Data::EntryPtr entry = coll->entries().at(0); QVERIFY(entry); QCOMPARE(entry->field(QStringLiteral("cover")), imageId); // the image should be in local memory now QVERIFY(Tellico::ImageFactory::self()->hasImageInMemory(imageId)); QVERIFY(Tellico::ImageFactory::self()->hasImageInfo(imageId)); const Tellico::Data::Image& img = Tellico::ImageFactory::imageById(imageId); QVERIFY(!img.isNull()); } void TellicoReadTest::testRemoteImage() { // this is the md5 hash of the logo.png icon, used as an image id const QString imageId(QSL("757322046f4aa54290a3d92b05b71ca1.png")); // not yet loaded QVERIFY(!Tellico::ImageFactory::self()->hasImageInMemory(imageId)); QVERIFY(!Tellico::ImageFactory::self()->hasImageInfo(imageId)); QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/local_image.xml")); QFile f(url.toLocalFile()); QVERIFY(f.exists()); QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text)); QTextStream in(&f); QString fileText = in.readAll(); // replace %COVER% with image file location fileText.replace(QSL("%COVER%"), QSL("http://tellico-project.org/sites/default/files/logo.png")); Tellico::Import::TellicoImporter importer(fileText); Tellico::Data::CollPtr coll = importer.collection(); QVERIFY(coll); QCOMPARE(coll->entries().count(), 1); Tellico::Data::EntryPtr entry = coll->entries().at(0); QVERIFY(entry); QCOMPARE(entry->field(QStringLiteral("cover")), imageId); // the image should be in local memory now QVERIFY(Tellico::ImageFactory::self()->hasImageInMemory(imageId)); QVERIFY(Tellico::ImageFactory::self()->hasImageInfo(imageId)); const Tellico::Data::Image& img = Tellico::ImageFactory::imageById(imageId); QVERIFY(!img.isNull()); } void TellicoReadTest::testXMLHandler() { QFETCH(QByteArray, data); QFETCH(QString, expectedString); QFETCH(bool, changeEncoding); QString origString = QString::fromUtf8(data); QCOMPARE(Tellico::XMLHandler::readXMLData(data), expectedString); QCOMPARE(Tellico::XMLHandler::setUtf8XmlEncoding(origString), changeEncoding); } void TellicoReadTest::testXMLHandler_data() { QTest::addColumn("data"); QTest::addColumn("expectedString"); QTest::addColumn("changeEncoding"); QTest::newRow("basic") << QByteArray("value") << QStringLiteral("value") << false; QTest::newRow("utf8") << QByteArray("\nvalue") << QStringLiteral("\nvalue") << false; QTest::newRow("latin1") << QByteArray("\nvalue") << QStringLiteral("\nvalue") << true; } void TellicoReadTest::testXmlName() { QFETCH(bool, valid); QFETCH(QString, input); QFETCH(QString, modified); QCOMPARE(Tellico::XML::validXMLElementName(input), valid); QCOMPARE(Tellico::XML::elementName(input), modified); } void TellicoReadTest::testXmlName_data() { QTest::addColumn("valid"); QTest::addColumn("input"); QTest::addColumn("modified"); QTest::newRow("start") << true << QSL("start") << QSL("start"); QTest::newRow("_start") << true << QSL("_start") << QSL("_start"); QTest::newRow("n42") << true << QSL("n42") << QSL("n42"); // an empty string is handled in CollectionFieldsDialog when creating the field name - QTest::newRow("42") << false << QSL("42") << QString(""); + QTest::newRow("42") << false << QSL("42") << QString(); QTest::newRow("she is") << false << QSL("she is") << QSL("she-is"); QTest::newRow("colon:") << true << QSL("colon:") << QSL("colon:"); QTest::newRow("Svět") << true << QSL("Svět") << QSL("Svět"); QTest::newRow("") << false << QSL("") << QSL("test"); }