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-2018 Andrius Štikonas <andrius@stikonas.eu>
0004     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
0005     SPDX-FileCopyrightText: 2016 Teo Mrnjavac <teo@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-3.0-or-later
0008 */
0009 
0010 #include "gui/sizedialogbase.h"
0011 #include "gui/sizedetailswidget.h"
0012 #include "gui/sizedialogwidget.h"
0013 
0014 #include "util/guihelpers.h"
0015 
0016 #include <core/partitiontable.h>
0017 #include <core/device.h>
0018 #include <core/lvmdevice.h>
0019 #include <core/partition.h>
0020 #include <core/partitionalignment.h>
0021 
0022 #include <gui/partresizerwidget.h>
0023 
0024 #include <util/capacity.h>
0025 
0026 #include <algorithm>
0027 
0028 #include <KLocalizedString>
0029 
0030 #include <QDialogButtonBox>
0031 #include <QFrame>
0032 #include <QtGlobal>
0033 #include <QPushButton>
0034 #include <QVBoxLayout>
0035 
0036 #include <config.h>
0037 
0038 static double sectorsToDialogUnit(const Device& d, qint64 v);
0039 static qint64 dialogUnitToSectors(const Device& d, double v);
0040 
0041 SizeDialogBase::SizeDialogBase(QWidget* parent, Device& d, Partition& part, qint64 minFirst, qint64 maxLast) :
0042     QDialog(parent),
0043     m_SizeDialogWidget(new SizeDialogWidget(this)),
0044     m_SizeDetailsWidget(new SizeDetailsWidget(this)),
0045     m_Device(d),
0046     m_Partition(part),
0047     m_MinimumFirstSector(minFirst),
0048     m_MaximumLastSector(maxLast),
0049     m_MinimumLength(-1),
0050     m_MaximumLength(-1),
0051     m_IsValidLVName(true)
0052 {
0053     QVBoxLayout *mainLayout = new QVBoxLayout(this);
0054     setLayout(mainLayout);
0055     mainLayout->addWidget(&dialogWidget());
0056     QFrame* detailsBox = new QFrame(this);
0057     mainLayout->addWidget(detailsBox);
0058     QVBoxLayout *detailsLayout = new QVBoxLayout(detailsBox);
0059     detailsLayout->addWidget(&detailsWidget());
0060     detailsWidget().hide();
0061 
0062     QDialogButtonBox* dialogButtonBox = new QDialogButtonBox(this);
0063     detailsButton = new QPushButton(dialogButtonBox);
0064     okButton = dialogButtonBox->addButton(QDialogButtonBox::Ok);
0065     cancelButton = dialogButtonBox->addButton(QDialogButtonBox::Cancel);
0066     detailsButton->setText(xi18nc("@action:button advanced settings button", "Advanced") + QStringLiteral(" >>"));
0067     dialogButtonBox->addButton(detailsButton, QDialogButtonBox::ActionRole);
0068     mainLayout->setSizeConstraint(QLayout::SetFixedSize);
0069     mainLayout->addWidget(dialogButtonBox);
0070 
0071     connect(dialogButtonBox, &QDialogButtonBox::accepted, this, &SizeDialogBase::accept);
0072     connect(dialogButtonBox, &QDialogButtonBox::rejected, this, &SizeDialogBase::reject);
0073     connect(detailsButton, &QPushButton::clicked, this, &SizeDialogBase::toggleDetails);
0074 }
0075 
0076 void SizeDialogBase::setupDialog()
0077 {
0078     dialogWidget().spinFreeBefore().setValue(sectorsToDialogUnit(device(), partition().firstSector() - minimumFirstSector()));
0079     dialogWidget().spinFreeAfter().setValue(sectorsToDialogUnit(device(), maximumLastSector() - partition().lastSector()));
0080 
0081     dialogWidget().spinCapacity().setValue(Capacity(partition().capacity()).toDouble(preferredUnit()));
0082 
0083     dialogWidget().spinFreeBefore().setSuffix(QStringLiteral(" ") + Capacity::unitName(preferredUnit()));
0084     dialogWidget().spinFreeAfter().setSuffix(QStringLiteral(" ") + Capacity::unitName(preferredUnit()));
0085     dialogWidget().spinCapacity().setSuffix(QStringLiteral(" ") + Capacity::unitName(preferredUnit()));
0086 
0087     detailsWidget().spinFirstSector().setValue(partition().firstSector());
0088     detailsWidget().spinLastSector().setValue(partition().lastSector());
0089 
0090     detailsWidget().checkAlign().setChecked(Config::alignDefault());
0091 
0092     if (canGrow() || canShrink())
0093         dialogWidget().partResizerWidget().init(device(), partition(), minimumFirstSector(), maximumLastSector(), false, canMove());
0094     else
0095         dialogWidget().partResizerWidget().init(device(), partition(), minimumFirstSector(), maximumLastSector(), true, canMove());
0096     dialogWidget().partResizerWidget().setAlign(Config::alignDefault());
0097 
0098     if (device().type() == Device::Type::Disk_Device) {
0099         dialogWidget().lvName().hide();
0100         dialogWidget().textLVName().hide();
0101     }
0102     if (device().type() == Device::Type::LVM_Device) {
0103         dialogWidget().hideBeforeAndAfter();
0104         detailsWidget().checkAlign().setChecked(false);
0105         detailsWidget().checkAlign().setEnabled(false);
0106         detailsButton->hide();
0107         m_IsValidLVName = false;
0108 
0109         /* LVM logical volume name can consist of: letters numbers _ . - +
0110          * It cannot start with underscore _ and must not be equal to . or .. or any entry in /dev/
0111          * QLineEdit accepts QValidator::Intermediate, so we just disable . at the beginning */
0112         QRegularExpression re(QStringLiteral(R"(^(?!_|\.)[\w\-.+]+)"));
0113         QRegularExpressionValidator *validator = new QRegularExpressionValidator(re, this);
0114         dialogWidget().lvName().setValidator(validator);
0115     }
0116 }
0117 
0118 void SizeDialogBase::setupConstraints()
0119 {
0120     // Do not allow moving first sector if moving partition is disabled
0121     bool moveAllowed = canMove();
0122     if (!moveAllowed)
0123         m_MinimumFirstSector = partition().firstSector();
0124     dialogWidget().spinFreeBefore().setEnabled(moveAllowed);
0125     dialogWidget().spinFreeAfter().setEnabled(moveAllowed);
0126     detailsWidget().spinFirstSector().setEnabled(moveAllowed);
0127 
0128     setMinimumLength(!canShrink() ? partition().length() : std::max(partition().sectorsUsed(), partition().minimumSectors()));
0129     setMaximumLength(!canGrow() ? partition().length() : std::min(maximumLastSector() - minimumFirstSector() + 1, partition().maximumSectors()));
0130 
0131     dialogWidget().partResizerWidget().setMinimumLength(minimumLength());
0132     dialogWidget().partResizerWidget().setMaximumLength(maximumLength());
0133 
0134     dialogWidget().labelMinSize().setText(Capacity::formatByteSize(minimumLength() * device().logicalSize()));
0135     dialogWidget().labelMaxSize().setText(Capacity::formatByteSize(maximumLength() * device().logicalSize()));
0136 
0137     dialogWidget().spinCapacity().setEnabled(canShrink() || canGrow());
0138 
0139     dialogWidget().partResizerWidget().setMaximumFirstSector(maximumFirstSector());
0140     dialogWidget().partResizerWidget().setMinimumLastSector(minimumLastSector());
0141 
0142     const qint64 totalCapacity = sectorsToDialogUnit(device(), maximumLastSector() - minimumFirstSector() + 1);
0143 
0144     const qint64 minCapacity = sectorsToDialogUnit(device(), minimumLength());
0145     const qint64 maxCapacity = sectorsToDialogUnit(device(), maximumLength());
0146     dialogWidget().spinCapacity().setRange(minCapacity, maxCapacity);
0147     minCapacity > maxCapacity ? okButton->setEnabled(false) : okButton->setEnabled(true);
0148 
0149     const qint64 maxFree = totalCapacity - minCapacity;
0150 
0151     dialogWidget().spinFreeBefore().setRange(0, maxFree);
0152     dialogWidget().spinFreeAfter().setRange(0, maxFree);
0153 
0154     detailsWidget().spinFirstSector().setRange(minimumFirstSector(), maximumLastSector());
0155     detailsWidget().spinLastSector().setRange(minimumFirstSector(), maximumLastSector());
0156 
0157     onAlignToggled(align());
0158 }
0159 
0160 void SizeDialogBase::setupConnections()
0161 {
0162     connect(&dialogWidget().partResizerWidget(), &PartResizerWidget::firstSectorChanged, this, &SizeDialogBase::onResizerWidgetFirstSectorChanged);
0163     connect(&dialogWidget().partResizerWidget(), &PartResizerWidget::lastSectorChanged, this, &SizeDialogBase::onResizerWidgetLastSectorChanged);
0164 
0165     connect(&dialogWidget().spinFreeBefore(), &QDoubleSpinBox::valueChanged, this, &SizeDialogBase::onSpinFreeBeforeChanged);
0166     connect(&dialogWidget().spinFreeAfter(), &QDoubleSpinBox::valueChanged, this, &SizeDialogBase::onSpinFreeAfterChanged);
0167     connect(&dialogWidget().spinCapacity(), &QDoubleSpinBox::valueChanged, this, &SizeDialogBase::onSpinCapacityChanged);
0168 
0169     connect(&detailsWidget().spinFirstSector(), &QDoubleSpinBox::valueChanged, this, &SizeDialogBase::onSpinFirstSectorChanged);
0170     connect(&detailsWidget().spinLastSector(), &QDoubleSpinBox::valueChanged, this, &SizeDialogBase::onSpinLastSectorChanged);
0171     connect(&detailsWidget().checkAlign(), &QCheckBox::toggled, this, &SizeDialogBase::onAlignToggled);
0172 
0173     connect(&dialogWidget().lvName(), &QLineEdit::textChanged, this, &SizeDialogBase::onLVNameChanged);
0174 }
0175 
0176 void SizeDialogBase::toggleDetails()
0177 {
0178     const bool isVisible = detailsWidget().isVisible();
0179     detailsWidget().setVisible(!isVisible);
0180     detailsButton->setText(xi18nc("@action:button", "&Advanced") + (isVisible ? QStringLiteral(" >>") : QStringLiteral(" <<")));
0181 }
0182 
0183 void SizeDialogBase::onSpinFreeBeforeChanged(double newBefore)
0184 {
0185     bool success = false;
0186 
0187     const double oldBefore = sectorsToDialogUnit(device(), partition().firstSector() - minimumFirstSector());
0188     const qint64 newFirstSector = minimumFirstSector() + dialogUnitToSectors(device(), newBefore);
0189     const qint64 deltaCorrection = newBefore > oldBefore
0190                                    ? PartitionAlignment::firstDelta(device(), partition(), newFirstSector)
0191                                    : 0;
0192 
0193     // We need different alignFirstSector parameters for moving the first sector (which
0194     // has to take into account min and max length of the partition) and for moving
0195     // the whole partition (which must NOT take min and max length into account since
0196     // the length is fixed in this case anyway)
0197 
0198     qint64 alignedFirstSector = align()
0199                                 ? PartitionAlignment::alignedFirstSector(device(), partition(), newFirstSector + deltaCorrection, minimumFirstSector(), -1, -1, -1)
0200                                 : newFirstSector;
0201 
0202     if (dialogWidget().partResizerWidget().movePartition(alignedFirstSector))
0203         success = true;
0204     else {
0205         alignedFirstSector = align()
0206                              ? PartitionAlignment::alignedFirstSector(device(), partition(), newFirstSector + deltaCorrection, minimumFirstSector(), -1, minimumLength(), maximumLength())
0207                              : newFirstSector;
0208 
0209         success = dialogWidget().partResizerWidget().updateFirstSector(alignedFirstSector);
0210     }
0211 
0212     if (success)
0213         setDirty();
0214     else
0215         // TODO: this is not the best solution: we should prevent the user from entering
0216         // illegal values with a validator
0217         updateSpinFreeBefore(dialogUnitToSectors(device(), oldBefore));
0218 }
0219 
0220 void SizeDialogBase::onSpinCapacityChanged(double newCapacity)
0221 {
0222     bool success = false;
0223 
0224     qint64 newLength = qBound(
0225                            minimumLength(),
0226                            dialogUnitToSectors(device(), newCapacity),
0227                            std::min(maximumLastSector() - minimumFirstSector() + 1, maximumLength())
0228                        );
0229 
0230     if (newLength == partition().length())
0231         return;
0232 
0233     qint64 delta = newLength - partition().length();
0234 
0235     qint64 tmp = std::min(delta, maximumLastSector() - partition().lastSector());
0236     delta -= tmp;
0237 
0238     const bool signalState = dialogWidget().partResizerWidget().blockSignals(true);
0239 
0240     if (tmp != 0) {
0241         qint64 newLastSector = partition().lastSector() + tmp;
0242 
0243         if (align())
0244             newLastSector = PartitionAlignment::alignedLastSector(device(), partition(), newLastSector, minimumLastSector(), maximumLastSector(), minimumLength(), maximumLength());
0245 
0246         if (dialogWidget().partResizerWidget().updateLastSector(newLastSector)) {
0247             success = true;
0248             updateSpinFreeAfter(maximumLastSector() - newLastSector);
0249             updateSpinLastSector(newLastSector);
0250         }
0251     }
0252 
0253     tmp = std::min(delta, partition().firstSector() - minimumFirstSector());
0254     delta -= tmp;
0255 
0256     if (tmp != 0) {
0257         qint64 newFirstSector = partition().firstSector() - tmp;
0258 
0259         if (align())
0260             newFirstSector = PartitionAlignment::alignedFirstSector(device(), partition(), newFirstSector, minimumFirstSector(), maximumFirstSector(), minimumLength(), maximumLength());
0261 
0262         if (dialogWidget().partResizerWidget().updateFirstSector(newFirstSector)) {
0263             success = true;
0264             updateSpinFreeBefore(newFirstSector - minimumFirstSector());
0265             updateSpinFirstSector(newFirstSector);
0266         }
0267     }
0268 
0269     dialogWidget().partResizerWidget().blockSignals(signalState);
0270 
0271     if (success)
0272         setDirty();
0273 }
0274 
0275 void SizeDialogBase::onSpinFreeAfterChanged(double newAfter)
0276 {
0277     bool success = false;
0278     const double oldAfter = sectorsToDialogUnit(device(), maximumLastSector() - partition().lastSector());
0279     const qint64 newLastSector = maximumLastSector() - dialogUnitToSectors(device(), newAfter);
0280     const qint64 deltaCorrection = newAfter > oldAfter
0281                                    ? PartitionAlignment::lastDelta(device(), partition(), newLastSector)
0282                                    : 0;
0283 
0284     // see onSpinFreeBeforeChanged on why this is as complicated as it is
0285 
0286     qint64 alignedLastSector = align()
0287                                ? PartitionAlignment::alignedLastSector(device(), partition(), newLastSector - deltaCorrection, -1, maximumLastSector(), -1, -1)
0288                                : newLastSector;
0289 
0290     if (dialogWidget().partResizerWidget().movePartition(alignedLastSector - partition().length() + 1))
0291         success = true;
0292     else {
0293         alignedLastSector = align()
0294                             ? PartitionAlignment::alignedLastSector(device(), partition(), newLastSector - deltaCorrection, -1, maximumLastSector(), minimumLength(), maximumLength())
0295                             : newLastSector;
0296 
0297         success = dialogWidget().partResizerWidget().updateLastSector(alignedLastSector);
0298     }
0299 
0300     if (success)
0301         setDirty();
0302     else
0303         // TODO: this is not the best solution: we should prevent the user from entering
0304         // illegal values with a validator
0305         updateSpinFreeAfter(dialogUnitToSectors(device(), oldAfter));
0306 }
0307 
0308 void SizeDialogBase::onSpinFirstSectorChanged(double newFirst)
0309 {
0310     if (newFirst >= minimumFirstSector() && dialogWidget().partResizerWidget().updateFirstSector(newFirst))
0311         setDirty();
0312     else
0313         // TODO: this is not the best solution: we should prevent the user from entering
0314         // illegal values with a validator
0315         updateSpinFirstSector(partition().firstSector());
0316 }
0317 
0318 void SizeDialogBase::onSpinLastSectorChanged(double newLast)
0319 {
0320     if (newLast <= maximumLastSector() && dialogWidget().partResizerWidget().updateLastSector(newLast))
0321         setDirty();
0322     else
0323         // TODO: this is not the best solution: we should prevent the user from entering
0324         // illegal values with a validator
0325         updateSpinLastSector(partition().lastSector());
0326 }
0327 
0328 void SizeDialogBase::onResizerWidgetFirstSectorChanged(qint64 newFirst)
0329 {
0330     updateSpinFreeBefore(newFirst - minimumFirstSector());
0331     updateSpinFirstSector(newFirst);
0332     updateSpinCapacity(partition().length());
0333     setDirty();
0334 }
0335 
0336 void SizeDialogBase::onResizerWidgetLastSectorChanged(qint64 newLast)
0337 {
0338     updateSpinFreeAfter(maximumLastSector() - newLast);
0339     updateSpinLastSector(newLast);
0340     updateSpinCapacity(partition().length());
0341     setDirty();
0342 }
0343 
0344 void SizeDialogBase::onAlignToggled(bool align)
0345 {
0346     dialogWidget().partResizerWidget().setAlign(align);
0347 
0348     detailsWidget().spinFirstSector().setSingleStep(align ? PartitionAlignment::sectorAlignment(device()) : 1);
0349     detailsWidget().spinLastSector().setSingleStep(align ? PartitionAlignment::sectorAlignment(device()) : 1);
0350 
0351     const double capacityStep = align ? sectorsToDialogUnit(device(), PartitionAlignment::sectorAlignment(device())) : 1;
0352 
0353     dialogWidget().spinFreeBefore().setSingleStep(capacityStep);
0354     dialogWidget().spinFreeBefore().setSingleStep(capacityStep);
0355     dialogWidget().spinCapacity().setSingleStep(capacityStep);
0356 
0357     // if align is on, turn off keyboard tracking for all spin boxes to avoid the two clashing
0358     const auto children = dialogWidget().findChildren<QAbstractSpinBox*>() + detailsWidget().findChildren<QAbstractSpinBox*>();
0359     for (const auto &box : children)
0360         box->setKeyboardTracking(!align);
0361 
0362     if (align) {
0363         onSpinFirstSectorChanged(partition().firstSector());
0364         onSpinLastSectorChanged(partition().lastSector());
0365     }
0366 }
0367 
0368 void SizeDialogBase::onLVNameChanged(const QString& newName)
0369 {
0370     partition().setPartitionPath(device().deviceNode() + QStringLiteral("/") + newName.trimmed());
0371     if ((dialogWidget().lvName().isVisible() &&
0372         dialogWidget().lvName().text().isEmpty()) ||
0373         (device().type() == Device::Type::LVM_Device &&
0374          dynamic_cast<LvmDevice&>(device()).partitionNodes().contains(partition().partitionPath())) ) {
0375         m_IsValidLVName = false;
0376     } else {
0377         m_IsValidLVName = true;
0378     }
0379     updateOkButtonStatus();
0380 }
0381 
0382 void SizeDialogBase::updateOkButtonStatus()
0383 {
0384     okButton->setEnabled(isValidLVName());
0385 }
0386 
0387 void SizeDialogBase::updateSpinFreeBefore(qint64 sectorsFreeBefore)
0388 {
0389     const bool signalState = dialogWidget().spinFreeBefore().blockSignals(true);
0390     dialogWidget().spinFreeBefore().setValue(sectorsToDialogUnit(device(), sectorsFreeBefore));
0391     dialogWidget().spinFreeBefore().blockSignals(signalState);
0392 }
0393 
0394 void SizeDialogBase::updateSpinCapacity(qint64 newLengthInSectors)
0395 {
0396     bool state = dialogWidget().spinCapacity().blockSignals(true);
0397     dialogWidget().spinCapacity().setValue(sectorsToDialogUnit(device(), newLengthInSectors));
0398     dialogWidget().spinCapacity().blockSignals(state);
0399 }
0400 
0401 void SizeDialogBase::updateSpinFreeAfter(qint64 sectorsFreeAfter)
0402 {
0403     const bool signalState = dialogWidget().spinFreeAfter().blockSignals(true);
0404     dialogWidget().spinFreeAfter().setValue(sectorsToDialogUnit(device(), sectorsFreeAfter));
0405     dialogWidget().spinFreeAfter().blockSignals(signalState);
0406 }
0407 
0408 void SizeDialogBase::updateSpinFirstSector(qint64 newFirst)
0409 {
0410     const bool signalState = detailsWidget().spinFirstSector().blockSignals(true);
0411     detailsWidget().spinFirstSector().setValue(newFirst);
0412     detailsWidget().spinFirstSector().blockSignals(signalState);
0413 }
0414 
0415 void SizeDialogBase::updateSpinLastSector(qint64 newLast)
0416 {
0417     const bool signalState = detailsWidget().spinLastSector().blockSignals(true);
0418     detailsWidget().spinLastSector().setValue(newLast);
0419     detailsWidget().spinLastSector().blockSignals(signalState);
0420 }
0421 
0422 const PartitionTable& SizeDialogBase::partitionTable() const
0423 {
0424     Q_ASSERT(device().partitionTable());
0425     return *device().partitionTable();
0426 }
0427 
0428 bool SizeDialogBase::align() const
0429 {
0430     return detailsWidget().checkAlign().isChecked();
0431 }
0432 
0433 qint64 SizeDialogBase::minimumLastSector() const
0434 {
0435     return partition().minLastSector();
0436 }
0437 
0438 qint64 SizeDialogBase::maximumFirstSector() const
0439 {
0440     return partition().maxFirstSector();
0441 }
0442 
0443 qint64 SizeDialogBase::minimumLength() const
0444 {
0445     return m_MinimumLength;
0446 }
0447 
0448 qint64 SizeDialogBase::maximumLength() const
0449 {
0450     return m_MaximumLength;
0451 }
0452 
0453 static double sectorsToDialogUnit(const Device& d, qint64 v)
0454 {
0455     return Capacity(v * d.logicalSize()).toDouble(preferredUnit());
0456 }
0457 
0458 static qint64 dialogUnitToSectors(const Device& d, double v)
0459 {
0460     return v * Capacity::unitFactor(Capacity::Unit::Byte, preferredUnit()) / d.logicalSize();
0461 }
0462