File indexing completed on 2024-04-21 05:41:04

0001 /*
0002     SPDX-FileCopyrightText: 2019-2020 Nikolai Krasheninnikov <nkrasheninnikov@yandex.ru>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "svncommitdialog.h"
0008 
0009 #include <QVBoxLayout>
0010 #include <QPlainTextEdit>
0011 #include <QLabel>
0012 #include <QTableWidget>
0013 #include <QHeaderView>
0014 #include <QTemporaryFile>
0015 #include <QDialogButtonBox>
0016 #include <QPushButton>
0017 #include <QShortcut>
0018 #include <QMenu>
0019 #include <QDebug>
0020 
0021 #include <KLocalizedString>
0022 #include <KWindowConfig>
0023 #include <KSharedConfig>
0024 
0025 namespace {
0026 
0027 // Helper function: returns true if str starts with any string in a list.
0028 bool startsWith(const QStringList &list, const QString &str)
0029 {
0030     for (const auto &i : std::as_const(list)) {
0031         if (str.startsWith(i)) {
0032             return true;
0033         }
0034     }
0035 
0036     return false;
0037 }
0038 
0039 // Helper function: makes a new list from an existing one and a hashTable. It combines all the list
0040 // records which exists in hashTable. Existence means hashTable entry starts with a list entry.
0041 QStringList makeContext(const QStringList &list, const QHash<QString, KVersionControlPlugin::ItemVersion> *hashTable)
0042 {
0043     QStringList ret;
0044 
0045     for (const auto &i : std::as_const(list)) {
0046         for ( auto it = hashTable->cbegin(); it != hashTable->cend(); ++it ) {
0047             if (it.key().startsWith(i)) {
0048                 ret.append(i);
0049                 break;
0050             }
0051         }
0052     }
0053 
0054     return ret;
0055 }
0056 
0057 }
0058 
0059 struct svnCommitEntryInfo_t {
0060     svnCommitEntryInfo_t() :
0061         localPath(QString()),
0062         fileVersion( KVersionControlPlugin::NormalVersion )
0063     {}
0064 
0065     QString localPath;                              ///< Affected local path.
0066     KVersionControlPlugin::ItemVersion fileVersion; ///< File status in terms of KVersionControlPlugin
0067 };
0068 Q_DECLARE_METATYPE(svnCommitEntryInfo_t);
0069 
0070 enum columns_t {
0071     columnPath,
0072     columnStatus
0073 };
0074 
0075 SvnCommitDialog::SvnCommitDialog(const QHash<QString, KVersionControlPlugin::ItemVersion> *versionInfo, const QStringList& context, QWidget *parent) :
0076     QDialog(parent),
0077     m_versionInfoHash(versionInfo),
0078     m_context(context)
0079 {
0080     Q_ASSERT(versionInfo);
0081     Q_ASSERT(!context.empty());
0082 
0083     /*
0084      * Setup UI.
0085      */
0086     QVBoxLayout* boxLayout = new QVBoxLayout(this);
0087 
0088     boxLayout->addWidget(new QLabel(i18nc("@label", "Description:"), this));
0089     m_editor = new QPlainTextEdit(this);
0090     boxLayout->addWidget(m_editor, 1);
0091 
0092     QFrame* line = new QFrame(this);
0093     line->setFrameShape(QFrame::HLine);
0094     line->setFrameShadow(QFrame::Sunken);
0095     boxLayout->addWidget(line);
0096 
0097     m_changes = new QTableWidget(this);
0098     boxLayout->addWidget(m_changes, 3);
0099 
0100     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
0101     // The more appropriate role is QDialogButtonBox::ActionRole but we use QDialogButtonBox::ResetRole
0102     // because of QDialogButtonBox automatic button layout (button is separated from others).
0103     auto refreshButton = buttonBox->addButton(i18nc("@action:button", "Refresh"), QDialogButtonBox::ResetRole);
0104     refreshButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0105 
0106     auto okButton = buttonBox->button(QDialogButtonBox::Ok);
0107     okButton->setDefault(true);
0108     okButton->setText(i18nc("@action:button", "Commit"));
0109     boxLayout->addWidget(buttonBox);
0110 
0111     /*
0112      * Add actions, establish connections.
0113      */
0114     m_actRevertFile = new QAction(i18nc("@item:inmenu", "Revert"), this);
0115     m_actRevertFile->setIcon(QIcon::fromTheme(QStringLiteral("document-revert")));
0116     connect(m_actRevertFile, &QAction::triggered, this, [this] () {
0117         const QString filePath = m_actRevertFile->data().value<svnCommitEntryInfo_t>().localPath;
0118         Q_EMIT revertFiles(QStringList() << filePath);
0119     } );
0120 
0121     m_actDiffFile = new QAction(i18nc("@item:inmenu", "Show changes"), this);
0122     m_actDiffFile->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
0123     connect(m_actDiffFile, &QAction::triggered, this, [this] () {
0124         const QString filePath = m_actDiffFile->data().value<svnCommitEntryInfo_t>().localPath;
0125         Q_EMIT diffFile(filePath);
0126     } );
0127 
0128     m_actAddFile = new QAction(i18nc("@item:inmenu", "Add file"), this);
0129     m_actAddFile->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0130     connect(m_actAddFile, &QAction::triggered, this, [this] () {
0131         const QString filePath = m_actAddFile->data().value<svnCommitEntryInfo_t>().localPath;
0132         Q_EMIT addFiles(QStringList() << filePath);
0133     } );
0134 
0135     connect(buttonBox, &QDialogButtonBox::accepted, this, [this] () {
0136         // Form a new context list from an existing one and a possibly modified m_versionInfoHash (some
0137         // files from original context might no longer be in m_versionInfoHash).
0138         QStringList context = makeContext(m_context, m_versionInfoHash);
0139         Q_EMIT commit(context, m_editor->toPlainText());
0140         QDialog::accept();
0141     } );
0142     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0143     connect(refreshButton, &QPushButton::clicked, this, &SvnCommitDialog::refreshChangesList);
0144 
0145     connect(m_changes, &QWidget::customContextMenuRequested, this, &SvnCommitDialog::contextMenu);
0146 
0147     QShortcut *refreshShortcut = new QShortcut(QKeySequence::Refresh, this);
0148     connect(refreshShortcut, &QShortcut::activated, this, &SvnCommitDialog::refreshChangesList);
0149     refreshShortcut->setAutoRepeat(false);
0150 
0151     /*
0152      * Additional setup.
0153      */
0154     setWindowTitle(i18nc("@title:window", "SVN Commit"));
0155 
0156     const QStringList tableHeader = { i18nc("@title:column", "Path"),
0157                                       i18nc("@title:column", "Status") };
0158     m_changes->setColumnCount(tableHeader.size());
0159     m_changes->setHorizontalHeaderLabels(tableHeader);
0160     m_changes->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
0161     m_changes->horizontalHeader()->setSectionResizeMode(columnStatus, QHeaderView::ResizeToContents);
0162     m_changes->verticalHeader()->setVisible(false);
0163     m_changes->setSortingEnabled(false);
0164     m_changes->setSelectionMode(QAbstractItemView::SingleSelection);
0165     m_changes->setSelectionBehavior(QAbstractItemView::SelectRows);
0166     m_changes->setContextMenuPolicy(Qt::CustomContextMenu);
0167 
0168     refreshChangesList();
0169 }
0170 
0171 SvnCommitDialog::~SvnCommitDialog()
0172 {
0173     KConfigGroup dialogConfig(KSharedConfig::openConfig(QStringLiteral("dolphinrc")), QStringLiteral("SvnCommitDialog"));
0174     KWindowConfig::saveWindowSize(windowHandle(), dialogConfig, KConfigBase::Persistent);
0175 }
0176 
0177 void SvnCommitDialog::refreshChangesList()
0178 {
0179     // Remove all the contents.
0180     m_changes->clearContents();
0181     m_changes->setRowCount(0);
0182 
0183     auto it = m_versionInfoHash->cbegin();
0184     for ( int row = 0 ; it != m_versionInfoHash->cend(); ++it ) {
0185         // If current item is not in a context list we skip it. Each file must be in a context dir
0186         // or be in a context itself.
0187         if (!startsWith(m_context, it.key())) {
0188             continue;
0189         }
0190 
0191         QTableWidgetItem *path = new QTableWidgetItem( it.key() );
0192         QTableWidgetItem *status = new QTableWidgetItem;
0193 
0194         path->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
0195         status->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
0196 
0197         m_changes->insertRow(row);
0198         m_changes->setItem(row, columnPath, path);
0199         m_changes->setItem(row, columnStatus, status);
0200         row++;
0201 
0202         svnCommitEntryInfo_t info;
0203         info.localPath = it.key();
0204         info.fileVersion = it.value();
0205         path->setData(Qt::UserRole, QVariant::fromValue(info));
0206         status->setData(Qt::UserRole, QVariant::fromValue(info));
0207 
0208         switch(it.value()) {
0209         case KVersionControlPlugin::UnversionedVersion:
0210             status->setText( i18nc("@item:intable", "Unversioned") );
0211             break;
0212         case KVersionControlPlugin::LocallyModifiedVersion:
0213             status->setText( i18nc("@item:intable", "Modified") );
0214             break;
0215         case KVersionControlPlugin::AddedVersion:
0216             status->setText( i18nc("@item:intable", "Added") );
0217             break;
0218         case KVersionControlPlugin::RemovedVersion:
0219             status->setText( i18nc("@item:intable", "Deleted") );
0220             break;
0221         case KVersionControlPlugin::ConflictingVersion:
0222             status->setText( i18nc("@item:intable", "Conflict") );
0223             break;
0224         case KVersionControlPlugin::MissingVersion:
0225             status->setText( i18nc("@item:intable", "Missing") );
0226             break;
0227         case KVersionControlPlugin::UpdateRequiredVersion:
0228             status->setText( i18nc("@item:intable", "Update required") );
0229             break;
0230         default:
0231             // For SVN normaly we shouldn't be here with:
0232             // NormalVersion, LocallyModifiedUnstagedVersion, IgnoredVersion.
0233             // 'default' is for any future changes in ItemVersion enum.
0234             qWarning() << QStringLiteral("Unknown SVN status for item %1, ItemVersion = %2").arg(it.key()).arg(it.value());
0235             status->setText(QString());
0236         }
0237     }
0238 
0239     // Sort by status: unversioned is at the bottom.
0240     m_changes->sortByColumn(columnStatus, Qt::AscendingOrder);
0241 }
0242 
0243 void SvnCommitDialog::show()
0244 {
0245     QWidget::show();
0246 
0247     // Restore window size after show() for workaround for QTBUG-40584. See KWindowConfig::restoreWindowSize() docs.
0248     KConfigGroup dialogConfig(KSharedConfig::openConfig(QStringLiteral("dolphinrc")), QStringLiteral("SvnCommitDialog"));
0249     KWindowConfig::restoreWindowSize(windowHandle(), dialogConfig);
0250 }
0251 
0252 void SvnCommitDialog::contextMenu(const QPoint& pos)
0253 {
0254     QTableWidgetItem *item = m_changes->item( m_changes->currentRow(), 0 );
0255     if (item == nullptr) {
0256         return;
0257     }
0258 
0259     const QVariant data = item->data(Qt::UserRole);
0260     m_actRevertFile->setData( data );
0261     m_actDiffFile->setData( data );
0262     m_actAddFile->setData( data );
0263 
0264     m_actRevertFile->setEnabled(false);
0265     m_actDiffFile->setEnabled(false);
0266     m_actAddFile->setEnabled(false);
0267 
0268     const svnCommitEntryInfo_t info = data.value<svnCommitEntryInfo_t>();
0269     switch(info.fileVersion) {
0270     case KVersionControlPlugin::UnversionedVersion:
0271         m_actAddFile->setEnabled(true);
0272         break;
0273     case KVersionControlPlugin::LocallyModifiedVersion:
0274         m_actRevertFile->setEnabled(true);
0275         m_actDiffFile->setEnabled(true);
0276         break;
0277     case KVersionControlPlugin::AddedVersion:
0278     case KVersionControlPlugin::RemovedVersion:
0279         m_actRevertFile->setEnabled(true);
0280         break;
0281     case KVersionControlPlugin::MissingVersion:
0282         m_actRevertFile->setEnabled(true);
0283         break;
0284     default:
0285         // For this items we don't show any menu: return now.
0286         return;
0287     }
0288 
0289     QMenu *menu = new QMenu(this);
0290     menu->addAction(m_actAddFile);
0291     menu->addAction(m_actRevertFile);
0292     menu->addAction(m_actDiffFile);
0293 
0294     // Adjust popup menu position for QTableWidget header height.
0295     const QPoint popupPoint = QPoint(pos.x(), pos.y() + m_changes->horizontalHeader()->height());
0296     menu->exec( m_changes->mapToGlobal(popupPoint) );
0297 }
0298 
0299 #include "moc_svncommitdialog.cpp"