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