diff --git a/app/savebar.cpp b/app/savebar.cpp index e5e46a30..e5fcf055 100644 --- a/app/savebar.cpp +++ b/app/savebar.cpp @@ -1,371 +1,367 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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) any later version. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self #include "savebar.h" // Qt #include #include #include #include #include #include #include // KDE #include #include #include #include // Local #include "lib/document/documentfactory.h" #include "lib/gwenviewconfig.h" #include "lib/memoryutils.h" #include "lib/paintutils.h" namespace Gwenview { QToolButton* createToolButton() { QToolButton* button = new QToolButton; button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); button->hide(); return button; } struct SaveBarPrivate { SaveBar* q; KActionCollection* mActionCollection; QWidget* mSaveBarWidget; QWidget* mTopRowWidget; QToolButton* mUndoButton; QToolButton* mRedoButton; QToolButton* mSaveCurrentUrlButton; QToolButton* mSaveAsButton; QToolButton* mSaveAllButton; QToolButton* mSaveAllFullScreenButton; QLabel* mMessageLabel; QLabel* mActionsLabel; QFrame* mTooManyChangesFrame; QUrl mCurrentUrl; void createTooManyChangesFrame() { mTooManyChangesFrame = new QFrame; // Icon QLabel* iconLabel = new QLabel; QPixmap pix = KIconLoader::global()->loadIcon( "dialog-warning", KIconLoader::Dialog, KIconLoader::SizeSmall); iconLabel->setPixmap(pix); // Text label QLabel* textLabel = new QLabel; textLabel->setText( i18n("You have modified many images. To avoid memory problems, you should save your changes.") ); mSaveAllFullScreenButton = createToolButton(); // Layout QHBoxLayout* layout = new QHBoxLayout(mTooManyChangesFrame); layout->setMargin(0); layout->addWidget(iconLabel); layout->addWidget(textLabel); layout->addWidget(mSaveAllFullScreenButton); mTooManyChangesFrame->hide(); // CSS KColorScheme scheme(mSaveBarWidget->palette().currentColorGroup(), KColorScheme::Window); QColor warningBackgroundColor = scheme.background(KColorScheme::NegativeBackground).color(); QColor warningBorderColor = PaintUtils::adjustedHsv(warningBackgroundColor, 0, 150, 0); QColor warningColor = scheme.foreground(KColorScheme::NegativeText).color(); QString css = ".QFrame {" " background-color: %1;" " border: 1px solid %2;" " border-radius: 4px;" " padding: 3px;" "}" ".QFrame QLabel {" " color: %3;" "}" ; - css = css - .arg(warningBackgroundColor.name()) - .arg(warningBorderColor.name()) - .arg(warningColor.name()) - ; + css = css.arg(warningBackgroundColor.name(), + warningBorderColor.name(), + warningColor.name()); mTooManyChangesFrame->setStyleSheet(css); } void applyNormalStyleSheet() { QColor bgColor = QToolTip::palette().base().color(); QColor borderColor = PaintUtils::adjustedHsv(bgColor, 0, 150, 0); QColor fgColor = QToolTip::palette().text().color(); QString css = "#saveBarWidget {" " background-color: %1;" " border-top: 1px solid %2;" " border-bottom: 1px solid %2;" " color: %3;" "}" ; - css = css - .arg(bgColor.name()) - .arg(borderColor.name()) - .arg(fgColor.name()) - ; + css = css.arg(bgColor.name(), + borderColor.name(), + fgColor.name()); mSaveBarWidget->setStyleSheet(css); } void applyFullScreenStyleSheet() { QString css = "#saveBarWidget {" " background-color: #333;" "}"; mSaveBarWidget->setStyleSheet(css); } void updateTooManyChangesFrame(const QList& list) { qreal maxPercentageOfMemoryUsage = GwenviewConfig::percentageOfMemoryUsageWarning(); qulonglong maxMemoryUsage = MemoryUtils::getTotalMemory() * maxPercentageOfMemoryUsage; qulonglong memoryUsage = 0; Q_FOREACH(const QUrl &url, list) { Document::Ptr doc = DocumentFactory::instance()->load(url); memoryUsage += doc->memoryUsage(); } mTooManyChangesFrame->setVisible(memoryUsage > maxMemoryUsage); } void updateTopRowWidget(const QList& lst) { QStringList links; QString message; if (lst.contains(mCurrentUrl)) { message = i18n("Current image modified"); mUndoButton->show(); mRedoButton->show(); if (lst.size() > 1) { QString previous = i18n("Previous modified image"); QString next = i18n("Next modified image"); if (mCurrentUrl == lst[0]) { links << previous; } else { links << QStringLiteral("%1").arg(previous); } if (mCurrentUrl == lst[lst.size() - 1]) { links << next; } else { links << QStringLiteral("%1").arg(next); } } } else { mUndoButton->hide(); mRedoButton->hide(); message = i18np("One image modified", "%1 images modified", lst.size()); if (lst.size() > 1) { links << QStringLiteral("%1").arg(i18n("Go to first modified image")); } else { links << QStringLiteral("%1").arg(i18n("Go to it")); } } mSaveCurrentUrlButton->setVisible(lst.contains(mCurrentUrl)); mSaveAsButton->setVisible(lst.contains(mCurrentUrl)); mSaveAllButton->setVisible(lst.size() >= 1); mMessageLabel->setText(message); mMessageLabel->setMaximumWidth(mMessageLabel->minimumSizeHint().width()); mActionsLabel->setText(links.join(" | ")); } void updateWidgetSizes() { QVBoxLayout* layout = static_cast(mSaveBarWidget->layout()); int topRowHeight = q->window()->isFullScreen() ? 0 : mTopRowWidget->height(); int bottomRowHeight = mTooManyChangesFrame->isVisibleTo(mSaveBarWidget) ? mTooManyChangesFrame->sizeHint().height() : 0; int height = 2 * layout->margin() + topRowHeight + bottomRowHeight; if (topRowHeight > 0 && bottomRowHeight > 0) { height += layout->spacing(); } mSaveBarWidget->setFixedHeight(height); } }; SaveBar::SaveBar(QWidget* parent, KActionCollection* actionCollection) : SlideContainer(parent) , d(new SaveBarPrivate) { d->q = this; d->mActionCollection = actionCollection; d->mSaveBarWidget = new QWidget(); d->mSaveBarWidget->setObjectName(QLatin1String("saveBarWidget")); d->applyNormalStyleSheet(); d->mMessageLabel = new QLabel; d->mMessageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); d->mUndoButton = createToolButton(); d->mRedoButton = createToolButton(); d->mSaveCurrentUrlButton = createToolButton(); d->mSaveAsButton = createToolButton(); d->mSaveAllButton = createToolButton(); d->mActionsLabel = new QLabel; d->mActionsLabel->setAlignment(Qt::AlignCenter); d->mActionsLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); d->createTooManyChangesFrame(); // Setup top row d->mTopRowWidget = new QWidget; QHBoxLayout* rowLayout = new QHBoxLayout(d->mTopRowWidget); rowLayout->addWidget(d->mMessageLabel); rowLayout->setStretchFactor(d->mMessageLabel, 1); rowLayout->addWidget(d->mUndoButton); rowLayout->addWidget(d->mRedoButton); rowLayout->addWidget(d->mActionsLabel); rowLayout->addWidget(d->mSaveCurrentUrlButton); rowLayout->addWidget(d->mSaveAsButton); rowLayout->addWidget(d->mSaveAllButton); rowLayout->setMargin(0); // Setup bottom row QHBoxLayout* bottomRowLayout = new QHBoxLayout; bottomRowLayout->addStretch(); bottomRowLayout->addWidget(d->mTooManyChangesFrame); bottomRowLayout->addStretch(); // Gather everything together QVBoxLayout* layout = new QVBoxLayout(d->mSaveBarWidget); layout->addWidget(d->mTopRowWidget); layout->addLayout(bottomRowLayout); layout->setMargin(3); layout->setSpacing(3); setContent(d->mSaveBarWidget); connect(DocumentFactory::instance(), SIGNAL(modifiedDocumentListChanged()), SLOT(updateContent())); connect(d->mActionsLabel, &QLabel::linkActivated, this, &SaveBar::triggerAction); } SaveBar::~SaveBar() { delete d; } void SaveBar::initActionDependentWidgets() { d->mUndoButton->setDefaultAction(d->mActionCollection->action("edit_undo")); d->mRedoButton->setDefaultAction(d->mActionCollection->action("edit_redo")); d->mSaveCurrentUrlButton->setDefaultAction(d->mActionCollection->action("file_save")); d->mSaveAsButton->setDefaultAction(d->mActionCollection->action("file_save_as")); // FIXME: Not using an action for now d->mSaveAllButton->setText(i18n("Save All")); d->mSaveAllButton->setIcon(QIcon::fromTheme("document-save-all")); connect(d->mSaveAllButton, &QToolButton::clicked, this, &SaveBar::requestSaveAll); d->mSaveAllFullScreenButton->setText(i18n("Save All")); connect(d->mSaveAllFullScreenButton, &QToolButton::clicked, this, &SaveBar::requestSaveAll); int height = d->mUndoButton->sizeHint().height(); d->mTopRowWidget->setFixedHeight(height); d->updateWidgetSizes(); } void SaveBar::setFullScreenMode(bool isFullScreen) { d->mSaveAllFullScreenButton->setVisible(isFullScreen); if (isFullScreen) { d->applyFullScreenStyleSheet(); } else { d->applyNormalStyleSheet(); } updateContent(); } void SaveBar::updateContent() { QList lst = DocumentFactory::instance()->modifiedDocumentList(); if (window()->isFullScreen()) { d->mTopRowWidget->hide(); } else { d->mTopRowWidget->show(); d->updateTopRowWidget(lst); } d->updateTooManyChangesFrame(lst); d->updateWidgetSizes(); if (lst.isEmpty() || (window()->isFullScreen() && !d->mTooManyChangesFrame->isVisibleTo(d->mSaveBarWidget))) { slideOut(); } else { slideIn(); } } void SaveBar::triggerAction(const QString& action) { QList lst = DocumentFactory::instance()->modifiedDocumentList(); if (action == "first") { emit goToUrl(lst[0]); } else if (action == "previous") { int pos = lst.indexOf(d->mCurrentUrl); --pos; Q_ASSERT(pos >= 0); emit goToUrl(lst[pos]); } else if (action == "next") { int pos = lst.indexOf(d->mCurrentUrl); ++pos; Q_ASSERT(pos < lst.size()); emit goToUrl(lst[pos]); } else { qWarning() << "Unknown action: " << action ; } } void SaveBar::setCurrentUrl(const QUrl &url) { d->mCurrentUrl = url; updateContent(); } } // namespace diff --git a/importer/thumbnailpage.cpp b/importer/thumbnailpage.cpp index f4e3cd34..d9a7eb98 100644 --- a/importer/thumbnailpage.cpp +++ b/importer/thumbnailpage.cpp @@ -1,470 +1,470 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2009 Aurélien Gâteau 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) any later version. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "thumbnailpage.h" #include "dialogguard.h" // Qt #include #include #include #include #include // KDE #include #include #include #include #include #include #include // Local #include #include #include #include #include #include #include #include #include #include #include #include namespace Gwenview { static const int DEFAULT_THUMBNAIL_SIZE = 128; static const qreal DEFAULT_THUMBNAIL_ASPECT_RATIO = 3. / 2.; static const char* URL_FOR_BASE_URL_GROUP = "UrlForBaseUrl"; class ImporterThumbnailViewHelper : public AbstractThumbnailViewHelper { public: ImporterThumbnailViewHelper(QObject* parent) : AbstractThumbnailViewHelper(parent) {} void showContextMenu(QWidget*) override {} void showMenuForUrlDroppedOnViewport(QWidget*, const QList&) override {} void showMenuForUrlDroppedOnDir(QWidget*, const QList&, const QUrl&) override {} }; inline KFileItem itemForIndex(const QModelIndex& index) { return index.data(KDirModel::FileItemRole).value(); } struct ThumbnailPagePrivate : public Ui_ThumbnailPage { ThumbnailPage* q; SerializedUrlMap mUrlMap; QIcon mSrcBaseIcon; QString mSrcBaseName; QUrl mSrcBaseUrl; QUrl mSrcUrl; KModelIndexProxyMapper* mSrcUrlModelProxyMapper; RecursiveDirModel* mRecursiveDirModel; QAbstractItemModel* mFinalModel; ThumbnailProvider mThumbnailProvider; QPushButton* mImportSelectedButton; QPushButton* mImportAllButton; QList mUrlList; void setupDirModel() { mRecursiveDirModel = new RecursiveDirModel(q); KindProxyModel* kindProxyModel = new KindProxyModel(q); kindProxyModel->setKindFilter( MimeTypeUtils::KIND_RASTER_IMAGE | MimeTypeUtils::KIND_SVG_IMAGE | MimeTypeUtils::KIND_VIDEO); kindProxyModel->setSourceModel(mRecursiveDirModel); QSortFilterProxyModel *sortModel = new QSortFilterProxyModel(q); sortModel->setDynamicSortFilter(true); sortModel->setSourceModel(kindProxyModel); sortModel->sort(0); mFinalModel = sortModel; QObject::connect( mFinalModel, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(updateImportButtons())); QObject::connect( mFinalModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(updateImportButtons())); QObject::connect( mFinalModel, SIGNAL(modelReset()), q, SLOT(updateImportButtons())); } void setupIcons() { const KIconLoader::Group group = KIconLoader::NoGroup; const int size = KIconLoader::SizeHuge; mSrcIconLabel->setPixmap(KIconLoader::global()->loadIcon("camera-photo", group, size)); mDstIconLabel->setPixmap(KIconLoader::global()->loadIcon("computer", group, size)); } void setupSrcUrlWidgets() { mSrcUrlModelProxyMapper = nullptr; QObject::connect(mSrcUrlButton, SIGNAL(clicked()), q, SLOT(setupSrcUrlTreeView())); QObject::connect(mSrcUrlButton, SIGNAL(clicked()), q, SLOT(toggleSrcUrlTreeView())); mSrcUrlTreeView->hide(); KAcceleratorManager::setNoAccel(mSrcUrlButton); } void setupDstUrlRequester() { mDstUrlRequester->setMode(KFile::Directory | KFile::LocalOnly); } void setupThumbnailView() { mThumbnailView->setModel(mFinalModel); mThumbnailView->setSelectionMode(QAbstractItemView::ExtendedSelection); mThumbnailView->setThumbnailViewHelper(new ImporterThumbnailViewHelper(q)); PreviewItemDelegate* delegate = new PreviewItemDelegate(mThumbnailView); delegate->setThumbnailDetails(PreviewItemDelegate::FileNameDetail); PreviewItemDelegate::ContextBarActions actions; switch (GwenviewConfig::thumbnailActions()) { case ThumbnailActions::None: actions = PreviewItemDelegate::NoAction; break; case ThumbnailActions::ShowSelectionButtonOnly: case ThumbnailActions::AllButtons: actions = PreviewItemDelegate::SelectionAction; break; } delegate->setContextBarActions(actions); mThumbnailView->setItemDelegate(delegate); // Colors int value = GwenviewConfig::viewBackgroundValue(); QColor bgColor = QColor::fromHsv(0, 0, value); QColor fgColor = value > 128 ? Qt::black : Qt::white; QPalette pal = mThumbnailView->palette(); pal.setColor(QPalette::Base, bgColor); pal.setColor(QPalette::Text, fgColor); mThumbnailView->setPalette(pal); QObject::connect(mSlider, SIGNAL(valueChanged(int)), mThumbnailView, SLOT(setThumbnailWidth(int))); QObject::connect(mThumbnailView, SIGNAL(thumbnailWidthChanged(int)), mSlider, SLOT(setValue(int))); int thumbnailSize = DEFAULT_THUMBNAIL_SIZE; mSlider->setValue(thumbnailSize); mSlider->updateToolTip(); mThumbnailView->setThumbnailAspectRatio(DEFAULT_THUMBNAIL_ASPECT_RATIO); mThumbnailView->setThumbnailWidth(thumbnailSize); mThumbnailView->setThumbnailProvider(&mThumbnailProvider); QObject::connect( mThumbnailView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(updateImportButtons())); } void setupButtonBox() { QObject::connect(mConfigureButton, SIGNAL(clicked()), q, SLOT(showConfigDialog())); mImportSelectedButton = mButtonBox->addButton(i18n("Import Selected"), QDialogButtonBox::AcceptRole); QObject::connect(mImportSelectedButton, SIGNAL(clicked(bool)), q, SLOT(slotImportSelected())); mImportAllButton = mButtonBox->addButton(i18n("Import All"), QDialogButtonBox::AcceptRole); QObject::connect(mImportAllButton, SIGNAL(clicked(bool)), q, SLOT(slotImportAll())); QObject::connect( mButtonBox, SIGNAL(rejected()), q, SIGNAL(rejected())); } QUrl urlForBaseUrl() const { QUrl url = mUrlMap.value(mSrcBaseUrl); if (!url.isValid()) { return QUrl(); } KIO::StatJob *job = KIO::stat(url); KJobWidgets::setWindow(job, q); if (!job->exec()) { return QUrl(); } KFileItem item(job->statResult(), url, true /* delayedMimeTypes */); return item.isDir() ? url : QUrl(); } void rememberUrl(const QUrl& url) { mUrlMap.insert(mSrcBaseUrl, url); } }; ThumbnailPage::ThumbnailPage() : d(new ThumbnailPagePrivate) { d->q = this; d->mUrlMap.setConfigGroup(KConfigGroup(KSharedConfig::openConfig(), URL_FOR_BASE_URL_GROUP)); d->setupUi(this); d->setupIcons(); d->setupDirModel(); d->setupSrcUrlWidgets(); d->setupDstUrlRequester(); d->setupThumbnailView(); d->setupButtonBox(); updateImportButtons(); } ThumbnailPage::~ThumbnailPage() { delete d; } void ThumbnailPage::setSourceUrl(const QUrl& srcBaseUrl, const QString& iconName, const QString& name) { d->mSrcBaseIcon = QIcon::fromTheme(iconName); d->mSrcBaseName = name; const int size = KIconLoader::SizeHuge; d->mSrcIconLabel->setPixmap(d->mSrcBaseIcon.pixmap(size)); d->mSrcBaseUrl = srcBaseUrl; if (!d->mSrcBaseUrl.path().endsWith('/')) { d->mSrcBaseUrl.setPath(d->mSrcBaseUrl.path() + '/'); } QUrl url = d->urlForBaseUrl(); if (url.isValid()) { openUrl(url); } else { DocumentDirFinder* finder = new DocumentDirFinder(srcBaseUrl); connect(finder, SIGNAL(done(QUrl,DocumentDirFinder::Status)), SLOT(slotDocumentDirFinderDone(QUrl,DocumentDirFinder::Status))); finder->start(); } } void ThumbnailPage::slotDocumentDirFinderDone(const QUrl& url, DocumentDirFinder::Status /*status*/) { d->rememberUrl(url); openUrl(url); } void ThumbnailPage::openUrl(const QUrl& url) { d->mSrcUrl = url; QString path = QDir(d->mSrcBaseUrl.path()).relativeFilePath(d->mSrcUrl.path()); QString text; if (path.isEmpty() || path == ".") { text = d->mSrcBaseName; } else { path = QUrl::fromPercentEncoding(path.toUtf8()); path.replace('/', QString::fromUtf8(" › ")); - text = QString::fromUtf8("%1 › %2").arg(d->mSrcBaseName).arg(path); + text = QString::fromUtf8("%1 › %2").arg(d->mSrcBaseName, path); } d->mSrcUrlButton->setText(text); d->mRecursiveDirModel->setUrl(url); } QList ThumbnailPage::urlList() const { return d->mUrlList; } void ThumbnailPage::setDestinationUrl(const QUrl& url) { d->mDstUrlRequester->setUrl(url); } QUrl ThumbnailPage::destinationUrl() const { return d->mDstUrlRequester->url(); } void ThumbnailPage::slotImportSelected() { importList(d->mThumbnailView->selectionModel()->selectedIndexes()); } void ThumbnailPage::slotImportAll() { QModelIndexList list; QAbstractItemModel* model = d->mThumbnailView->model(); for (int row = model->rowCount() - 1; row >= 0; --row) { list << model->index(row, 0); } importList(list); } void ThumbnailPage::importList(const QModelIndexList& list) { d->mUrlList.clear(); Q_FOREACH(const QModelIndex & index, list) { KFileItem item = itemForIndex(index); if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { d->mUrlList << item.url(); } // FIXME: Handle dirs (do we want to import recursively?) } emit importRequested(); } void ThumbnailPage::updateImportButtons() { d->mImportSelectedButton->setEnabled(d->mThumbnailView->selectionModel()->hasSelection()); d->mImportAllButton->setEnabled(d->mThumbnailView->model()->rowCount(QModelIndex()) > 0); } void ThumbnailPage::showConfigDialog() { DialogGuard dialog(this); dialog->exec(); } /** * This model allows only the url passed in the constructor to appear at the root * level. This makes it possible to select the url, but not its siblings. * It also provides custom role values for the root item. */ class OnlyBaseUrlProxyModel : public QSortFilterProxyModel { public: OnlyBaseUrlProxyModel(const QUrl& url, const QIcon& icon, const QString& name, QObject* parent) : QSortFilterProxyModel(parent) , mUrl(url) , mIcon(icon) , mName(name) {} bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override { if (sourceParent.isValid()) { return true; } QModelIndex index = sourceModel()->index(sourceRow, 0); KFileItem item = itemForIndex(index); return item.url().matches(mUrl, QUrl::StripTrailingSlash); } QVariant data(const QModelIndex& index, int role) const override { if (index.parent().isValid()) { return QSortFilterProxyModel::data(index, role); } switch (role) { case Qt::DisplayRole: return mName; case Qt::DecorationRole: return mIcon; case Qt::ToolTipRole: return mUrl.toDisplayString(QUrl::PreferLocalFile); default: return QSortFilterProxyModel::data(index, role); } } private: QUrl mUrl; QIcon mIcon; QString mName; }; void ThumbnailPage::setupSrcUrlTreeView() { if (d->mSrcUrlTreeView->model()) { // Already initialized return; } KDirModel* dirModel = new KDirModel(this); dirModel->dirLister()->setDirOnlyMode(true); dirModel->dirLister()->openUrl(KIO::upUrl(d->mSrcBaseUrl)); OnlyBaseUrlProxyModel* onlyBaseUrlModel = new OnlyBaseUrlProxyModel(d->mSrcBaseUrl, d->mSrcBaseIcon, d->mSrcBaseName, this); onlyBaseUrlModel->setSourceModel(dirModel); QSortFilterProxyModel* sortModel = new QSortFilterProxyModel(this); sortModel->setDynamicSortFilter(true); sortModel->setSourceModel(onlyBaseUrlModel); sortModel->sort(0); d->mSrcUrlModelProxyMapper = new KModelIndexProxyMapper(dirModel, sortModel, this); d->mSrcUrlTreeView->setModel(sortModel); for(int i = 1; i < dirModel->columnCount(); ++i) { d->mSrcUrlTreeView->hideColumn(i); } connect(d->mSrcUrlTreeView, SIGNAL(activated(QModelIndex)), SLOT(openUrlFromIndex(QModelIndex))); connect(d->mSrcUrlTreeView, SIGNAL(clicked(QModelIndex)), SLOT(openUrlFromIndex(QModelIndex))); dirModel->expandToUrl(d->mSrcUrl); connect(dirModel, SIGNAL(expand(QModelIndex)), SLOT(slotSrcUrlModelExpand(QModelIndex))); } void ThumbnailPage::slotSrcUrlModelExpand(const QModelIndex& index) { QModelIndex viewIndex = d->mSrcUrlModelProxyMapper->mapLeftToRight(index); d->mSrcUrlTreeView->expand(viewIndex); KFileItem item = itemForIndex(index); if (item.url() == d->mSrcUrl) { d->mSrcUrlTreeView->selectionModel()->select(viewIndex, QItemSelectionModel::ClearAndSelect); } } void ThumbnailPage::toggleSrcUrlTreeView() { d->mSrcUrlTreeView->setVisible(!d->mSrcUrlTreeView->isVisible()); } void ThumbnailPage::openUrlFromIndex(const QModelIndex& index) { KFileItem item = itemForIndex(index); if (item.isNull()) { return; } QUrl url = item.url(); d->rememberUrl(url); openUrl(url); } } // namespace diff --git a/lib/crop/cropwidget.cpp b/lib/crop/cropwidget.cpp index 67a6d0b3..95dda595 100644 --- a/lib/crop/cropwidget.cpp +++ b/lib/crop/cropwidget.cpp @@ -1,458 +1,458 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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) any later version. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self // Qt #include #include #include #include #include #include // KDE #include // Local #include #include #include "croptool.h" #include "ui_cropwidget.h" #include "cropwidget.h" namespace Gwenview { // Euclidean algorithm to compute the greatest common divisor of two integers. // Found at: // http://en.wikipedia.org/wiki/Euclidean_algorithm static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } static QSize ratio(const QSize &size) { const int divisor = gcd(size.width(), size.height()); return size / divisor; } struct CropWidgetPrivate : public Ui_CropWidget { CropWidget* q; Document::Ptr mDocument; CropTool* mCropTool; bool mUpdatingFromCropTool; int mCurrentImageComboBoxIndex; int mCropRatioComboBoxCurrentIndex; bool ratioIsConstrained() const { return cropRatio() > 0; } QSizeF chosenRatio() const { // A size of 0 represents no ratio, i.e. the combobox is empty if (ratioComboBox->currentText().isEmpty()) { return QSizeF(0, 0); } // A preset ratio is selected const int index = ratioComboBox->currentIndex(); if (index != -1 && ratioComboBox->currentText() == ratioComboBox->itemText(index)) { return ratioComboBox->currentData().toSizeF(); } // A custom ratio has been entered, extract ratio from the text // If invalid, return zero size instead const QStringList lst = ratioComboBox->currentText().split(QLatin1Char(':')); if (lst.size() != 2) { return QSizeF(0, 0); } bool ok; const double width = lst[0].toDouble(&ok); if (!ok) { return QSizeF(0, 0); } const double height = lst[1].toDouble(&ok); if (!ok) { return QSizeF(0, 0); } // Valid custom value return QSizeF(width, height); } void setChosenRatio(QSizeF size) const { // Size matches preset ratio, let's set the combobox to that const int index = ratioComboBox->findData(size); if (index >= 0) { ratioComboBox->setCurrentIndex(index); return; } // Deselect whatever was selected if anything ratioComboBox->setCurrentIndex(-1); // If size is 0 (represents blank combobox, i.e., unrestricted) if (size.isEmpty()) { ratioComboBox->clearEditText(); return; } // Size must be custom ratio, convert to text and add to combobox QString ratioString = QStringLiteral("%1:%2").arg(size.width()).arg(size.height()); ratioComboBox->setCurrentText(ratioString); } double cropRatio() const { if (q->advancedSettingsEnabled()) { QSizeF size = chosenRatio(); if (size.isEmpty()) { return 0; } return size.height() / size.width(); } if (q->preserveAspectRatio()) { QSizeF size = ratio(mDocument->size()); return size.height() / size.width(); } return 0; } void addRatioToComboBox(const QSizeF& size, const QString& label = QString()) { QString text = label.isEmpty() ? QStringLiteral("%1:%2").arg(size.width()).arg(size.height()) : label; ratioComboBox->addItem(text, QVariant(size)); } void addSectionHeaderToComboBox(const QString& title) { // Insert a line ratioComboBox->insertSeparator(ratioComboBox->count()); // Insert our section header // This header is made of a separator with a text. We reset // Qt::AccessibleDescriptionRole to the header text otherwise QComboBox // delegate will draw a separator line instead of our text. int index = ratioComboBox->count(); ratioComboBox->insertSeparator(index); ratioComboBox->setItemText(index, title); ratioComboBox->setItemData(index, title, Qt::AccessibleDescriptionRole); ratioComboBox->setItemData(index, Qt::AlignHCenter, Qt::TextAlignmentRole); } void initRatioComboBox() { QList ratioList; const qreal sqrt2 = qSqrt(2.); ratioList << QSizeF(16, 9) << QSizeF(7, 5) << QSizeF(3, 2) << QSizeF(4, 3) << QSizeF(5, 4); addRatioToComboBox(ratio(mDocument->size()), i18n("Current Image")); mCurrentImageComboBoxIndex = ratioComboBox->count() - 1; // We need to refer to this ratio later addRatioToComboBox(QSizeF(1, 1), i18n("Square")); addRatioToComboBox(ratio(QApplication::desktop()->screenGeometry().size()), i18n("This Screen")); addSectionHeaderToComboBox(i18n("Landscape")); Q_FOREACH(const QSizeF& size, ratioList) { addRatioToComboBox(size); } addRatioToComboBox(QSizeF(sqrt2, 1), i18n("ISO (A4, A3...)")); addRatioToComboBox(QSizeF(11, 8.5), i18n("US Letter")); addSectionHeaderToComboBox(i18n("Portrait")); Q_FOREACH(QSizeF size, ratioList) { size.transpose(); addRatioToComboBox(size); } addRatioToComboBox(QSizeF(1, sqrt2), i18n("ISO (A4, A3...)")); addRatioToComboBox(QSizeF(8.5, 11), i18n("US Letter")); ratioComboBox->setMaxVisibleItems(ratioComboBox->count()); ratioComboBox->clearEditText(); QLineEdit* edit = qobject_cast(ratioComboBox->lineEdit()); Q_ASSERT(edit); // Do not use i18n("%1:%2") because ':' should not be translated, it is // used to parse the ratio string. - edit->setPlaceholderText(QStringLiteral("%1:%2").arg(i18n("Width")).arg(i18n("Height"))); + edit->setPlaceholderText(QStringLiteral("%1:%2").arg(i18n("Width"), i18n("Height"))); // Enable clear button edit->setClearButtonEnabled(true); // Must manually adjust minimum width because the auto size adjustment doesn't take the // clear button into account const int width = ratioComboBox->minimumSizeHint().width(); ratioComboBox->setMinimumWidth(width + 24); mCropRatioComboBoxCurrentIndex = -1; ratioComboBox->setCurrentIndex(mCropRatioComboBoxCurrentIndex); } QRect cropRect() const { QRect rect( leftSpinBox->value(), topSpinBox->value(), widthSpinBox->value(), heightSpinBox->value() ); return rect; } void initSpinBoxes() { QSize size = mDocument->size(); leftSpinBox->setMaximum(size.width()); widthSpinBox->setMaximum(size.width()); topSpinBox->setMaximum(size.height()); heightSpinBox->setMaximum(size.height()); // When users change the crop rectangle, QSpinBox::setMaximum will be called // again, which then adapts the sizeHint due to a different maximum number // of digits, leading to horizontal movement in the layout. This can be // avoided by setting the minimum width so it fits the largest value possible. leftSpinBox->setMinimumWidth(leftSpinBox->sizeHint().width()); widthSpinBox->setMinimumWidth(widthSpinBox->sizeHint().width()); topSpinBox->setMinimumWidth(topSpinBox->sizeHint().width()); heightSpinBox->setMinimumWidth(heightSpinBox->sizeHint().width()); } void initDialogButtonBox() { QPushButton* cropButton = dialogButtonBox->button(QDialogButtonBox::Ok); cropButton->setIcon(QIcon::fromTheme(QStringLiteral("transform-crop-and-resize"))); cropButton->setText(i18n("Crop")); QObject::connect(dialogButtonBox, &QDialogButtonBox::accepted, q, &CropWidget::cropRequested); QObject::connect(dialogButtonBox, &QDialogButtonBox::rejected, q, &CropWidget::done); } }; CropWidget::CropWidget(QWidget* parent, RasterImageView* imageView, CropTool* cropTool) : QWidget(parent) , d(new CropWidgetPrivate) { setWindowFlags(Qt::Tool); d->q = this; d->mDocument = imageView->document(); d->mUpdatingFromCropTool = false; d->mCropTool = cropTool; d->setupUi(this); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); layout()->setSizeConstraint(QLayout::SetFixedSize); connect(d->advancedCheckBox, &QCheckBox::toggled, this, &CropWidget::slotAdvancedCheckBoxToggled); d->advancedWidget->setVisible(false); d->advancedWidget->layout()->setMargin(0); connect(d->preserveAspectRatioCheckBox, &QCheckBox::toggled, this, &CropWidget::applyRatioConstraint); d->initRatioComboBox(); connect(d->mCropTool, &CropTool::rectUpdated, this, &CropWidget::setCropRect); connect(d->leftSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &CropWidget::slotPositionChanged); connect(d->topSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &CropWidget::slotPositionChanged); connect(d->widthSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &CropWidget::slotWidthChanged); connect(d->heightSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &CropWidget::slotHeightChanged); d->initDialogButtonBox(); // We need to listen for both signals because the combobox is multi-function: // Text Changed: required so that manual ratio entry is detected (index doesn't change) // Index Changed: required so that choosing an item with the same text is detected (e.g. going from US Letter portrait // to US Letter landscape) connect(d->ratioComboBox, &QComboBox::editTextChanged, this, &CropWidget::slotRatioComboBoxChanged); connect(d->ratioComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &CropWidget::slotRatioComboBoxChanged); // Don't do this before signals are connected, otherwise the tool won't get // initialized d->initSpinBoxes(); setCropRect(d->mCropTool->rect()); } CropWidget::~CropWidget() { delete d; } void CropWidget::setAdvancedSettingsEnabled(bool enable) { d->advancedCheckBox->setChecked(enable); } bool CropWidget::advancedSettingsEnabled() const { return d->advancedCheckBox->isChecked(); } void CropWidget::setPreserveAspectRatio(bool preserve) { d->preserveAspectRatioCheckBox->setChecked(preserve); } bool CropWidget::preserveAspectRatio() const { return d->preserveAspectRatioCheckBox->isChecked(); } void CropWidget::setCropRatio(QSizeF size) { d->setChosenRatio(size); } QSizeF CropWidget::cropRatio() const { return d->chosenRatio(); } void CropWidget::setCropRatioIndex(int index) { d->ratioComboBox->setCurrentIndex(index); } int CropWidget::cropRatioIndex() const { return d->mCropRatioComboBoxCurrentIndex; } void CropWidget::setCropRect(const QRect& rect) { d->mUpdatingFromCropTool = true; d->leftSpinBox->setValue(rect.left()); d->topSpinBox->setValue(rect.top()); d->widthSpinBox->setValue(rect.width()); d->heightSpinBox->setValue(rect.height()); d->mUpdatingFromCropTool = false; } void CropWidget::slotPositionChanged() { const QSize size = d->mDocument->size(); d->widthSpinBox->setMaximum(size.width() - d->leftSpinBox->value()); d->heightSpinBox->setMaximum(size.height() - d->topSpinBox->value()); if (d->mUpdatingFromCropTool) { return; } d->mCropTool->setRect(d->cropRect()); } void CropWidget::slotWidthChanged() { d->leftSpinBox->setMaximum(d->mDocument->width() - d->widthSpinBox->value()); if (d->mUpdatingFromCropTool) { return; } if (d->ratioIsConstrained()) { int height = int(d->widthSpinBox->value() * d->cropRatio()); d->heightSpinBox->setValue(height); } d->mCropTool->setRect(d->cropRect()); } void CropWidget::slotHeightChanged() { d->topSpinBox->setMaximum(d->mDocument->height() - d->heightSpinBox->value()); if (d->mUpdatingFromCropTool) { return; } if (d->ratioIsConstrained()) { int width = int(d->heightSpinBox->value() / d->cropRatio()); d->widthSpinBox->setValue(width); } d->mCropTool->setRect(d->cropRect()); } void CropWidget::applyRatioConstraint() { double ratio = d->cropRatio(); d->mCropTool->setCropRatio(ratio); if (!d->ratioIsConstrained()) { return; } QRect rect = d->cropRect(); rect.setHeight(int(rect.width() * ratio)); d->mCropTool->setRect(rect); } void CropWidget::slotAdvancedCheckBoxToggled(bool checked) { d->advancedWidget->setVisible(checked); d->preserveAspectRatioCheckBox->setVisible(!checked); applyRatioConstraint(); } void CropWidget::slotRatioComboBoxChanged() { const QString text = d->ratioComboBox->currentText(); // If text cleared, clear the current item as well if (text.isEmpty()) { d->ratioComboBox->setCurrentIndex(-1); } // We want to keep track of the selected ratio, including when the user has entered a custom ratio // or cleared the text. We can't simply use currentIndex() because this stays >= 0 when the user manually // enters text. We also can't set the current index to -1 when there is no match like above because that // interferes when manually entering text. // Furthermore, since there can be duplicate text items, we can't rely on findText() as it will stop on // the first match it finds. Therefore we must check if there's a match, and if so, get the index directly. if (d->ratioComboBox->findText(text) >= 0) { d->mCropRatioComboBoxCurrentIndex = d->ratioComboBox->currentIndex(); } else { d->mCropRatioComboBoxCurrentIndex = -1; } applyRatioConstraint(); } void CropWidget::updateCropRatio() { // First we need to re-calculate the "Current Image" ratio in case the user rotated the image d->ratioComboBox->setItemData(d->mCurrentImageComboBoxIndex, QVariant(ratio(d->mDocument->size()))); // Always re-apply the constraint, even though we only need to when the user has "Current Image" // selected or the "Preserve aspect ratio" checked, since there's no harm applyRatioConstraint(); // If the ratio is unrestricted, calling applyRatioConstraint doesn't update the rect, so we call // this manually to make sure the rect is adjusted to fit within the image d->mCropTool->setRect(d->mCropTool->rect()); } } // namespace diff --git a/lib/documentview/messageviewadapter.cpp b/lib/documentview/messageviewadapter.cpp index 3352b741..8c42cf50 100644 --- a/lib/documentview/messageviewadapter.cpp +++ b/lib/documentview/messageviewadapter.cpp @@ -1,130 +1,130 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2008 Aurélien Gâteau 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) any later version. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "messageviewadapter.h" // Qt #include #include // KDE #include // Local #include #include namespace Gwenview { struct MessageViewAdapterPrivate : Ui_MessageView { Document::Ptr mDocument; }; MessageViewAdapter::MessageViewAdapter() : d(new MessageViewAdapterPrivate) { QWidget* widget = new QWidget; widget->installEventFilter(this); d->setupUi(widget); d->mMessageWidget->setCloseButtonVisible(false); d->mMessageWidget->setWordWrap(true); setInfoMessage(i18n("No document selected")); widget->setAutoFillBackground(true); widget->setBackgroundRole(QPalette::Base); widget->setForegroundRole(QPalette::Text); QGraphicsProxyWidget* proxy = new QGraphicsProxyWidget; proxy->setWidget(widget); setWidget(proxy); } MessageViewAdapter::~MessageViewAdapter() { delete d; } void MessageViewAdapter::setErrorMessage(const QString& main, const QString& detail) { if (main.isEmpty()) { d->mMessageWidget->hide(); return; } d->mMessageWidget->show(); d->mMessageWidget->setMessageType(KMessageWidget::Error); QString message; if (detail.isEmpty()) { message = main; } else { - message = QStringLiteral("%1
%2").arg(main).arg(detail); + message = QStringLiteral("%1
%2").arg(main, detail); } d->mMessageWidget->setText(message); } void MessageViewAdapter::setInfoMessage(const QString& message) { if (message.isEmpty()) { d->mMessageWidget->hide(); return; } d->mMessageWidget->show(); d->mMessageWidget->setMessageType(KMessageWidget::Information); d->mMessageWidget->setText(message); } Document::Ptr MessageViewAdapter::document() const { return d->mDocument; } void MessageViewAdapter::setDocument(Document::Ptr doc) { d->mDocument = doc; } bool MessageViewAdapter::eventFilter(QObject*, QEvent* ev) { if (ev->type() == QEvent::KeyPress) { QKeyEvent* event = static_cast(ev); if (event->modifiers() != Qt::NoModifier) { return false; } switch (event->key()) { case Qt::Key_Left: case Qt::Key_Up: emit previousImageRequested(); break; case Qt::Key_Right: case Qt::Key_Down: emit nextImageRequested(); break; default: break; } } return false; } } // namespace diff --git a/lib/stylesheetutils.cpp b/lib/stylesheetutils.cpp index 67920f26..bd92b4cd 100644 --- a/lib/stylesheetutils.cpp +++ b/lib/stylesheetutils.cpp @@ -1,69 +1,68 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau Copyright 2018 Huon Imberger 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) any later version. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "stylesheetutils.h" // Qt // KDE // Local #include "paintutils.h" namespace Gwenview { namespace StyleSheetUtils { QString rgba(const QColor &color) { return QString::fromLocal8Bit("rgba(%1, %2, %3, %4)") .arg(color.red()) .arg(color.green()) .arg(color.blue()) .arg(color.alpha()); } QString gradient(Qt::Orientation orientation, const QColor &color, int value) { int x2, y2; if (orientation == Qt::Horizontal) { x2 = 0; y2 = 1; } else { x2 = 1; y2 = 0; } QString grad = QStringLiteral("qlineargradient(x1:0, y1:0, x2:%1, y2:%2," "stop:0 %3, stop: 1 %4)"); return grad .arg(x2) .arg(y2) - .arg(rgba(PaintUtils::adjustedHsv(color, 0, 0, qMin(255 - color.value(), value / 2)))) - .arg(rgba(PaintUtils::adjustedHsv(color, 0, 0, -qMin(color.value(), value / 2)))) - ; + .arg(rgba(PaintUtils::adjustedHsv(color, 0, 0, qMin(255 - color.value(), value / 2))), + rgba(PaintUtils::adjustedHsv(color, 0, 0, -qMin(color.value(), value / 2)))); } } // namespace } // namespace diff --git a/tests/auto/placetreemodeltest.cpp b/tests/auto/placetreemodeltest.cpp index b0d850ba..6a6c7d37 100644 --- a/tests/auto/placetreemodeltest.cpp +++ b/tests/auto/placetreemodeltest.cpp @@ -1,167 +1,164 @@ /* Gwenview: an image viewer Copyright 2009 Aurélien Gâteau 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) any later version. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "placetreemodeltest.h" // Qt #include #include // KDE #include #include #include #include // Local #include "../lib/placetreemodel.h" #include "testutils.h" //#define KEEP_TEMP_DIR QTEST_MAIN(PlaceTreeModelTest) using namespace Gwenview; const char* BOOKMARKS_XML = "" "" "" " " " url1" " " " " " " " " " " " 1214343736/0" " true" " " " " " " " " " url2" " " " " " " " " " " " 1214343736/1" " true" " " " " " " ""; void PlaceTreeModelTest::initTestCase() { Q_ASSERT(mTempDir.isValid()); QDir dir(mTempDir.path()); const bool dir1created = dir.mkdir("url1"); Q_ASSERT(dir1created); Q_UNUSED(dir1created); mUrl1 = QUrl::fromLocalFile(dir.filePath("url1")); const bool dir2created = dir.mkdir("url2"); Q_ASSERT(dir2created); Q_UNUSED(dir2created); mUrl2 = QUrl::fromLocalFile(dir.filePath("url2")); mUrl1Dirs << "aaa" << "zzz" << "bbb"; Q_FOREACH(const QString & dirName, mUrl1Dirs) { dir.mkdir("url1/" + dirName); } #ifdef KEEP_TEMP_DIR mTempDir.setAutoRemove(false); //qDebug() << "mTempDir:" << mTempDir.name(); #endif } void PlaceTreeModelTest::init() { QStandardPaths::setTestModeEnabled(true); TestUtils::purgeUserConfiguration(); const QString confDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); QDir().mkpath(confDir); QFile bookmark(confDir + "/user-places.xbel"); const bool bookmarkOpened = bookmark.open(QIODevice::WriteOnly); Q_ASSERT(bookmarkOpened); Q_UNUSED(bookmarkOpened); - QString xml = QString(BOOKMARKS_XML) - .arg(mUrl1.url()) - .arg(mUrl2.url()) - ; + QString xml = QString(BOOKMARKS_XML).arg(mUrl1.url(), mUrl2.url()); bookmark.write(xml.toUtf8()); #ifdef KEEP_TEMP_DIR mTempDir.setAutoRemove(false); //qDebug() << "mTempDir:" << mTempDir.name(); #endif } void PlaceTreeModelTest::testListPlaces() { PlaceTreeModel model(nullptr); #if KIO_VERSION >= QT_VERSION_CHECK(5, 45, 0) QCOMPARE(model.rowCount(), 8); #else QCOMPARE(model.rowCount(), 10); #endif QModelIndex index; index = model.index(0, 0); QCOMPARE(model.urlForIndex(index), mUrl1); index = model.index(1, 0); QCOMPARE(model.urlForIndex(index), mUrl2); } void PlaceTreeModelTest::testListUrl1() { PlaceTreeModel model(nullptr); QModelIndex index = model.index(0, 0); QCOMPARE(model.urlForIndex(index), mUrl1); // We should not have fetched content yet QCOMPARE(model.rowCount(index), 0); QVERIFY(model.canFetchMore(index)); while (model.canFetchMore(index)) { model.fetchMore(index); } QTest::qWait(1000); QCOMPARE(model.rowCount(index), mUrl1Dirs.length()); QStringList dirs = mUrl1Dirs; dirs.sort(); for (int row = 0; row < dirs.count(); ++row) { QModelIndex subIndex = model.index(row, 0, index); QVERIFY(subIndex.isValid()); QString dirName = model.data(subIndex).toString(); QCOMPARE(dirName, dirs.value(row)); } }