/** * \file basemainwindow.cpp * Base class for main window. * * \b Project: Kid3 * \author Urs Fleisch * \date 9 Jan 2003 * * Copyright (C) 2003-2018 Urs Fleisch * * This file is part of Kid3. * * Kid3 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. * * Kid3 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 "basemainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #include #endif #include "kid3form.h" #include "kid3application.h" #include "framelist.h" #include "frametablemodel.h" #include "frametable.h" #include "importdialog.h" #include "tagimportdialog.h" #include "batchimportdialog.h" #include "browsecoverartdialog.h" #include "exportdialog.h" #include "findreplacedialog.h" #include "tagsearcher.h" #include "numbertracksdialog.h" #include "filterdialog.h" #include "rendirdialog.h" #include "downloadclient.h" #include "downloaddialog.h" #include "playlistdialog.h" #include "playlisteditdialog.h" #include "editframefieldsdialog.h" #include "progresswidget.h" #include "fileproxymodel.h" #include "fileproxymodeliterator.h" #include "modeliterator.h" #include "taggedfileselection.h" #include "filelist.h" #include "pictureframe.h" #include "fileconfig.h" #include "playlistconfig.h" #include "playlistmodel.h" #include "importconfig.h" #include "exportconfig.h" #include "guiconfig.h" #include "tagconfig.h" #include "filterconfig.h" #include "isettings.h" #include "contexthelp.h" #include "frame.h" #include "textexporter.h" #include "serverimporter.h" #include "batchimporter.h" #include "dirrenamer.h" #include "iplatformtools.h" #include "saferename.h" #include "config.h" #ifdef HAVE_QTMULTIMEDIA #include "audioplayer.h" #include "playtoolbar.h" #endif /** * Constructor. * * @param mainWin main window widget * @param platformTools platform specific tools * @param app application context */ BaseMainWindowImpl::BaseMainWindowImpl(QMainWindow* mainWin, IPlatformTools* platformTools, Kid3Application* app) : m_platformTools(platformTools), m_w(mainWin), m_self(nullptr), m_deferredItemCountTimer(new QTimer(this)), m_deferredSelectionCountTimer(new QTimer(this)), m_statusLabel(nullptr), m_form(nullptr), m_app(app), m_exportDialog(nullptr), m_findReplaceDialog(nullptr), m_downloadDialog(new DownloadDialog(m_w, tr("Download"))), m_progressWidget(nullptr), m_progressLabel(nullptr), m_progressBar(nullptr), m_progressAbortButton(nullptr), m_editFrameDialog(nullptr), m_playToolBar(nullptr), m_editFrameTaggedFile(nullptr), m_editFrameTagNr(Frame::Tag_2), m_progressTerminationHandler(nullptr), m_folderCount(0), m_fileCount(0), m_selectionCount(0), m_progressDisconnected(false), m_findReplaceActive(false), m_expandNotificationNeeded(false) { m_deferredItemCountTimer->setSingleShot(true); m_deferredItemCountTimer->setInterval(1000); connect(m_deferredItemCountTimer, &QTimer::timeout, this, &BaseMainWindowImpl::onItemCountChanged); m_deferredSelectionCountTimer->setSingleShot(true); m_deferredSelectionCountTimer->setInterval(500); connect(m_deferredSelectionCountTimer, &QTimer::timeout, this, &BaseMainWindowImpl::onSelectionCountChanged); m_downloadDialog->close(); ContextHelp::init(m_platformTools); DownloadClient* downloadClient = m_app->getDownloadClient(); connect(downloadClient, &HttpClient::progress, m_downloadDialog, &DownloadDialog::updateProgressStatus); connect(downloadClient, &DownloadClient::downloadStarted, m_downloadDialog, &DownloadDialog::showStartOfDownload); connect(downloadClient, &DownloadClient::aborted, m_downloadDialog, &QProgressDialog::reset); connect(m_downloadDialog, &QProgressDialog::canceled, downloadClient, &DownloadClient::cancelDownload); connect(downloadClient, &DownloadClient::downloadFinished, m_app, &Kid3Application::imageDownloaded); connect(m_app, &Kid3Application::fileSelectionUpdateRequested, this, &BaseMainWindowImpl::updateCurrentSelection); connect(m_app, &Kid3Application::selectedFilesUpdated, this, &BaseMainWindowImpl::updateGuiControls); connect(m_app, &Kid3Application::selectedFilesChanged, this, &BaseMainWindowImpl::applySelectionChange); connect(m_app, &Kid3Application::frameModified, this, &BaseMainWindowImpl::updateAfterFrameModification); connect(m_app, &Kid3Application::confirmedOpenDirectoryRequested, this, &BaseMainWindowImpl::confirmedOpenDirectory); connect(m_app, &Kid3Application::toggleExpandedRequested, this, &BaseMainWindowImpl::toggleExpanded); connect(m_app, &Kid3Application::expandFileListRequested, this, &BaseMainWindowImpl::expandFileList); connect(m_app, &Kid3Application::directoryOpened, this, &BaseMainWindowImpl::onDirectoryOpened); connect(m_app, &Kid3Application::modifiedChanged, this, &BaseMainWindowImpl::updateWindowCaption); connect(m_app, &Kid3Application::filteredChanged, this, &BaseMainWindowImpl::updateWindowCaption); connect(m_app, &Kid3Application::longRunningOperationProgress, this, &BaseMainWindowImpl::showOperationProgress); connect(m_app, &Kid3Application::aboutToPlayAudio, this, &BaseMainWindowImpl::showPlayToolBar); } /** * Destructor. */ BaseMainWindowImpl::~BaseMainWindowImpl() { qDeleteAll(m_playlistEditDialogs); #ifdef HAVE_QTMULTIMEDIA delete m_playToolBar; #endif } /** * Initialize main window. * Shall be called at end of constructor body. */ void BaseMainWindowImpl::init() { m_statusLabel = new QLabel; m_w->statusBar()->addWidget(m_statusLabel); m_form = new Kid3Form(m_app, this, m_w); m_w->setCentralWidget(m_form); m_self->initActions(); m_w->resize(m_w->sizeHint()); readOptions(); applyChangedShortcuts(); } /** * Open directory, user has to confirm if current directory modified. * * @param paths directory or file paths */ void BaseMainWindowImpl::confirmedOpenDirectory(const QStringList& paths) { if (!saveModified()) { return; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); slotStatusMsg(tr("Opening folder...")); m_app->openDirectory(paths, false); slotClearStatusMsg(); QApplication::restoreOverrideCursor(); } /** * Update the recent file list and the caption when a new directory * is opened. */ void BaseMainWindowImpl::onDirectoryOpened() { m_self->addDirectoryToRecentFiles(m_app->getDirName()); updateWindowCaption(); } /** * Save application options. */ void BaseMainWindowImpl::saveOptions() { m_self->saveConfig(); m_form->saveConfig(); m_app->saveConfig(); } /** * Load application options. */ void BaseMainWindowImpl::readOptions() { m_app->readConfig(); m_self->readConfig(); m_form->readConfig(); } /** * Show progress of long running operation in status bar. * @param name name of operation * @param done amount of work done * @param total total amount of work * @param abort if not 0, can be set to true to abort operation */ void BaseMainWindowImpl::showOperationProgress(const QString& name, int done, int total, bool* abort) { if (done == -1) { // Operation started. if (!m_progressLabel) { m_progressLabel = new QLabel; } if (!m_progressBar) { m_progressBar = new QProgressBar; } if (!m_progressAbortButton) { m_progressAbortButton = new QToolButton; m_progressAbortButton->setIcon( QIcon(m_w->style()->standardIcon(QStyle::SP_BrowserStop))); m_progressAbortButton->setToolTip(tr("Abort")); m_progressAbortButton->setCheckable(true); } if (m_statusLabel) { m_w->statusBar()->removeWidget(m_statusLabel); } m_w->statusBar()->addPermanentWidget(m_progressLabel); m_w->statusBar()->addPermanentWidget(m_progressBar, 1); m_w->statusBar()->addPermanentWidget(m_progressAbortButton, 1); m_progressLabel->setText(name); m_progressBar->setMinimum(0); m_progressBar->setMaximum(total); m_progressBar->setValue(0); m_progressAbortButton->setChecked(false); } else if (done == total && total != 0) { // Operation finished. if (m_progressLabel) { m_w->statusBar()->removeWidget(m_progressLabel); delete m_progressLabel; m_progressLabel = nullptr; } if (m_progressBar) { m_w->statusBar()->removeWidget(m_progressBar); delete m_progressBar; m_progressBar = nullptr; } if (m_progressAbortButton) { m_w->statusBar()->removeWidget(m_progressAbortButton); delete m_progressAbortButton; m_progressAbortButton = nullptr; if (m_statusLabel) { m_w->statusBar()->addWidget(m_statusLabel); m_statusLabel->show(); } } slotClearStatusMsg(); } else if (done < total || (done == 0 && total == 0)) { // Operation progress. if (m_progressBar) { m_progressBar->setMaximum(total); m_progressBar->setValue(done); // Is needed to get abort button events. qApp->processEvents(); } if (m_progressAbortButton && m_progressAbortButton->isChecked() && abort) { *abort = true; } } } /** * Save all changed files. * * @param updateGui true to update GUI (controls, status, cursor) */ void BaseMainWindowImpl::saveDirectory(bool updateGui) { if (updateGui) { updateCurrentSelection(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } #if defined Q_OS_WIN32 && defined HAVE_QTMULTIMEDIA // Close player on Windows because it holds file handles which prevent // files from being saved. if (m_playToolBar) { m_playToolBar->close(); delete m_playToolBar; m_playToolBar = nullptr; } m_app->deleteAudioPlayer(); #endif const QStringList errorFiles = m_app->saveDirectory(); if (!errorFiles.empty()) { QStringList errorMsgs, notWritableFiles; errorMsgs.reserve(errorFiles.size()); for (const QString& filePath : errorFiles) { QFileInfo fileInfo(filePath); if (!fileInfo.isWritable()) { errorMsgs.append(tr("%1 is not writable").arg(fileInfo.fileName())); notWritableFiles.append(filePath); // clazy:exclude=reserve-candidates } else { errorMsgs.append(fileInfo.fileName()); } } if (notWritableFiles.isEmpty()) { m_platformTools->errorList( m_w, tr("Error while writing file:\n"), errorMsgs, tr("File Error")); } else { int rc = m_platformTools->warningYesNoList( m_w, tr("Error while writing file. " "Do you want to change the permissions?"), errorMsgs, tr("File Error")); if (rc == QMessageBox::Yes) { auto model = qobject_cast(m_form->getFileList()->model()); TaggedFile* taggedFile; const auto filePaths = notWritableFiles; for (const QString& filePath : filePaths) { #ifdef Q_OS_MAC // On macOS, files may be locked, so try to make them mutable. const auto utf8Path = filePath.toUtf8(); struct stat st; if (::stat(utf8Path.constData(), &st) == 0 && st.st_flags & UF_IMMUTABLE) { ::chflags(utf8Path.constData(), st.st_flags & ~UF_IMMUTABLE); } #endif QFile::setPermissions(filePath, QFile::permissions(filePath) | QFile::WriteUser); if (model && (taggedFile = FileProxyModel::getTaggedFileOfIndex( model->index(filePath))) != nullptr) { taggedFile->undoRevertChangedFilename(); } } m_app->saveDirectory(); } } } if (updateGui) { QApplication::restoreOverrideCursor(); updateGuiControls(); } } /** * If anything was modified, save after asking user. * * @param doNotRevert if true, modifications are not reverted, this can be * used to skip the possibly long process if the application is not be closed * * @return false if user canceled. */ bool BaseMainWindowImpl::saveModified(bool doNotRevert) { bool completed = true; if(m_app->isModified() && !m_app->getDirName().isEmpty()) { int want_save = m_platformTools->warningYesNoCancel( m_w, tr("The current folder has been modified.\n" "Do you want to save it?"), tr("Warning")); switch(want_save) { case QMessageBox::Yes: saveDirectory(); completed = true; break; case QMessageBox::No: if (!doNotRevert) { if (m_app->getFileSelectionModel()) m_app->getFileSelectionModel()->clearSelection(); m_app->revertFileModifications(); } completed = true; break; case QMessageBox::Cancel: completed = false; break; default: completed = false; break; } } return completed; } /** * If a playlist was modified, save after asking user. * @return false if user canceled. */ bool BaseMainWindowImpl::saveModifiedPlaylists() { if (m_app->hasModifiedPlaylistModel()) { int answer = m_platformTools->warningYesNoCancel( m_w, tr("A playlist has been modified.\n" "Do you want to save it?"), tr("Warning")); if (answer == QMessageBox::Yes) { m_app->saveModifiedPlaylistModels(); } if (answer != QMessageBox::Yes && answer != QMessageBox::No) { return false; } } return true; } /** * Free allocated resources. * Our destructor may not be called, so cleanup is done here. */ void BaseMainWindowImpl::cleanup() { m_app->getSettings()->sync(); } /** * Update modification state before closing. * If anything was modified, save after asking user. * Save options before closing. * This method shall be called by closeEvent() (Qt) or * queryClose() (KDE). * * @return false if user canceled, * true will quit the application. */ bool BaseMainWindowImpl::queryBeforeClosing() { updateCurrentSelection(); if (saveModified(true) && saveModifiedPlaylists()) { saveOptions(); cleanup(); return true; } return false; } /** * Request new directory and open it. */ void BaseMainWindowImpl::slotFileOpen() { updateCurrentSelection(); if(saveModified()) { static QString flt = m_app->createFilterString(); QString filter(FileConfig::instance().nameFilter()); QStringList dirs = m_platformTools->getOpenFileNames( m_w, QString(), m_app->getDirName(), flt, &filter); if (!dirs.isEmpty()) { m_app->resetFileFilterIfNotMatching(dirs); m_app->openDirectory(dirs); } } } /** * Request new directory and open it. */ void BaseMainWindowImpl::slotFileOpenDirectory() { updateCurrentSelection(); if(saveModified()) { QString dir = m_platformTools->getExistingDirectory(m_w, QString(), m_app->getDirName()); if (!dir.isEmpty()) { m_app->openDirectory({dir}); } } } /** * Reload the current directory. */ void BaseMainWindowImpl::slotFileReload() { updateCurrentSelection(); if(saveModified()) { m_app->openDirectoryAfterReset(); } } /** * Open recent directory. * * @param dir directory to open */ void BaseMainWindowImpl::openRecentDirectory(const QString& dir) { updateCurrentSelection(); confirmedOpenDirectory({dir}); } /** * Save modified files. */ void BaseMainWindowImpl::slotFileSave() { saveDirectory(true); } /** * Quit application. */ void BaseMainWindowImpl::slotFileQuit() { slotStatusMsg(tr("Exiting...")); m_w->close(); /* this will lead to call of closeEvent(), queryClose() */ slotClearStatusMsg(); } /** * Change visibility of status bar. * @param visible true to show status bar */ void BaseMainWindowImpl::setStatusBarVisible(bool visible) { auto model = qobject_cast(m_form->getFileList()->model()); auto selModel = m_app->getFileSelectionModel(); if (visible) { m_w->statusBar()->show(); if (model && selModel) { connect(model, &FileProxyModel::sortingFinished, m_deferredItemCountTimer, static_cast(&QTimer::start), Qt::UniqueConnection); connect(model->sourceModel(), &QAbstractItemModel::dataChanged, m_deferredItemCountTimer, static_cast(&QTimer::start), Qt::UniqueConnection); connect(selModel, &QItemSelectionModel::selectionChanged, m_deferredSelectionCountTimer, static_cast(&QTimer::start), Qt::UniqueConnection); } onItemCountChanged(); onSelectionCountChanged(); } else { m_deferredItemCountTimer->stop(); m_deferredSelectionCountTimer->stop(); m_w->statusBar()->hide(); if (model && selModel) { disconnect(model, &FileProxyModel::sortingFinished, m_deferredItemCountTimer, static_cast(&QTimer::start)); disconnect(model->sourceModel(), &QAbstractItemModel::dataChanged, m_deferredItemCountTimer, static_cast(&QTimer::start)); disconnect(selModel, &QItemSelectionModel::selectionChanged, m_deferredSelectionCountTimer, static_cast(&QTimer::start)); } m_folderCount = 0; m_fileCount = 0; m_selectionCount = 0; updateStatusLabel(); } } /** * Called when the item count of the file proxy model changed. */ void BaseMainWindowImpl::onItemCountChanged() { if (auto model = qobject_cast(m_form->getFileList()->model())) { model->countItems(m_app->getRootIndex(), m_folderCount, m_fileCount); updateStatusLabel(); } } /** * Called when the item count of the file selection model changed. */ void BaseMainWindowImpl::onSelectionCountChanged() { if (auto selModel = m_app->getFileSelectionModel()) { m_selectionCount = selModel->selectedRows().size(); updateStatusLabel(); } } /** * Update label of status bar with information about the number of files. */ void BaseMainWindowImpl::updateStatusLabel() { if (m_statusLabel) { QStringList counts; if (m_folderCount != 0) { //~ singular %n folder //~ plural %n folders counts.append(tr("%n folders", "", m_folderCount)); } if (m_fileCount != 0) { //~ singular %n file //~ plural %n files counts.append(tr("%n files", "", m_fileCount)); } if (m_selectionCount != 0) { //~ singular %n selected //~ plural %n selected counts.append(tr("%n selected", "", m_selectionCount)); } if (counts.isEmpty()) { m_statusLabel->setText((tr("Ready."))); } else { m_statusLabel->setText(counts.join(QLatin1String(", "))); } } } /** * Change status message. * * @param text message */ void BaseMainWindowImpl::slotStatusMsg(const QString& text) { m_w->statusBar()->showMessage(text); // processEvents() is necessary to make the change of the status bar // visible when it is changed back again in the same function, // i.e. in the same call from the Qt main event loop. qApp->processEvents(); } /** * Clear status message. * To be called when a message set with slotStatusMsg() is no longer valid. */ void BaseMainWindowImpl::slotClearStatusMsg() { m_w->statusBar()->clearMessage(); } /** * Show playlist dialog. */ void BaseMainWindowImpl::slotPlaylistDialog() { if (!m_playlistDialog) { m_playlistDialog.reset(new PlaylistDialog(m_w)); } m_playlistDialog->readConfig(); if (m_playlistDialog->exec() == QDialog::Accepted) { PlaylistConfig cfg; m_playlistDialog->getCurrentConfig(cfg); QString newEmptyPlaylistFileName = m_playlistDialog->getFileNameForNewEmptyPlaylist(); if (newEmptyPlaylistFileName.isEmpty()) { writePlaylist(cfg); } else { m_app->writeEmptyPlaylist(cfg, newEmptyPlaylistFileName); } } } /** * Write playlist according to playlist configuration. * * @param cfg playlist configuration to use * * @return true if ok. */ bool BaseMainWindowImpl::writePlaylist(const PlaylistConfig& cfg) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); slotStatusMsg(tr("Creating playlist...")); bool ok = m_app->writePlaylist(cfg); slotClearStatusMsg(); QApplication::restoreOverrideCursor(); return ok; } /** * Create playlist. * * @return true if ok. */ bool BaseMainWindowImpl::slotCreatePlaylist() { return writePlaylist(PlaylistConfig::instance()); } /** * Open dialog to edit playlist. * @param playlistPath path to playlist file */ void BaseMainWindowImpl::showPlaylistEditDialog(const QString& playlistPath) { PlaylistEditDialog* dialog = m_playlistEditDialogs.value(playlistPath); if (!dialog) { PlaylistModel* model = m_app->playlistModel(playlistPath); dialog = new PlaylistEditDialog(model, m_form->getFileList()->selectionModel(), m_w); connect(dialog, &QDialog::finished, this, &BaseMainWindowImpl::onPlaylistEditDialogFinished); m_playlistEditDialogs.insert(playlistPath, dialog); // The playlist windows are placed above the directory list. // If multiple playlist windows are open, they are displaced by the height // of the title bar. QWidget* dirList = m_form->getDirList(); int titleBarHeight = dialog->style()->pixelMetric(QStyle::PM_TitleBarHeight); int yOffset = titleBarHeight * m_playlistEditDialogs.size(); QRect geometry(dirList->mapToGlobal(QPoint(0, 0)), dirList->size()); geometry.setTop(geometry.top() + yOffset); dialog->setGeometry(geometry); QStringList filesNotFound = model->filesNotFound(); if (!filesNotFound.isEmpty()) { m_platformTools->warningDialog( m_w, tr("Files not found"), filesNotFound.join(QLatin1Char('\n')), tr("Error")); } } dialog->showNormal(); dialog->raise(); } /** * Called when a playlist edit dialog is closed. */ void BaseMainWindowImpl::onPlaylistEditDialogFinished() { if (auto dialog = qobject_cast(sender())) { m_playlistEditDialogs.remove(m_playlistEditDialogs.key(dialog)); dialog->deleteLater(); } } /** * Update track data and create import dialog. */ void BaseMainWindowImpl::setupImportDialog() { m_app->filesToTrackDataModel(ImportConfig::instance().importDest()); if (!m_importDialog) { QString caption(tr("Import")); m_importDialog.reset( new ImportDialog(m_platformTools, m_w, caption, m_app->getTrackDataModel(), m_app->genreModel(Frame::Tag_2), m_app->getServerImporters(), m_app->getServerTrackImporters())); connect(m_importDialog.data(), &QDialog::accepted, this, &BaseMainWindowImpl::applyImportedTrackData); } m_importDialog->clear(); } /** * Set tagged files of directory from imported track data model. */ void BaseMainWindowImpl::applyImportedTrackData() { m_app->trackDataModelToFiles(m_importDialog->getDestination()); } /** * Import. */ void BaseMainWindowImpl::slotImport() { if (auto action = qobject_cast(sender())) { setupImportDialog(); if (m_importDialog) { m_importDialog->showWithSubDialog(action->data().toInt()); } } } /** * Tag import. */ void BaseMainWindowImpl::slotTagImport() { if (!m_tagImportDialog) { m_tagImportDialog.reset(new TagImportDialog(m_w, nullptr)); connect(m_tagImportDialog.data(), &TagImportDialog::trackDataUpdated, this, [this]() { m_app->importFromTagsToSelection( m_tagImportDialog->getDestination(), m_tagImportDialog->getSourceFormat(), m_tagImportDialog->getExtractionFormat()); }); } m_tagImportDialog->clear(); m_tagImportDialog->show(); } /** * Batch import. */ void BaseMainWindowImpl::slotBatchImport() { if (!m_batchImportDialog) { m_batchImportDialog.reset(new BatchImportDialog(m_app->getServerImporters(), m_w)); connect(m_batchImportDialog.data(), &BatchImportDialog::start, m_app, static_cast( &Kid3Application::batchImport)); connect(m_app->getBatchImporter(), &BatchImporter::reportImportEvent, m_batchImportDialog.data(), &BatchImportDialog::showImportEvent); connect(m_batchImportDialog.data(), &BatchImportDialog::abort, m_app->getBatchImporter(), &BatchImporter::abort); connect(m_app->getBatchImporter(), &BatchImporter::finished, this, &BaseMainWindowImpl::updateGuiControls); } m_app->getBatchImporter()->clearAborted(); m_batchImportDialog->readConfig(); m_batchImportDialog->show(); } /** * Browse album cover artwork. */ void BaseMainWindowImpl::slotBrowseCoverArt() { if (!m_browseCoverArtDialog) { m_browseCoverArtDialog.reset(new BrowseCoverArtDialog(m_app, m_w)); } FrameCollection frames2; QModelIndex index = m_form->getFileList()->currentIndex(); if (TaggedFile* taggedFile = FileProxyModel::getTaggedFileOfIndex(index)) { taggedFile->readTags(false); frames2.clear(); for (Frame::TagNumber tagNr : Frame::allTagNumbers()) { if (frames2.empty()) { taggedFile->getAllFrames(tagNr, frames2); } else { FrameCollection frames1; taggedFile->getAllFrames(tagNr, frames1); frames2.merge(frames1); } } } m_browseCoverArtDialog->readConfig(); m_browseCoverArtDialog->setFrames(frames2); m_browseCoverArtDialog->exec(); } /** * Export. */ void BaseMainWindowImpl::slotExport() { m_exportDialog = new ExportDialog( m_platformTools, m_w, m_app->getTextExporter()); m_exportDialog->readConfig(); ImportTrackDataVector trackDataVector; m_app->filesToTrackData(ExportConfig::instance().exportSource(), trackDataVector); m_app->getTextExporter()->setTrackData(trackDataVector); m_exportDialog->showPreview(); m_exportDialog->exec(); delete m_exportDialog; m_exportDialog = nullptr; } /** * Toggle auto hiding of tags. */ void BaseMainWindowImpl::slotSettingsAutoHideTags() { GuiConfig::instance().setAutoHideTags(m_self->autoHideTagsAction()->isChecked()); updateCurrentSelection(); updateGuiControls(); } /** * Show or hide picture. */ void BaseMainWindowImpl::slotSettingsShowHidePicture() { GuiConfig::instance().setHidePicture(!m_self->showHidePictureAction()->isChecked()); m_form->hidePicture(GuiConfig::instance().hidePicture()); // In Qt3 the picture is displayed too small if Kid3 is started with picture // hidden, and then "Show Picture" is triggered while a file with a picture // is selected. Thus updating the controls is only done for Qt4, in Qt3 the // file has to be selected again for the picture to be shown. if (!GuiConfig::instance().hidePicture()) { updateGuiControls(); } } /** * Apply configuration changes. */ void BaseMainWindowImpl::applyChangedConfiguration() { m_app->applyChangedConfiguration(); if (!FileConfig::instance().markChanges()) { m_form->markChangedFilename(false); } } /** * Apply keyboard shortcut changes. */ void BaseMainWindowImpl::applyChangedShortcuts() { auto shortcuts = m_self->shortcutsMap(); m_form->setSectionActionShortcuts(shortcuts); } /** * Find and replace in tags of files. * @param findOnly true to display only find part of dialog */ void BaseMainWindowImpl::findReplace(bool findOnly) { TagSearcher* tagSearcher = m_app->getTagSearcher(); if (!m_findReplaceDialog) { m_findReplaceDialog = new FindReplaceDialog(m_w); connect(m_findReplaceDialog, &FindReplaceDialog::findRequested, m_app, &Kid3Application::findText); connect(m_findReplaceDialog, &FindReplaceDialog::replaceRequested, m_app, &Kid3Application::replaceText); connect(m_findReplaceDialog, &FindReplaceDialog::replaceAllRequested, m_app, &Kid3Application::replaceAll); connect(m_findReplaceDialog, &QDialog::finished, this, &BaseMainWindowImpl::deactivateFindReplace); connect(tagSearcher, &TagSearcher::progress, m_findReplaceDialog, &FindReplaceDialog::showProgress); } m_findReplaceDialog->init(findOnly); m_findReplaceDialog->show(); if (!m_findReplaceActive) { QModelIndexList selItems(m_app->getFileSelectionModel()->selectedRows()); if (selItems.size() == 1) { tagSearcher->setStartIndex(selItems.first()); } connect(tagSearcher, &TagSearcher::textFound, this, &BaseMainWindowImpl::showFoundText); connect(tagSearcher, &TagSearcher::textReplaced, this, &BaseMainWindowImpl::updateReplacedText); m_findReplaceActive = true; } } /** * Deactivate showing of find replace results. */ void BaseMainWindowImpl::deactivateFindReplace() { if (m_findReplaceActive) { TagSearcher* tagSearcher = m_app->getTagSearcher(); tagSearcher->abort(); disconnect(tagSearcher, &TagSearcher::textFound, this, &BaseMainWindowImpl::showFoundText); disconnect(tagSearcher, &TagSearcher::textReplaced, this, &BaseMainWindowImpl::updateReplacedText); m_findReplaceActive = false; } } /** * Ensure that found text is made visible in the GUI. */ void BaseMainWindowImpl::showFoundText() { const TagSearcher::Position& pos = m_app->getTagSearcher()->getPosition(); if (pos.isValid()) { m_app->getFileSelectionModel()->setCurrentIndex(pos.getFileIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); if (pos.getPart() == TagSearcher::Position::FileName) { m_form->setFilenameSelection(pos.getMatchedPos(), pos.getMatchedLength()); } else { m_form->frameTable(TagSearcher::Position::partToTagNumber(pos.getPart())) ->setValueSelection(pos.getFrameIndex(), pos.getMatchedPos(), pos.getMatchedLength()); } } } /** * Update GUI controls after text has been replaced. */ void BaseMainWindowImpl::updateReplacedText() { const TagSearcher::Position& pos = m_app->getTagSearcher()->getPosition(); if (pos.isValid()) { m_app->getFileSelectionModel()->setCurrentIndex(pos.getFileIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); updateGuiControls(); } } /** * Rename directory. */ void BaseMainWindowImpl::slotRenameDirectory() { if (saveModified()) { if (!m_renDirDialog) { m_renDirDialog.reset(new RenDirDialog(m_w, m_app->getDirRenamer())); connect(m_renDirDialog.data(), &RenDirDialog::actionSchedulingRequested, m_app, &Kid3Application::scheduleRenameActions); connect(m_app->getDirRenamer(), &DirRenamer::actionScheduled, m_renDirDialog.data(), &RenDirDialog::displayActionPreview); } if (TaggedFile* taggedFile = TaggedFileOfDirectoryIterator::first(m_app->currentOrRootIndex())) { m_renDirDialog->startDialog(taggedFile); } else { m_renDirDialog->startDialog(nullptr, m_app->getDirName()); } if (m_renDirDialog->exec() == QDialog::Accepted) { QString errorMsg(m_app->performRenameActions()); if (!errorMsg.isEmpty()) { #ifdef Q_OS_WIN32 if (m_platformTools->warningContinueCancelList( m_w, tr("Error while renaming:\n") + tr("Retry after closing directories?"), QStringList(errorMsg), tr("File Error"))) { m_app->tryRenameActionsAfterReset(); } #else m_platformTools->warningDialog(m_w, tr("Error while renaming:\n"), errorMsg, tr("File Error")); #endif } } } } /** * Number tracks. */ void BaseMainWindowImpl::slotNumberTracks() { if (!m_numberTracksDialog) { m_numberTracksDialog.reset(new NumberTracksDialog(m_w)); } m_numberTracksDialog->setTotalNumberOfTracks( m_app->getTotalNumberOfTracksInDir(), TagConfig::instance().enableTotalNumberOfTracks()); if (m_numberTracksDialog->exec() == QDialog::Accepted) { int nr = m_numberTracksDialog->getStartNumber(); bool totalEnabled; int total = m_numberTracksDialog->getTotalNumberOfTracks(&totalEnabled); if (!totalEnabled) total = 0; TagConfig::instance().setEnableTotalNumberOfTracks(totalEnabled); Kid3Application::NumberTrackOptions options; if (m_numberTracksDialog->isTrackNumberingEnabled()) options |= Kid3Application::NumberTracksEnabled; if (m_numberTracksDialog->isDirectoryCounterResetEnabled()) options |= Kid3Application::NumberTracksResetCounterForEachDirectory; m_app->numberTracks(nr, total, m_numberTracksDialog->getDestination(), options); } } /** * Filter. */ void BaseMainWindowImpl::slotFilter() { if (saveModified()) { if (!m_filterDialog) { m_filterDialog.reset(new FilterDialog(m_w)); connect(m_filterDialog.data(), &FilterDialog::apply, m_app, static_cast( &Kid3Application::applyFilter)); connect(m_app, &Kid3Application::fileFiltered, m_filterDialog.data(), &FilterDialog::showFilterEvent); connect(m_app, &Kid3Application::fileFiltered, this, &BaseMainWindowImpl::filterProgress); } FilterConfig::instance().setFilenameFormat( FileConfig::instance().toFilenameFormat()); m_filterDialog->readConfig(); m_filterDialog->show(); } } /** * Show filter operation progress. * @param type filter event type * @param fileName name of file processed * @param passed number of files which passed the filter * @param total total number of files checked */ void BaseMainWindowImpl::filterProgress(int type, const QString& fileName, int passed, int total) { Q_UNUSED(fileName) switch (type) { case FileFilter::Started: startProgressMonitoring(tr("Filter"), &BaseMainWindowImpl::terminateFilter, true); break; case FileFilter::Finished: case FileFilter::Aborted: stopProgressMonitoring(); break; default: checkProgressMonitoring(0, 0, QString::number(passed) + QLatin1Char('/') + QString::number(total)); } } /** * Terminate filtering the file list. */ void BaseMainWindowImpl::terminateFilter() { if (m_filterDialog) { m_filterDialog->abort(); } } /** * Play audio file. */ void BaseMainWindowImpl::slotPlayAudio() { m_app->playAudio(); } /** * Show play tool bar. */ void BaseMainWindowImpl::showPlayToolBar() { #ifdef HAVE_QTMULTIMEDIA if (!m_playToolBar) { if (AudioPlayer* player = qobject_cast(m_app->getAudioPlayer())) { m_playToolBar = new PlayToolBar(player, m_w); m_playToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); m_w->addToolBar(Qt::BottomToolBarArea, m_playToolBar); connect(m_playToolBar, &PlayToolBar::errorMessage, this, &BaseMainWindowImpl::slotStatusMsg); #ifdef HAVE_QTDBUS connect(m_playToolBar, &PlayToolBar::closed, m_app, &Kid3Application::deactivateMprisInterface); #endif #ifdef Q_OS_WIN32 // Phonon on Windows cannot play if the file is open. connect(m_playToolBar, &PlayToolBar::aboutToPlay, m_app, &Kid3Application::closeFileHandle); #endif } } m_playToolBar->show(); #endif } /** * Set window title with information from directory, filter and modification * state. */ void BaseMainWindowImpl::updateWindowCaption() { QString cap; if (!m_app->getDirName().isEmpty()) { cap += QDir(m_app->getDirName()).dirName(); } if (m_app->isFiltered()) { cap += tr(" [filtered %1/%2]") .arg(m_app->filterPassedCount()).arg(m_app->filterTotalCount()); } m_self->setWindowCaption(cap, m_app->isModified()); } /** * Update files of current selection. */ void BaseMainWindowImpl::updateCurrentSelection() { TaggedFileSelection* selection = m_app->selectionInfo(); if (!selection->isEmpty()) { FOR_ALL_TAGS(tagNr) { m_form->frameTable(tagNr)->acceptEdit(); } m_app->frameModelsToTags(); selection->setFilename(m_form->getFilename()); } } /** * Apply selection change and update GUI controls. * The new selection is stored and the GUI controls and frame list * updated accordingly (filtered for multiple selection). * @param selected selected items * @param deselected deselected items */ void BaseMainWindowImpl::applySelectionChange(const QItemSelection& selected, const QItemSelection& deselected) { if (!deselected.isEmpty()) { m_app->tagsToFrameModels(); } else { m_app->selectedTagsToFrameModels(selected); } updateGuiControlsFromSelection(); } /** * Update GUI controls from the tags in the files. * The new selection is stored and the GUI controls and frame list * updated accordingly (filtered for multiple selection). */ void BaseMainWindowImpl::updateGuiControls() { m_app->tagsToFrameModels(); updateGuiControlsFromSelection(); } /** * Update GUI controls from the current selection. */ void BaseMainWindowImpl::updateGuiControlsFromSelection() { TaggedFileSelection* selection = m_app->selectionInfo(); m_form->setFilename(selection->getFilename()); m_form->setFilenameEditEnabled(selection->isSingleFileSelected()); m_form->setDetailInfo(selection->getDetailInfo()); FOR_ALL_TAGS(tagNr) { m_form->setTagFormat(tagNr, selection->getTagFormat(tagNr)); } if (FileConfig::instance().markChanges()) { m_form->markChangedFilename(selection->isFilenameChanged()); } if (!GuiConfig::instance().hidePicture()) { m_form->setPictureData(selection->getPicture()); } bool selectionEmpty = selection->isEmpty(); bool autoHideTags = GuiConfig::instance().autoHideTags(); FOR_ALL_TAGS(tagNr) { m_form->enableControls(tagNr, selection->isTagUsed(tagNr) || selectionEmpty); if (autoHideTags) { m_form->hideTag(tagNr, !selection->hasTag(tagNr)); } } } /** * Update ID3v2 tags in GUI controls from file displayed in frame list. * * @param taggedFile the selected file * @param tagNr tag number */ void BaseMainWindowImpl::updateAfterFrameModification(TaggedFile* taggedFile, Frame::TagNumber tagNr) { if (taggedFile) { FrameCollection frames; taggedFile->getAllFrames(tagNr, frames); m_app->frameModel(tagNr)->transferFrames(frames); } } /** * Let user select a frame type. * frameSelected() is emitted when the edit dialog is closed with the selected * frame as a parameter if a frame is selected. * * @param frame is filled with the selected frame * @param taggedFile tagged file for which frame has to be selected */ void BaseMainWindowImpl::selectFrame(Frame* frame, const TaggedFile* taggedFile) { bool ok = false; if (taggedFile && frame) { QStringList frameIds = taggedFile->getFrameIds(m_editFrameTagNr); QMap nameMap = Frame::getDisplayNameMap(frameIds); QString displayName = QInputDialog::getItem( m_w, tr("Add Frame"), tr("Select the frame ID"), nameMap.keys(), 0, true, &ok); if (ok) { if (displayName.startsWith(QLatin1Char('!'))) { QString name = displayName.mid(1); Frame::ExtendedType type(Frame::FT_Other, name); *frame = Frame(type, QLatin1String(""), -1); } else { QString name = nameMap.value(displayName, displayName); Frame::Type type = Frame::getTypeFromName(name); *frame = Frame(type, QLatin1String(""), name, -1); } } } emit frameSelected(m_editFrameTagNr, ok ? frame : nullptr); } /** * Return object which emits frameSelected(), frameEdited() signals. * * @return object which emits signals. */ QObject* BaseMainWindowImpl::qobject() { return this; } /** * Get the tag number of the edited frame. * @return tag number. */ Frame::TagNumber BaseMainWindowImpl::tagNumber() const { return m_editFrameTagNr; } /** * Set the tag number of the edited frame. * @param tagNr tag number */ void BaseMainWindowImpl::setTagNumber(Frame::TagNumber tagNr) { m_editFrameTagNr = tagNr; } /** * Create dialog to edit a frame and update the fields * if Ok is returned. * frameEdited() is emitted when the edit dialog is closed with the edited * frame as a parameter if it was accepted. * * @param frame frame to edit * @param taggedFile tagged file where frame has to be set */ void BaseMainWindowImpl::editFrameOfTaggedFile(const Frame* frame, TaggedFile* taggedFile) { if (!frame || !taggedFile) { emit frameEdited(m_editFrameTagNr, nullptr); return; } m_editFrame = *frame; m_editFrameTaggedFile = taggedFile; QString name(m_editFrame.getInternalName()); if (name.isEmpty()) { name = m_editFrame.getName(); } if (!name.isEmpty()) { int nlPos = name.indexOf(QLatin1Char('\n')); if (nlPos > 0) { // probably "TXXX - User defined text information\nDescription" or // "WXXX - User defined URL link\nDescription" name.truncate(nlPos); } name = QCoreApplication::translate("@default", name.toLatin1().data()); } if (!m_editFrameDialog) { m_editFrameDialog = new EditFrameFieldsDialog(m_platformTools, m_app, m_w); connect(m_editFrameDialog, &QDialog::finished, this, &BaseMainWindowImpl::onEditFrameDialogFinished); } m_editFrameDialog->setWindowTitle(name); m_editFrameDialog->setFrame(m_editFrame, m_editFrameTaggedFile, m_editFrameTagNr); m_editFrameDialog->show(); } /** * Called when the edit fram dialog is finished. * @param result dialog result */ void BaseMainWindowImpl::onEditFrameDialogFinished(int result) { if (auto dialog = qobject_cast(sender())) { if (result == QDialog::Accepted) { const Frame::FieldList& fields = dialog->getUpdatedFieldList(); if (fields.isEmpty()) { m_editFrame.setValue(dialog->getFrameValue()); } else { m_editFrame.setFieldList(fields); m_editFrame.setValueFromFieldList(); } if (m_editFrameTaggedFile->setFrame(m_editFrameTagNr, m_editFrame)) { m_editFrameTaggedFile->markTagChanged(m_editFrameTagNr, m_editFrame.getType()); } } } emit frameEdited(m_editFrameTagNr, result == QDialog::Accepted ? &m_editFrame : nullptr); } /** * Rename the selected file(s). */ void BaseMainWindowImpl::renameFile() { QItemSelectionModel* selectModel = m_app->getFileSelectionModel(); auto model = qobject_cast(m_form->getFileList()->model()); if (!selectModel || !model) return; QList selItems; const auto indexes = selectModel->selectedRows(); selItems.reserve(indexes.size()); for (const QModelIndex& index : indexes) selItems.append(index); const auto selectedIndexes = selItems; for (const QPersistentModelIndex& index : selectedIndexes) { TaggedFile* taggedFile = FileProxyModel::getTaggedFileOfIndex(index); QString absFilename, dirName, fileName; if (taggedFile) { absFilename = taggedFile->getAbsFilename(); dirName = taggedFile->getDirname(); fileName = taggedFile->getFilename(); } else { QFileInfo fi(model->fileInfo(index)); absFilename = fi.filePath(); dirName = fi.dir().path(); fileName = fi.fileName(); } bool ok; QString newFileName = QInputDialog::getText( m_w, tr("Rename File"), tr("Enter new file name:"), QLineEdit::Normal, fileName, &ok); if (ok && !newFileName.isEmpty() && newFileName != fileName) { if (taggedFile) { if (taggedFile->isChanged()) { taggedFile->setFilename(newFileName); if (selItems.size() == 1) m_form->setFilename(newFileName); continue; } // This will close the file. // The file must be closed before renaming on Windows. taggedFile->closeFileHandle(); } else if (model->isDir(index)) { // The directory must be closed before renaming on Windows. TaggedFileIterator::closeFileHandles(index); } QString newPath = dirName + QLatin1Char('/') + newFileName; bool ok = model->rename(index, newFileName); if (!ok && !(index.flags() & Qt::ItemIsEditable)) { // The file system model seems to be too restrictive, renaming without // write permission is possible on Linux, so try again without // using the model. ok = QFile::rename(absFilename, newPath); } if (ok) { if (taggedFile) { taggedFile->updateCurrentFilename(); if (selItems.size() == 1) { m_form->setFilename(newFileName); } } } else { #ifdef Q_OS_WIN32 if (QMessageBox::warning( 0, tr("File Error"), tr("Error while renaming:\n") + tr("Rename %1 to %2 failed\n").arg(fileName).arg(newFileName) + tr("Retry after closing folders?"), QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok) { m_app->tryRenameAfterReset(absFilename, newPath); } #else QMessageBox::warning( nullptr, tr("File Error"), tr("Error while renaming:\n") + tr("Rename %1 to %2 failed\n").arg(fileName, newFileName), QMessageBox::Ok, Qt::NoButton); #endif } } } } /** Only defined for generation of translation files */ #define WANT_TO_DELETE_FOR_PO \ QT_TRANSLATE_NOOP("@default", "Do you really want to move these %1 items to the trash?") /** * Delete the selected file(s). */ void BaseMainWindowImpl::deleteFile() { QItemSelectionModel* selectModel = m_app->getFileSelectionModel(); auto model = qobject_cast(m_form->getFileList()->model()); if (!selectModel || !model) return; QStringList files; QList selItems; const auto indexes = selectModel->selectedRows(); selItems.reserve(indexes.size()); for (const QModelIndex& index : indexes) selItems.append(index); const auto selectedIndexes = selItems; for (const QPersistentModelIndex& index : selectedIndexes) { files.append(model->filePath(index)); // clazy:exclude=reserve-candidates } const int numFiles = files.size(); if (numFiles > 0) { if (m_platformTools->warningContinueCancelList( m_w, numFiles > 1 ? tr("Do you really want to move these %1 items to the trash?") .arg(numFiles) : tr("Do you really want to move this item to the trash?"), files, tr("Move to Trash"))) { bool rmdirError = false; files.clear(); for (const QPersistentModelIndex& index : selectedIndexes) { QString absFilename(model->filePath(index)); if (!QFileInfo(absFilename).isWritable()) { QFile::setPermissions(absFilename, QFile::permissions(absFilename) | QFile::WriteUser); } if (model->isDir(index)) { if (!m_platformTools->moveToTrash(absFilename)) { rmdirError = true; files.append(absFilename); // clazy:exclude=reserve-candidates } } else { if (TaggedFile* taggedFile = FileProxyModel::getTaggedFileOfIndex(index)) { // This will close the file. // The file must be closed before deleting on Windows. taggedFile->closeFileHandle(); } if (!m_platformTools->moveToTrash(absFilename)) { files.append(absFilename); // clazy:exclude=reserve-candidates } } } if (!files.isEmpty()) { QString txt; if (rmdirError) txt += tr("Folder must be empty.\n"); txt += tr("Could not move these files to the Trash"); m_platformTools->errorList(m_w, txt, files, tr("File Error")); } } } } /** * Toggle expanded state of directory in the file list. * @param index index of directory */ void BaseMainWindowImpl::toggleExpanded(const QModelIndex& index) { QTreeView* fileList = m_form->getFileList(); fileList->setExpanded(index, !fileList->isExpanded(index)); } /** * Expand the file list. */ void BaseMainWindowImpl::expandFileList() { m_expandNotificationNeeded = sender() == m_app; connect(m_app->getFileProxyModelIterator(), &FileProxyModelIterator::nextReady, this, &BaseMainWindowImpl::expandNextDirectory); // If this slot is invoked from the file list menu action and the // shift key is pressed, only expand the current subtree. QObject* emitter = sender(); bool sentFromAction = emitter && emitter->metaObject() == &QAction::staticMetaObject; bool expandOnlySubtree = sentFromAction && QApplication::keyboardModifiers() == Qt::ShiftModifier; startProgressMonitoring(tr("Expand All"), &BaseMainWindowImpl::terminateExpandFileList, !expandOnlySubtree); m_app->getFileProxyModelIterator()->start(expandOnlySubtree ? m_form->getFileList()->currentIndex() : m_form->getFileList()->rootIndex()); } /** * Expand item if it is a directory. * * @param index index of file in file proxy model */ void BaseMainWindowImpl::expandNextDirectory(const QPersistentModelIndex& index) { if (index.isValid()) { if (m_app->getFileProxyModel()->isDir(index)) { m_form->getFileList()->expand(index); } int done = m_app->getFileProxyModelIterator()->getWorkDone(); int total = m_app->getFileProxyModelIterator()->getWorkToDo() + done; checkProgressMonitoring(done, total, QString()); } else { stopProgressMonitoring(); } } /** * Terminate expanding the file list. */ void BaseMainWindowImpl::terminateExpandFileList() { m_app->getFileProxyModelIterator()->abort(); disconnect(m_app->getFileProxyModelIterator(), &FileProxyModelIterator::nextReady, this, &BaseMainWindowImpl::expandNextDirectory); if (m_expandNotificationNeeded) { m_expandNotificationNeeded = false; m_app->notifyExpandFileListFinished(); } } /** * Start monitoring the progress of a possibly long operation. * * If the operation takes longer than 3 seconds, a progress widget is shown. * * @param title title to be displayed in progress widget * @param terminationHandler method to be called to terminate operation * @param disconnectModel true to disconnect the file list models while the * progress widget is shown */ void BaseMainWindowImpl::startProgressMonitoring( const QString& title, void (BaseMainWindowImpl::*terminationHandler)(), bool disconnectModel) { if (!m_progressTitle.isEmpty() && m_progressTitle != title) { stopProgressMonitoring(); } m_progressTitle = title; m_progressTerminationHandler = terminationHandler; m_progressDisconnected = disconnectModel; m_progressStartTime = QDateTime::currentDateTime(); } /** * Start monitoring the progress started with startProgressMonitoring(). */ void BaseMainWindowImpl::stopProgressMonitoring() { if (m_progressWidget) { m_form->removeLeftSideWidget(m_progressWidget); m_progressWidget->reset(); if (m_progressDisconnected) { m_form->getDirList()->reconnectModel(); m_form->getFileList()->reconnectModel(); m_form->getFileList()->expandAll(); } } if (m_progressTerminationHandler) { (this->*m_progressTerminationHandler)(); } m_progressTitle.clear(); m_progressTerminationHandler = nullptr; } /** * Check progress of a possibly long operation. * * Progress monitoring is started with startProgressMonitoring(). This method * will check if the opeation is running long enough to show a progress widget * and update the progress information. It will call stopProgressMonitoring() * when the operation is aborted. * * @param done amount of work done * @param total total amount of work * @param text text for progress label */ void BaseMainWindowImpl::checkProgressMonitoring(int done, int total, const QString& text) { if (m_progressStartTime.isValid() && m_progressStartTime.secsTo(QDateTime::currentDateTime()) >= 3) { // Operation is taking some time, show dialog to abort it. m_progressStartTime = QDateTime(); if (!m_progressWidget) { m_progressWidget = new ProgressWidget(m_w); } m_progressWidget->setWindowTitle(m_progressTitle); m_progressWidget->setLabelText(QString()); m_progressWidget->setCancelButtonText(tr("A&bort")); m_progressWidget->setMinimum(0); m_progressWidget->setMaximum(0); m_form->setLeftSideWidget(m_progressWidget); if (m_progressDisconnected) { m_form->getFileList()->disconnectModel(); m_form->getDirList()->disconnectModel(); } } if (m_progressWidget) { m_progressWidget->setValueAndMaximum(done, total); m_progressWidget->setLabelText(text); if (m_progressWidget->wasCanceled()) { stopProgressMonitoring(); } } } /** * Constructor. * * @param mainWin main window * @param platformTools platform specific tools * @param app application context */ BaseMainWindow::BaseMainWindow(QMainWindow* mainWin, IPlatformTools* platformTools, Kid3Application* app) : m_impl(new BaseMainWindowImpl(mainWin, platformTools, app)) { m_impl->setBackPointer(this); } /** * Destructor. */ BaseMainWindow::~BaseMainWindow() { // Must not be inline because of forwared declared QScopedPointer. } /** * Initialize main window. * Shall be called at end of constructor body in derived classes. */ void BaseMainWindow::init() { m_impl->init(); } /** * Play audio file. */ void BaseMainWindow::slotPlayAudio() { m_impl->slotPlayAudio(); } /** * Change visibility of status bar. * @param visible true to show status bar */ void BaseMainWindow::setStatusBarVisible(bool visible) { m_impl->setStatusBarVisible(visible); } /** * Update files of current selection. */ void BaseMainWindow::updateCurrentSelection() { m_impl->updateCurrentSelection(); } /** * Open directory, user has to confirm if current directory modified. * * @param paths directory or file paths */ void BaseMainWindow::confirmedOpenDirectory(const QStringList& paths) { m_impl->confirmedOpenDirectory(paths); } /** * Update modification state before closing. * If anything was modified, save after asking user. * Save options before closing. * This method shall be called by closeEvent() (Qt) or * queryClose() (KDE). * * @return false if user canceled, * true will quit the application. */ bool BaseMainWindow::queryBeforeClosing() { return m_impl->queryBeforeClosing(); } /** * Open recent directory. * * @param dir directory to open */ void BaseMainWindow::openRecentDirectory(const QString& dir) { m_impl->openRecentDirectory(dir); } /** * Set window title with information from directory, filter and modification * state. */ void BaseMainWindow::updateWindowCaption() { m_impl->updateWindowCaption(); } /** * Access to application. * @return application. */ Kid3Application* BaseMainWindow::app() { return m_impl->app(); } /** * Access to main form. * @return main form. */ Kid3Form* BaseMainWindow::form() { return m_impl->form(); }