File indexing completed on 2024-04-28 05:46:35

0001 /*
0002     SPDX-FileCopyrightText: 2008-2010 Volker Lanz <vl@fidra.de>
0003     SPDX-FileCopyrightText: 2014-2020 Andrius Štikonas <andrius@stikonas.eu>
0004     SPDX-FileCopyrightText: 2014 Yuri Chornoivan <yurchor@ukr.net>
0005     SPDX-FileCopyrightText: 2018 Abhijeet Sharma <sharma.abhijeet2096@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-3.0-or-later
0008 */
0009 
0010 #include "gui/partpropsdialog.h"
0011 #include "gui/partpropswidget.h"
0012 
0013 #include <core/partition.h>
0014 #include <core/device.h>
0015 
0016 #include <fs/filesystemfactory.h>
0017 
0018 #include <util/capacity.h>
0019 #include <util/helpers.h>
0020 #include "util/guihelpers.h"
0021 
0022 #include <utility>
0023 
0024 #include <QComboBox>
0025 #include <QFontDatabase>
0026 #include <QtGlobal>
0027 #include <QLineEdit>
0028 #include <QLocale>
0029 #include <QPalette>
0030 #include <QPushButton>
0031 
0032 #include <KConfigGroup>
0033 #include <KLocalizedString>
0034 #include <KMessageBox>
0035 #include <KSharedConfig>
0036 
0037 /** Creates a new PartPropsDialog
0038     @param parent pointer to the parent widget
0039     @param d the Device the Partition is on
0040     @param p the Partition to show properties for
0041 */
0042 PartPropsDialog::PartPropsDialog(QWidget* parent, Device& d, Partition& p) :
0043     QDialog(parent),
0044     m_Device(d),
0045     m_Partition(p),
0046     m_WarnFileSystemChange(false),
0047     m_DialogWidget(new PartPropsWidget(this)),
0048     m_ReadOnly(partition().isMounted() || partition().state() == Partition::State::Copy || partition().state() == Partition::State::Restore || d.partitionTable()->isReadOnly()),
0049     m_ForceRecreate(false)
0050 {
0051     mainLayout = new QVBoxLayout(this);
0052     setLayout(mainLayout);
0053     mainLayout->addWidget(&dialogWidget());
0054 
0055     setWindowTitle(xi18nc("@title:window", "Partition properties: <filename>%1</filename>", partition().deviceNode()));
0056 
0057     setupDialog();
0058     setupConnections();
0059 
0060     KConfigGroup kcg(KSharedConfig::openConfig(), QStringLiteral("partPropsDialog"));
0061     restoreGeometry(kcg.readEntry<QByteArray>("Geometry", QByteArray()));
0062 }
0063 
0064 /** Destroys a PartPropsDialog */
0065 PartPropsDialog::~PartPropsDialog()
0066 {
0067     KConfigGroup kcg(KSharedConfig::openConfig(), QStringLiteral("partPropsDialog"));
0068     kcg.writeEntry("Geometry", saveGeometry());
0069 }
0070 
0071 /** @return the new label */
0072 QString PartPropsDialog::newLabel() const
0073 {
0074     return dialogWidget().label().text();
0075 }
0076 
0077 /** @return the new Partition flags */
0078 PartitionTable::Flags PartPropsDialog::newFlags() const
0079 {
0080     PartitionTable::Flags flags;
0081 
0082     for (int i = 0; i < dialogWidget().listFlags().count(); i++)
0083         if (dialogWidget().listFlags().item(i)->checkState() == Qt::Checked)
0084             flags |= static_cast<PartitionTable::Flag>(dialogWidget().listFlags().item(i)->data(Qt::UserRole).toInt());
0085 
0086     return flags;
0087 }
0088 
0089 /** @return the new FileSystem type */
0090 FileSystem::Type PartPropsDialog::newFileSystemType() const
0091 {
0092     return FileSystem::typeForName(dialogWidget().fileSystem().currentText());
0093 }
0094 
0095 void PartPropsDialog::setupDialog()
0096 {
0097     dialogButtonBox = new QDialogButtonBox;
0098     okButton = dialogButtonBox->addButton(QDialogButtonBox::Ok);
0099     cancelButton = dialogButtonBox->addButton(QDialogButtonBox::Cancel);
0100     mainLayout->addWidget(dialogButtonBox);
0101     okButton->setEnabled(false);
0102     cancelButton->setFocus();
0103     cancelButton->setDefault(true);
0104     connect(dialogButtonBox, &QDialogButtonBox::accepted, this, &PartPropsDialog::accept);
0105     connect(dialogButtonBox, &QDialogButtonBox::rejected, this, &PartPropsDialog::reject);
0106 
0107     dialogWidget().partWidget().init(&partition());
0108 
0109     const QString mp = partition().mountPoint().isEmpty()
0110                        ? xi18nc("@item mountpoint", "(none found)")
0111                        : partition().mountPoint();
0112     dialogWidget().mountPoint().setText(mp);
0113 
0114     const QString devPath = partition().deviceNode().isEmpty() || partition().deviceNode() == QStringLiteral("unallocated")
0115                             ? xi18nc("@item device path", "(not found)")
0116                             : partition().deviceNode();
0117     dialogWidget().devicePath().setText(devPath);
0118 
0119     dialogWidget().role().setText(partition().roles().toString());
0120 
0121     QString statusText = xi18nc("@label partition state", "idle");
0122     if (partition().isMounted()) {
0123         if (partition().roles().has(PartitionRole::Extended))
0124             statusText = xi18nc("@label partition state", "At least one logical partition is mounted.");
0125         else if (!partition().mountPoint().isEmpty())
0126             statusText = xi18nc("@label partition state", "mounted on <filename>%1</filename>", mp);
0127         else
0128             statusText = xi18nc("@label partition state", "mounted");
0129     }
0130 
0131     dialogWidget().status().setText(statusText);
0132     dialogWidget().uuid().setText(partition().fileSystem().uuid().isEmpty() ? xi18nc("@item uuid", "(none)") : partition().fileSystem().uuid());
0133 
0134     if(device().partitionTable()->type() == PartitionTable::gpt){
0135         QString PartitionLabel = partition().label().isEmpty() ? xi18nc("@item uuid", "(none)") : partition().label();
0136         QString PartitionUUID = partition().uuid().isEmpty() ? xi18nc("@item uuid", "(none)") : partition().uuid();
0137 
0138         dialogWidget().partitionLabel().setText(PartitionLabel);
0139         dialogWidget().partitionUuid().setText(PartitionUUID);
0140     }
0141     else{
0142         dialogWidget().partitionLabel().hide();
0143         dialogWidget().partitionTextLabel().hide();
0144         dialogWidget().partitionUuid().hide();
0145         dialogWidget().partitionTextUuid().hide();
0146     }
0147 
0148     setupFileSystemComboBox();
0149 
0150     // don't do this before the file system combo box has been set up!
0151     dialogWidget().label().setText(newLabel().isEmpty() ? partition().fileSystem().label() : newLabel());
0152     dialogWidget().capacity().setText(Capacity::formatByteSize(partition().capacity()));
0153 
0154     if (Capacity(partition(), Capacity::Type::Available).isValid()) {
0155         const qint64 availPercent = (partition().fileSystem().length() - partition().fileSystem().sectorsUsed()) * 100 / partition().fileSystem().length();
0156 
0157         const QString availString = QStringLiteral("%1% - %2")
0158                                     .arg(availPercent)
0159                                     .arg(Capacity::formatByteSize(partition().available()));
0160         const QString usedString = QStringLiteral("%1% - %2")
0161                                    .arg(100 - availPercent)
0162                                    .arg(Capacity::formatByteSize(partition().used()));
0163 
0164         dialogWidget().available().setText(availString);
0165         dialogWidget().used().setText(usedString);
0166     } else {
0167         dialogWidget().available().setText(Capacity::invalidString());
0168         dialogWidget().used().setText(Capacity::invalidString());
0169     }
0170 
0171     dialogWidget().firstSector().setText(QLocale().toString(partition().firstSector()));
0172     dialogWidget().lastSector().setText(QLocale().toString(partition().lastSector()));
0173     dialogWidget().numSectors().setText(QLocale().toString(partition().length()));
0174 
0175     setupFlagsList();
0176 
0177     updateHideAndShow();
0178 
0179     setMinimumSize(dialogWidget().size());
0180     resize(dialogWidget().size());
0181 }
0182 
0183 void PartPropsDialog::setupFlagsList()
0184 {
0185     int f = 1;
0186     QString s;
0187     while (!(s = PartitionTable::flagName(static_cast<PartitionTable::Flag>(f))).isEmpty()) {
0188         if (partition().availableFlags() & f) {
0189             QListWidgetItem* item = new QListWidgetItem(s);
0190             dialogWidget().listFlags().addItem(item);
0191             item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
0192             item->setData(Qt::UserRole, f);
0193             item->setCheckState((partition().activeFlags() & f) ? Qt::Checked : Qt::Unchecked);
0194         }
0195 
0196         f <<= 1;
0197     }
0198 }
0199 
0200 void PartPropsDialog::updateHideAndShow()
0201 {
0202     // create a temporary fs for some checks
0203     const FileSystem* fs = FileSystemFactory::create(newFileSystemType(), -1, -1, -1, -1, QString());
0204 
0205     if (fs == nullptr || fs->supportSetLabel() == FileSystem::cmdSupportNone) {
0206         dialogWidget().label().setReadOnly(true);
0207         dialogWidget().noSetLabel().setVisible(true);
0208         dialogWidget().noSetLabel().setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0209 
0210         QPalette palette = dialogWidget().noSetLabel().palette();
0211         QColor f = palette.color(QPalette::WindowText);
0212         f.setAlpha(128);
0213         palette.setColor(QPalette::WindowText, f);
0214         dialogWidget().noSetLabel().setPalette(palette);
0215     } else {
0216         dialogWidget().label().setReadOnly(isReadOnly() && !partition().fileSystem().supportSetLabelOnline());
0217         dialogWidget().noSetLabel().setVisible(false);
0218     }
0219 
0220     // when do we show the uuid?
0221     const bool showUuid =
0222         partition().state() != Partition::State::New &&                           // not for new partitions
0223         !(fs == nullptr || fs->supportGetUUID() == FileSystem::cmdSupportNone);   // not if the FS doesn't support it
0224 
0225     dialogWidget().showUuid(showUuid);
0226 
0227     delete fs;
0228 
0229     // when do we show available and used capacity?
0230     const bool showAvailableAndUsed =
0231         partition().state() != Partition::State::New &&             // not for new partitions
0232         !partition().roles().has(PartitionRole::Extended) &&        // neither for extended
0233         !partition().roles().has(PartitionRole::Unallocated) &&     // or for unallocated
0234         newFileSystemType() != FileSystem::Type::Unformatted;       // and not for unformatted file systems
0235 
0236     dialogWidget().showAvailable(showAvailableAndUsed);
0237     dialogWidget().showUsed(showAvailableAndUsed);
0238 
0239     // when do we show the file system combo box?
0240     const bool showFileSystem =
0241         !partition().roles().has(PartitionRole::Extended) &&        // not for extended, they have no file system
0242         !partition().roles().has(PartitionRole::Unallocated) &&     // and not for unallocated: no choice there
0243                                                                     // do not show file system comboBox for open luks volumes.
0244         !(partition().roles().has(PartitionRole::Luks) && partition().fileSystem().type() != FileSystem::Type::Luks);
0245     dialogWidget().showFileSystem(showFileSystem);
0246 
0247     // when do we show the recreate file system check box?
0248     const bool showCheckRecreate =
0249         showFileSystem &&                                                       // only if we also show the file system
0250         partition().fileSystem().supportCreate() != FileSystem::cmdSupportNone &&  // and support creating this file system
0251         partition().fileSystem().type() != FileSystem::Type::Unknown &&         // and not for unknown file systems
0252         partition().state() != Partition::State::New &&                         // or new partitions
0253         !partition().roles().has(PartitionRole::Luks);                          // or encrypted filesystems
0254 
0255     dialogWidget().showCheckRecreate(showCheckRecreate);
0256 
0257     // when do we show the list of partition flags?
0258     const bool showListFlags =
0259         partition().state() != Partition::State::New &&                         // not for new partitions
0260         !partition().roles().has(PartitionRole::Unallocated);                   // and not for unallocated space
0261 
0262     dialogWidget().showListFlags(showListFlags);
0263 
0264     dialogWidget().checkRecreate().setEnabled(!isReadOnly());
0265     dialogWidget().listFlags().setEnabled(!isReadOnly());
0266     dialogWidget().fileSystem().setEnabled(!isReadOnly() && !forceRecreate());
0267 }
0268 
0269 void PartPropsDialog::setupConnections()
0270 {
0271     connect(&dialogWidget().label(), &QLineEdit::textEdited, [this] (const QString &) {setDirty();});
0272     connect(&dialogWidget().fileSystem(), &QComboBox::currentIndexChanged, this, &PartPropsDialog::onFilesystemChanged);
0273     connect(&dialogWidget().checkRecreate(), &QCheckBox::stateChanged, this, &PartPropsDialog::onRecreate);
0274 
0275     // We want to enable the OK-button whenever the user checks or unchecks a flag in the flag list.
0276     // But it seems Qt doesn't offer a foolproof way to detect if this has happened: The currentRow/ItemChanged
0277     // signal only means the _current_ item has changed, but not necessarily that it was checked/unchecked. And
0278     // itemClicked alone isn't enough either. We choose to rather enable the OK-button too often than too
0279     // seldom.
0280     connect(&dialogWidget().listFlags(), &QListWidget::itemClicked, this, &PartPropsDialog::setDirty);
0281     connect(&dialogWidget().listFlags(), &QListWidget::currentRowChanged, [this] (int) {setDirty();});
0282 }
0283 
0284 void PartPropsDialog::setDirty(void*)
0285 {
0286     okButton->setEnabled(true);
0287     okButton->setDefault(true);
0288 }
0289 
0290 void PartPropsDialog::setupFileSystemComboBox()
0291 {
0292     dialogWidget().fileSystem().clear();
0293     QString selected;
0294     QStringList fsNames;
0295 
0296     for(const auto &fs : FileSystemFactory::map())
0297     {
0298         // If the partition isn't encrypted, skip the luks FS
0299         if (fs->type() == FileSystem::Type::Luks && partition().fileSystem().type() != FileSystem::Type::Luks)
0300             continue;
0301         if (fs->type() == FileSystem::Type::Luks2 && partition().fileSystem().type() != FileSystem::Type::Luks2)
0302             continue;
0303         if (partition().fileSystem().type() == fs->type() || (fs->supportCreate() != FileSystem::cmdSupportNone &&
0304                             partition().capacity() >= fs->minCapacity() && partition().capacity() <= fs->maxCapacity())) {
0305             QString name = fs->name();
0306 
0307             if (partition().fileSystem().type() == fs->type())
0308                 selected = name;
0309 
0310             // If the partition isn't extended, skip the extended FS
0311             if (fs->type() == FileSystem::Type::Extended && !partition().roles().has(PartitionRole::Extended))
0312                 continue;
0313 
0314             // The user cannot change the filesystem back to "unformatted" once a filesystem has been created.
0315             if (fs->type() == FileSystem::Type::Unformatted) {
0316                 // .. but if the file system is unknown to us, show the unformatted option as the currently selected one
0317                 if (partition().fileSystem().type() == FileSystem::Type::Unknown) {
0318                     name = FileSystem::nameForType(FileSystem::Type::Unformatted);
0319                     selected = name;
0320                 } else if (partition().fileSystem().type() != FileSystem::Type::Unformatted && partition().state() != Partition::State::New)
0321                     continue;
0322             }
0323 
0324             fsNames.append(name);
0325         }
0326     }
0327 
0328     std::sort(fsNames.begin(), fsNames.end(), caseInsensitiveLessThan);
0329 
0330     for (const auto &fsName : std::as_const(fsNames))
0331         dialogWidget().fileSystem().addItem(createFileSystemColor(FileSystem::typeForName(fsName), 8), fsName);
0332 
0333     dialogWidget().fileSystem().setCurrentIndex(dialogWidget().fileSystem().findText(selected));
0334 
0335     const FileSystem* fs = FileSystemFactory::create(FileSystem::typeForName(dialogWidget().fileSystem().currentText()), -1, -1, -1, -1, QString());
0336     dialogWidget().m_EditLabel->setMaxLength(fs->maxLabelLength());
0337     dialogWidget().m_EditLabel->setValidator(fs->labelValidator(dialogWidget().m_EditLabel));
0338 }
0339 
0340 void PartPropsDialog::updatePartitionFileSystem()
0341 {
0342     FileSystem* fs = FileSystemFactory::create(newFileSystemType(), partition().firstSector(), partition().lastSector(), partition().sectorSize());
0343     partition().deleteFileSystem();
0344     partition().setFileSystem(fs);
0345     dialogWidget().partWidget().update();
0346 }
0347 
0348 void PartPropsDialog::onFilesystemChanged(int)
0349 {
0350     if (partition().state() == Partition::State::New || warnFileSystemChange() || KMessageBox::warningContinueCancel(this,
0351             xi18nc("@info", "<para><warning>You are about to lose all data on partition <filename>%1</filename>.</warning></para>"
0352                    "<para>Changing the file system on a partition already on disk will erase all its contents. If you continue now and apply the resulting operation in the main window, all data on <filename>%1</filename> will unrecoverably be lost.</para>", partition().deviceNode()),
0353             xi18nc("@title:window", "Really Recreate <filename>%1</filename> with File System %2?", partition().deviceNode(), dialogWidget().fileSystem().currentText()),
0354             KGuiItem(xi18nc("@action:button", "Change the File System"), QStringLiteral("arrow-right")),
0355             KGuiItem(xi18nc("@action:button", "Do Not Change the File System"), QStringLiteral("dialog-cancel")), QStringLiteral("reallyChangeFileSystem")) == KMessageBox::Continue) {
0356         setDirty();
0357         updateHideAndShow();
0358         setWarnFileSystemChange();
0359         updatePartitionFileSystem();
0360 
0361         const FileSystem* fs = FileSystemFactory::create(FileSystem::typeForName(dialogWidget().fileSystem().currentText()), -1, -1, -1, -1, QString());
0362         dialogWidget().m_EditLabel->setMaxLength(fs->maxLabelLength());
0363         dialogWidget().m_EditLabel->setValidator(fs->labelValidator(dialogWidget().m_EditLabel));
0364     } else {
0365         dialogWidget().fileSystem().disconnect(this);
0366         setupFileSystemComboBox();
0367         connect(&dialogWidget().fileSystem(), &QComboBox::currentIndexChanged, this, &PartPropsDialog::onFilesystemChanged);
0368     }
0369 }
0370 
0371 void PartPropsDialog::onRecreate(int state)
0372 {
0373     if (state == Qt::Checked && (warnFileSystemChange() || KMessageBox::warningContinueCancel(this,
0374                                  xi18nc("@info", "<para><warning>You are about to lose all data on partition <filename>%1</filename>.</warning></para>"
0375                                         "<para>Recreating a file system will erase all its contents. If you continue now and apply the resulting operation in the main window, all data on <filesystem>%1</filesystem> will unrecoverably be lost.</para>", partition().deviceNode()),
0376                                  xi18nc("@title:window", "Really Recreate File System on <filename>%1</filename>?", partition().deviceNode()),
0377                                  KGuiItem(xi18nc("@action:button", "Recreate the File System"), QStringLiteral("arrow-right")),
0378                                  KGuiItem(xi18nc("@action:button", "Do Not Recreate the File System"), QStringLiteral("dialog-cancel")), QStringLiteral("reallyRecreateFileSystem")) == KMessageBox::Continue)) {
0379         setDirty();
0380         setWarnFileSystemChange();
0381         setForceRecreate(true);
0382         dialogWidget().fileSystem().setCurrentIndex(dialogWidget().fileSystem().findText(partition().fileSystem().name()));
0383         dialogWidget().fileSystem().setEnabled(false);
0384         updateHideAndShow();
0385         updatePartitionFileSystem();
0386     } else {
0387         setForceRecreate(false);
0388         dialogWidget().checkRecreate().setCheckState(Qt::Unchecked);
0389         dialogWidget().fileSystem().setEnabled(true);
0390         updateHideAndShow();
0391     }
0392 }