/*************************************************************************** qgslayoutmanagerdialog.cpp ----------------------- begin : December 2017 copyright : (C) 2017 by Nyall Dawson email : nyall dot dawson at gmail dot com ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "qgslayoutmanagerdialog.h" #include "qgisapp.h" #include "qgsapplication.h" #include "qgsbusyindicatordialog.h" #include "qgslayoutdesignerdialog.h" #include "qgslayout.h" #include "qgslogger.h" #include "qgssettings.h" #include "qgslayoutview.h" #include "qgslayoutmanager.h" #include "qgsproject.h" #include "qgsgui.h" #include "qgsprintlayout.h" #include "qgsreport.h" #include #include #include #include #include #include #include #include QgsLayoutManagerDialog::QgsLayoutManagerDialog( QWidget *parent, Qt::WindowFlags f ): QDialog( parent, f ) { setupUi( this ); connect( mAddButton, &QPushButton::clicked, this, &QgsLayoutManagerDialog::mAddButton_clicked ); connect( mTemplate, static_cast( &QComboBox::currentIndexChanged ), this, &QgsLayoutManagerDialog::mTemplate_currentIndexChanged ); connect( mTemplatesDefaultDirBtn, &QPushButton::pressed, this, &QgsLayoutManagerDialog::mTemplatesDefaultDirBtn_pressed ); connect( mTemplatesUserDirBtn, &QPushButton::pressed, this, &QgsLayoutManagerDialog::mTemplatesUserDirBtn_pressed ); QgsGui::instance()->enableAutoGeometryRestore( this ); mTemplateFileWidget->setStorageMode( QgsFileWidget::GetFile ); mTemplateFileWidget->setFilter( tr( "Layout templates" ) + QStringLiteral( " (*.qpt *.QPT)" ) ); mTemplateFileWidget->setDialogTitle( tr( "Select a Template" ) ); mTemplateFileWidget->lineEdit()->setShowClearButton( false ); QgsSettings settings; mTemplateFileWidget->setDefaultRoot( settings.value( QStringLiteral( "UI/lastComposerTemplateDir" ), QString() ).toString() ); mTemplateFileWidget->setFilePath( settings.value( QStringLiteral( "UI/ComposerManager/templatePath" ), QString() ).toString() ); connect( mTemplateFileWidget, &QgsFileWidget::fileChanged, this, [ = ] { QgsSettings settings; settings.setValue( QStringLiteral( "UI/ComposerManager/templatePath" ), mTemplateFileWidget->filePath() ); QFileInfo tmplFileInfo( mTemplateFileWidget->filePath() ); settings.setValue( QStringLiteral( "UI/lastComposerTemplateDir" ), tmplFileInfo.absolutePath() ); } ); mModel = new QgsLayoutManagerModel( QgsProject::instance()->layoutManager(), this ); mLayoutListView->setModel( mModel ); connect( mButtonBox, &QDialogButtonBox::rejected, this, &QWidget::close ); connect( mLayoutListView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsLayoutManagerDialog::toggleButtons ); connect( mLayoutListView, &QListView::doubleClicked, this, &QgsLayoutManagerDialog::itemDoubleClicked ); mShowButton = mButtonBox->addButton( tr( "&Show" ), QDialogButtonBox::ActionRole ); connect( mShowButton, &QAbstractButton::clicked, this, &QgsLayoutManagerDialog::showClicked ); mCreateReportButton = mButtonBox->addButton( tr( "Create &Report" ), QDialogButtonBox::ActionRole ); connect( mCreateReportButton, &QAbstractButton::clicked, this, &QgsLayoutManagerDialog::createReport ); mDuplicateButton = mButtonBox->addButton( tr( "&Duplicate" ), QDialogButtonBox::ActionRole ); connect( mDuplicateButton, &QAbstractButton::clicked, this, &QgsLayoutManagerDialog::duplicateClicked ); mRemoveButton = mButtonBox->addButton( tr( "&Remove" ), QDialogButtonBox::ActionRole ); connect( mRemoveButton, &QAbstractButton::clicked, this, &QgsLayoutManagerDialog::removeClicked ); mRenameButton = mButtonBox->addButton( tr( "Re&name" ), QDialogButtonBox::ActionRole ); connect( mRenameButton, &QAbstractButton::clicked, this, &QgsLayoutManagerDialog::renameClicked ); #ifdef Q_OS_MAC // Create action to select this window mWindowAction = new QAction( windowTitle(), this ); connect( mWindowAction, SIGNAL( triggered() ), this, SLOT( activate() ) ); #endif mTemplate->addItem( tr( "Empty layout" ) ); mTemplate->addItem( tr( "Specific" ) ); mUserTemplatesDir = QgsApplication::qgisSettingsDirPath() + "/composer_templates"; QMap userTemplateMap = defaultTemplates( true ); this->addTemplates( userTemplateMap ); mDefaultTemplatesDir = QgsApplication::pkgDataPath() + "/composer_templates"; QMap defaultTemplateMap = defaultTemplates( false ); this->addTemplates( defaultTemplateMap ); this->addTemplates( this->otherTemplates() ); toggleButtons(); } void QgsLayoutManagerDialog::toggleButtons() { // Nothing selected: no button. if ( mLayoutListView->selectionModel()->selectedRows().isEmpty() ) { mShowButton->setEnabled( false ); mRemoveButton->setEnabled( false ); mRenameButton->setEnabled( false ); mDuplicateButton->setEnabled( false ); } // toggle everything if one layout is selected else if ( mLayoutListView->selectionModel()->selectedRows().count() == 1 ) { mShowButton->setEnabled( true ); mRemoveButton->setEnabled( true ); mRenameButton->setEnabled( true ); mDuplicateButton->setEnabled( true ); } // toggle only show and remove buttons in other cases else { mShowButton->setEnabled( true ); mRemoveButton->setEnabled( true ); mRenameButton->setEnabled( false ); mDuplicateButton->setEnabled( false ); } } void QgsLayoutManagerDialog::addTemplates( const QMap &templates ) { if ( !templates.isEmpty() ) { mTemplate->insertSeparator( mTemplate->count() ); QMap::const_iterator templateIt = templates.constBegin(); for ( ; templateIt != templates.constEnd(); ++templateIt ) { mTemplate->addItem( templateIt.key(), templateIt.value() ); } } } void QgsLayoutManagerDialog::activate() { raise(); setWindowState( windowState() & ~Qt::WindowMinimized ); activateWindow(); } QMap QgsLayoutManagerDialog::defaultTemplates( bool fromUser ) const { //search for default templates in $pkgDataPath/composer_templates // user templates in $qgisSettingsDirPath/composer_templates return templatesFromPath( fromUser ? mUserTemplatesDir : mDefaultTemplatesDir ); } QMap QgsLayoutManagerDialog::otherTemplates() const { QMap templateMap; QStringList paths = QgsApplication::composerTemplatePaths(); Q_FOREACH ( const QString &path, paths ) { QMap templates = templatesFromPath( path ); QMap::const_iterator templateIt = templates.constBegin(); for ( ; templateIt != templates.constEnd(); ++templateIt ) { templateMap.insert( templateIt.key(), templateIt.value() ); } } return templateMap; } QMap QgsLayoutManagerDialog::templatesFromPath( const QString &path ) const { QMap templateMap; QDir templateDir( path ); if ( !templateDir.exists() ) { return templateMap; } QFileInfoList fileInfoList = templateDir.entryInfoList( QDir::Files ); QFileInfoList::const_iterator infoIt = fileInfoList.constBegin(); for ( ; infoIt != fileInfoList.constEnd(); ++infoIt ) { if ( infoIt->suffix().toLower() == QLatin1String( "qpt" ) ) { templateMap.insert( infoIt->baseName(), infoIt->absoluteFilePath() ); } } return templateMap; } void QgsLayoutManagerDialog::mAddButton_clicked() { QFile templateFile; bool loadingTemplate = ( mTemplate->currentIndex() > 0 ); QDomDocument templateDoc; QString storedTitle; if ( loadingTemplate ) { if ( mTemplate->currentIndex() == 1 ) { templateFile.setFileName( mTemplateFileWidget->filePath() ); } else { templateFile.setFileName( mTemplate->currentData().toString() ); } if ( !templateFile.exists() ) { QMessageBox::warning( this, tr( "Create layout" ), tr( "Template file “%1” not found." ).arg( templateFile.fileName() ) ); return; } if ( !templateFile.open( QIODevice::ReadOnly ) ) { QMessageBox::warning( this, tr( "Create layout" ), tr( "Could not read template file “%1”." ).arg( templateFile.fileName() ) ); return; } if ( templateDoc.setContent( &templateFile, false ) ) { QDomElement layoutElem = templateDoc.documentElement(); if ( !layoutElem.isNull() ) storedTitle = layoutElem.attribute( "name" ); } } QString title; if ( !QgisApp::instance()->uniqueLayoutTitle( this, title, true, storedTitle ) ) { return; } if ( title.isEmpty() ) { title = QgsProject::instance()->layoutManager()->generateUniqueTitle(); } std::unique_ptr< QgsPrintLayout > layout = qgis::make_unique< QgsPrintLayout >( QgsProject::instance() ); if ( loadingTemplate ) { bool loadedOK = false; ( void )layout->loadFromTemplate( templateDoc, QgsReadWriteContext(), true, &loadedOK ); if ( !loadedOK ) { QMessageBox::warning( this, tr( "Create layout" ), tr( "Invalid template file “%1”." ).arg( templateFile.fileName() ) ); layout.reset(); } } else { layout->initializeDefaults(); } if ( layout ) { layout->setName( title ); QgisApp::instance()->openLayoutDesignerDialog( layout.get() ); QgsProject::instance()->layoutManager()->addLayout( layout.release() ); } } void QgsLayoutManagerDialog::mTemplate_currentIndexChanged( int indx ) { bool specific = ( indx == 1 ); // comes just after empty template mTemplateFileWidget->setEnabled( specific ); } void QgsLayoutManagerDialog::mTemplatesDefaultDirBtn_pressed() { openLocalDirectory( mDefaultTemplatesDir ); } void QgsLayoutManagerDialog::mTemplatesUserDirBtn_pressed() { openLocalDirectory( mUserTemplatesDir ); } void QgsLayoutManagerDialog::createReport() { QString title; if ( !QgisApp::instance()->uniqueLayoutTitle( this, title, true ) ) { return; } if ( title.isEmpty() ) { title = QgsProject::instance()->layoutManager()->generateUniqueTitle(); } std::unique_ptr< QgsReport > report = qgis::make_unique< QgsReport >( QgsProject::instance() ); report->setName( title ); std::unique_ptr< QgsLayout > header = qgis::make_unique< QgsLayout >( QgsProject::instance() ); header->initializeDefaults(); report->setHeader( header.release() ); report->setHeaderEnabled( true ); QgisApp::instance()->openLayoutDesignerDialog( report.get() ); QgsProject::instance()->layoutManager()->addLayout( report.release() ); } void QgsLayoutManagerDialog::openLocalDirectory( const QString &localDirPath ) { QDir localDir; if ( !localDir.mkpath( localDirPath ) ) { QMessageBox::warning( this, tr( "Open directory" ), tr( "Could not open or create local directory “%1”." ).arg( localDirPath ) ); } else { QDesktopServices::openUrl( QUrl::fromLocalFile( localDirPath ) ); } } #ifdef Q_OS_MAC void QgsLayoutManagerDialog::showEvent( QShowEvent *event ) { if ( !event->spontaneous() ) { QgisApp::instance()->addWindow( mWindowAction ); } } void QgsLayoutManagerDialog::changeEvent( QEvent *event ) { QDialog::changeEvent( event ); switch ( event->type() ) { case QEvent::ActivationChange: if ( QApplication::activeWindow() == this ) { mWindowAction->setChecked( true ); } break; default: break; } } #endif void QgsLayoutManagerDialog::removeClicked() { const QModelIndexList layoutItems = mLayoutListView->selectionModel()->selectedRows(); if ( layoutItems.isEmpty() ) { return; } QString title; QString message; if ( layoutItems.count() == 1 ) { title = tr( "Remove layout" ); message = tr( "Do you really want to remove the print layout “%1”?" ).arg( mLayoutListView->model()->data( layoutItems.at( 0 ), Qt::DisplayRole ).toString() ); } else { title = tr( "Remove layouts" ); message = tr( "Do you really want to remove all selected print layouts?" ); } if ( QMessageBox::warning( this, title, message, QMessageBox::Ok | QMessageBox::Cancel ) != QMessageBox::Ok ) { return; } QList layoutList; // Find the layouts that need to be deleted for ( const QModelIndex &index : layoutItems ) { QgsMasterLayoutInterface *l = mModel->layoutFromIndex( index ); if ( l ) { layoutList << l; } } // Once we have the layout list, we can delete all of them ! for ( QgsMasterLayoutInterface *l : qgis::as_const( layoutList ) ) { QgsProject::instance()->layoutManager()->removeLayout( l ); } } void QgsLayoutManagerDialog::showClicked() { const QModelIndexList layoutItems = mLayoutListView->selectionModel()->selectedRows(); for ( const QModelIndex &index : layoutItems ) { if ( QgsMasterLayoutInterface *l = mModel->layoutFromIndex( index ) ) { QgisApp::instance()->openLayoutDesignerDialog( l ); } } } void QgsLayoutManagerDialog::duplicateClicked() { if ( mLayoutListView->selectionModel()->selectedRows().isEmpty() ) { return; } QgsMasterLayoutInterface *currentLayout = mModel->layoutFromIndex( mLayoutListView->selectionModel()->selectedRows().at( 0 ) ); if ( !currentLayout ) return; QString currentTitle = currentLayout->name(); QString newTitle; if ( !QgisApp::instance()->uniqueLayoutTitle( this, newTitle, false, tr( "%1 copy" ).arg( currentTitle ) ) ) { return; } // provide feedback, since loading of template into duplicate layout will be hidden QDialog *dlg = new QgsBusyIndicatorDialog( tr( "Duplicating layout…" ) ); dlg->setStyleSheet( QgisApp::instance()->styleSheet() ); dlg->show(); QgsLayoutDesignerDialog *newDialog = QgisApp::instance()->duplicateLayout( currentLayout, newTitle ); dlg->close(); delete dlg; dlg = nullptr; if ( !newDialog ) { QMessageBox::warning( this, tr( "Duplicate layout" ), tr( "Layout duplication failed." ) ); } else { newDialog->activate(); } } void QgsLayoutManagerDialog::renameClicked() { if ( mLayoutListView->selectionModel()->selectedRows().isEmpty() ) { return; } QgsMasterLayoutInterface *currentLayout = mModel->layoutFromIndex( mLayoutListView->selectionModel()->selectedRows().at( 0 ) ); if ( !currentLayout ) return; QString currentTitle = currentLayout->name(); QString newTitle; if ( !QgisApp::instance()->uniqueLayoutTitle( this, newTitle, false, currentTitle ) ) { return; } currentLayout->setName( newTitle ); } void QgsLayoutManagerDialog::itemDoubleClicked( const QModelIndex &index ) { if ( QgsMasterLayoutInterface *l = mModel->layoutFromIndex( index ) ) { QgisApp::instance()->openLayoutDesignerDialog( l ); } } // // QgsLayoutManagerModel // QgsLayoutManagerModel::QgsLayoutManagerModel( QgsLayoutManager *manager, QObject *parent ) : QAbstractListModel( parent ) , mLayoutManager( manager ) { connect( mLayoutManager, &QgsLayoutManager::layoutAboutToBeAdded, this, &QgsLayoutManagerModel::layoutAboutToBeAdded ); connect( mLayoutManager, &QgsLayoutManager::layoutAdded, this, &QgsLayoutManagerModel::layoutAdded ); connect( mLayoutManager, &QgsLayoutManager::layoutAboutToBeRemoved, this, &QgsLayoutManagerModel::layoutAboutToBeRemoved ); connect( mLayoutManager, &QgsLayoutManager::layoutRemoved, this, &QgsLayoutManagerModel::layoutRemoved ); connect( mLayoutManager, &QgsLayoutManager::layoutRenamed, this, &QgsLayoutManagerModel::layoutRenamed ); } int QgsLayoutManagerModel::rowCount( const QModelIndex &parent ) const { Q_UNUSED( parent ); return mLayoutManager->layouts().count(); } QVariant QgsLayoutManagerModel::data( const QModelIndex &index, int role ) const { if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) ) return QVariant(); switch ( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: case Qt::EditRole: return mLayoutManager->layouts().at( index.row() )->name(); case LayoutRole: { if ( QgsLayout *l = dynamic_cast< QgsLayout * >( mLayoutManager->layouts().at( index.row() ) ) ) return QVariant::fromValue( l ); else if ( QgsReport *r = dynamic_cast< QgsReport * >( mLayoutManager->layouts().at( index.row() ) ) ) return QVariant::fromValue( r ); else return QVariant(); } case Qt::DecorationRole: { return mLayoutManager->layouts().at( index.row() )->icon(); } default: return QVariant(); } } bool QgsLayoutManagerModel::setData( const QModelIndex &index, const QVariant &value, int role ) { if ( !index.isValid() || role != Qt::EditRole ) { return false; } if ( index.row() >= mLayoutManager->layouts().count() ) { return false; } if ( value.toString().isEmpty() ) return false; QgsMasterLayoutInterface *layout = layoutFromIndex( index ); if ( !layout ) return false; //has name changed? bool changed = layout->name() != value.toString(); if ( !changed ) return true; //check if name already exists QStringList layoutNames; const QList< QgsMasterLayoutInterface * > layouts = QgsProject::instance()->layoutManager()->layouts(); for ( QgsMasterLayoutInterface *l : layouts ) { layoutNames << l->name(); } if ( layoutNames.contains( value.toString() ) ) { //name exists! QMessageBox::warning( nullptr, tr( "Rename layout" ), tr( "There is already a layout named “%1”." ).arg( value.toString() ) ); return false; } layout->setName( value.toString() ); return true; } Qt::ItemFlags QgsLayoutManagerModel::flags( const QModelIndex &index ) const { Qt::ItemFlags flags = QAbstractListModel::flags( index ); #if 0 // double-click is now used for opening the layout if ( index.isValid() ) { return flags | Qt::ItemIsEditable; } else { return flags; } #endif return flags; } QgsMasterLayoutInterface *QgsLayoutManagerModel::layoutFromIndex( const QModelIndex &index ) const { if ( QgsPrintLayout *l = qobject_cast< QgsPrintLayout * >( qvariant_cast( data( index, LayoutRole ) ) ) ) return l; else if ( QgsReport *r = qobject_cast< QgsReport * >( qvariant_cast( data( index, LayoutRole ) ) ) ) return r; else return nullptr; } void QgsLayoutManagerModel::layoutAboutToBeAdded( const QString & ) { int row = mLayoutManager->layouts().count(); beginInsertRows( QModelIndex(), row, row ); } void QgsLayoutManagerModel::layoutAboutToBeRemoved( const QString &name ) { QgsMasterLayoutInterface *l = mLayoutManager->layoutByName( name ); int row = mLayoutManager->layouts().indexOf( l ); if ( row >= 0 ) beginRemoveRows( QModelIndex(), row, row ); } void QgsLayoutManagerModel::layoutAdded( const QString & ) { endInsertRows(); } void QgsLayoutManagerModel::layoutRemoved( const QString & ) { endRemoveRows(); } void QgsLayoutManagerModel::layoutRenamed( QgsMasterLayoutInterface *layout, const QString & ) { int row = mLayoutManager->layouts().indexOf( layout ); QModelIndex index = createIndex( row, 0 ); emit dataChanged( index, index, QVector() << Qt::DisplayRole ); }