File indexing completed on 2024-04-28 05:45:53
0001 /* 0002 SPDX-FileCopyrightText: 2008-2010 Volker Lanz <vl@fidra.de> 0003 SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo@oxygen-icons.org> 0004 SPDX-FileCopyrightText: 2012-2020 Andrius Štikonas <andrius@stikonas.eu 0005 SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org> 0006 SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org> 0007 SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com> 0008 0009 SPDX-License-Identifier: GPL-3.0-or-later 0010 */ 0011 0012 #include "gui/partresizerwidget.h" 0013 #include "gui/partwidget.h" 0014 0015 #include "core/partition.h" 0016 #include "core/device.h" 0017 #include "core/partitiontable.h" 0018 #include "core/partitionalignment.h" 0019 0020 #include "fs/filesystem.h" 0021 0022 #include <algorithm> 0023 0024 #include <QDebug> 0025 #include <QPainter> 0026 #include <QMouseEvent> 0027 #include <QPaintEvent> 0028 #include <QResizeEvent> 0029 #include <QStyleOptionToolBar> 0030 #include <QStyleOptionFrame> 0031 #include <QStyleOptionButton> 0032 0033 const qint32 PartResizerWidget::m_HandleHeight = 59; 0034 0035 /** Creates a new PartResizerWidget 0036 0037 Initializing is mostly done in init(). 0038 0039 @param parent pointer to the parent widget 0040 */ 0041 PartResizerWidget::PartResizerWidget(QWidget* parent) : 0042 QWidget(parent), 0043 m_Device(nullptr), 0044 m_Partition(nullptr), 0045 m_PartWidget(nullptr), 0046 m_MinimumFirstSector(0), 0047 m_MaximumFirstSector(-1), 0048 m_MinimumLastSector(-1), 0049 m_MaximumLastSector(0), 0050 m_MinimumLength(-1), 0051 m_MaximumLength(-1), 0052 m_LeftHandle(this), 0053 m_RightHandle(this), 0054 m_DraggedWidget(nullptr), 0055 m_Hotspot(0), 0056 m_MoveAllowed(true), 0057 m_ReadOnly(false), 0058 m_Align(true) 0059 { 0060 } 0061 0062 /** Intializes the PartResizerWidget 0063 @param d the Device the Partition is on 0064 @param p the Partition to show and/or resize 0065 @param minFirst the minimum value for the first sector 0066 @param maxLast the maximum value for the last sector 0067 */ 0068 void PartResizerWidget::init(Device& d, Partition& p, qint64 minFirst, qint64 maxLast, bool read_only, bool move_allowed) 0069 { 0070 setDevice(d); 0071 setPartition(p); 0072 0073 setMinimumFirstSector(minFirst); 0074 setMaximumLastSector(maxLast); 0075 0076 setReadOnly(read_only); 0077 setMoveAllowed(move_allowed); 0078 0079 setMinimumLength(std::max(partition().sectorsUsed(), partition().minimumSectors())); 0080 setMaximumLength(std::min(totalSectors(), partition().maximumSectors())); 0081 0082 // set margins to accommodate to top/bottom button asymmetric layouts 0083 QStyleOptionButton bOpt; 0084 bOpt.initFrom(this); 0085 0086 QRect buttonRect(style()->subElementRect(QStyle::SE_PushButtonContents, &bOpt)); 0087 0088 int asym = (rect().bottom() - buttonRect.bottom()) - (buttonRect.top() - rect().top()); 0089 if (asym > 0) 0090 setContentsMargins(0, asym, 0, 0); 0091 else 0092 setContentsMargins(0, 0, 0, asym); 0093 0094 if (!readOnly()) { 0095 QPixmap pixmap(handleWidth(), handleHeight()); 0096 pixmap.fill(Qt::transparent); 0097 QPainter painter(&pixmap); 0098 QStyleOption opt; 0099 opt.state |= QStyle::State_Horizontal; 0100 opt.rect = pixmap.rect().adjusted(0, 2, 0, -2); 0101 style()->drawControl(QStyle::CE_Splitter, &opt, &painter, this); 0102 0103 if (moveAllowed()) 0104 leftHandle().setPixmap(pixmap); 0105 rightHandle().setPixmap(pixmap); 0106 0107 if (moveAllowed()) 0108 leftHandle().setFixedSize(handleWidth(), handleHeight()); 0109 rightHandle().setFixedSize(handleWidth(), handleHeight()); 0110 } 0111 0112 delete m_PartWidget; 0113 m_PartWidget = new PartWidget(this, &partition()); 0114 0115 if (!readOnly()) { 0116 if (moveAllowed()) 0117 leftHandle().setCursor(Qt::SizeHorCursor); 0118 rightHandle().setCursor(Qt::SizeHorCursor); 0119 } 0120 0121 if (moveAllowed()) 0122 partWidget().setCursor(Qt::SizeAllCursor); 0123 0124 partWidget().setToolTip(QString()); 0125 0126 updatePositions(); 0127 } 0128 0129 qint32 PartResizerWidget::handleWidth() const 0130 { 0131 return style()->pixelMetric(QStyle::PM_SplitterWidth); 0132 } 0133 0134 long double PartResizerWidget::sectorsPerPixel() const 0135 { 0136 return totalSectors() / (width() - 2.0L * handleWidth()); 0137 } 0138 0139 int PartResizerWidget::partWidgetStart() const 0140 { 0141 return static_cast<int>(handleWidth() + (partition().firstSector() - minimumFirstSector()) / sectorsPerPixel()); 0142 } 0143 0144 int PartResizerWidget::partWidgetWidth() const 0145 { 0146 return static_cast<int>(partition().length() / sectorsPerPixel()); 0147 } 0148 0149 void PartResizerWidget::updatePositions() 0150 { 0151 QMargins margins(contentsMargins()); 0152 0153 partWidget().move(partWidgetStart() + margins.left(), margins.top()); 0154 partWidget().resize(partWidgetWidth() - margins.left() - margins.right(), height() - margins.top() - margins.bottom()); 0155 0156 leftHandle().move(partWidgetStart() - leftHandle().width(), 0); 0157 0158 rightHandle().move(partWidgetStart() + partWidgetWidth(), 0); 0159 0160 partWidget().update(); 0161 } 0162 0163 void PartResizerWidget::resizeEvent(QResizeEvent* event) 0164 { 0165 updatePositions(); 0166 QWidget::resizeEvent(event); 0167 } 0168 0169 void PartResizerWidget::paintEvent(QPaintEvent*) 0170 { 0171 // draw sunken frame 0172 QPainter painter(this); 0173 QStyleOptionFrame opt; 0174 opt.initFrom(this); 0175 opt.frameShape = QFrame::StyledPanel; 0176 opt.rect = contentsRect(); 0177 opt.lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); 0178 opt.midLineWidth = 0; 0179 opt.state |= QStyle::State_Sunken; 0180 0181 style()->drawPrimitive(QStyle::PE_PanelLineEdit, &opt, &painter, this); 0182 } 0183 0184 void PartResizerWidget::mousePressEvent(QMouseEvent* event) 0185 { 0186 if (event->button() == Qt::LeftButton) { 0187 m_DraggedWidget = static_cast<QWidget*>(childAt(event->pos())); 0188 0189 if (m_DraggedWidget != nullptr) { 0190 if (partWidget().isAncestorOf(m_DraggedWidget)) 0191 m_DraggedWidget = &partWidget(); 0192 0193 m_Hotspot = m_DraggedWidget->mapFromParent(event->pos()).x(); 0194 } 0195 } 0196 } 0197 0198 bool PartResizerWidget::checkConstraints(qint64 first, qint64 last) const 0199 { 0200 return (maximumFirstSector() == -1 || first <= maximumFirstSector()) && 0201 (minimumFirstSector() == 0 || first >= minimumFirstSector()) && 0202 (minimumLastSector() == -1 || last >= minimumLastSector()) && 0203 (maximumLastSector() == 0 || last <= maximumLastSector()); 0204 } 0205 0206 bool PartResizerWidget::movePartition(qint64 newFirstSector) 0207 { 0208 const qint64 originalLength = partition().length(); 0209 const bool isLengthAligned = PartitionAlignment::isLengthAligned(device(), partition()); 0210 0211 if (maximumFirstSector(align()) > -1 && newFirstSector > maximumFirstSector(align())) 0212 newFirstSector = maximumFirstSector(align()); 0213 0214 if (minimumFirstSector(align()) > 0 && newFirstSector < minimumFirstSector(align())) 0215 newFirstSector = minimumFirstSector(align()); 0216 0217 if (align()) 0218 newFirstSector = PartitionAlignment::alignedFirstSector(device(), partition(), newFirstSector, minimumFirstSector(align()), maximumFirstSector(align()), -1, -1); 0219 0220 qint64 delta = newFirstSector - partition().firstSector(); 0221 0222 if (delta == 0) 0223 return false; 0224 0225 qint64 newLastSector = partition().lastSector() + delta; 0226 0227 if (minimumLastSector(align()) > -1 && newLastSector < minimumLastSector(align())) { 0228 const qint64 deltaLast = minimumLastSector(align()) - newLastSector; 0229 newFirstSector += deltaLast; 0230 newLastSector += deltaLast; 0231 } 0232 0233 if (maximumLastSector(align()) > 0 && newLastSector > maximumLastSector(align())) { 0234 const qint64 deltaLast = newLastSector - maximumLastSector(align()); 0235 newFirstSector -= deltaLast; 0236 newLastSector -= deltaLast; 0237 } 0238 0239 if (align()) 0240 newLastSector = PartitionAlignment::alignedLastSector(device(), partition(), newLastSector, minimumLastSector(align()), maximumLastSector(align()), -1, -1, originalLength, isLengthAligned); 0241 0242 if (newLastSector == partition().lastSector()) 0243 return false; 0244 0245 if (isLengthAligned && newLastSector - newFirstSector + 1 != partition().length()) { 0246 qDebug() << "length changes while trying to move partition " << partition().deviceNode() << ". new first: " << newFirstSector << ", new last: " << newLastSector << ", old length: " << partition().length() << ", new length: " << newLastSector - newFirstSector + 1; 0247 return false; 0248 } 0249 0250 if (!checkConstraints(newFirstSector, newLastSector)) { 0251 qDebug() << "constraints not satisfied while trying to move partition " << partition().deviceNode() << ". new first: " << newFirstSector << ", new last: " << newLastSector; 0252 return false; 0253 } 0254 0255 if (align() && !PartitionAlignment::isAligned(device(), partition(), newFirstSector, newLastSector, true)) { 0256 qDebug() << "partition " << partition().deviceNode() << " not aligned but supposed to be. new first: " << newFirstSector << " delta: " << PartitionAlignment::firstDelta(device(), partition(), newFirstSector) << ", new last: " << newLastSector << ", delta: " << PartitionAlignment::lastDelta(device(), partition(), newLastSector); 0257 return false; 0258 } 0259 0260 if (partition().children().size() > 0 && 0261 (!checkAlignment(*partition().children().first(), partition().firstSector() - newFirstSector) || 0262 !checkAlignment(*partition().children().last(), partition().lastSector() - newLastSector))) { 0263 qDebug() << "cannot align children while trying to move partition " << partition().deviceNode(); 0264 return false; 0265 } 0266 0267 partition().setFirstSector(newFirstSector); 0268 partition().fileSystem().setFirstSector(newFirstSector); 0269 0270 partition().setLastSector(newLastSector); 0271 partition().fileSystem().setLastSector(newLastSector); 0272 0273 updatePositions(); 0274 0275 Q_EMIT firstSectorChanged(partition().firstSector()); 0276 Q_EMIT lastSectorChanged(partition().lastSector()); 0277 0278 return true; 0279 } 0280 0281 void PartResizerWidget::mouseMoveEvent(QMouseEvent* event) 0282 { 0283 int x = event->pos().x() - m_Hotspot; 0284 0285 if (draggedWidget() == &leftHandle()) { 0286 const qint64 newFirstSector = static_cast<qint64>(std::max(minimumFirstSector() + x * sectorsPerPixel(), 0.0L)); 0287 updateFirstSector(newFirstSector); 0288 } else if (draggedWidget() == &rightHandle()) { 0289 const qint64 newLastSector = static_cast<qint64>(std::min(static_cast<qint64>(minimumFirstSector() + (x - rightHandle().width()) * sectorsPerPixel()), maximumLastSector())); 0290 updateLastSector(newLastSector); 0291 } else if (draggedWidget() == &partWidget() && moveAllowed()) { 0292 const qint64 newFirstSector = static_cast<qint64>(std::max(minimumFirstSector() + (x - handleWidth()) * sectorsPerPixel(), 0.0L)); 0293 movePartition(newFirstSector); 0294 } 0295 } 0296 0297 void PartResizerWidget::mouseReleaseEvent(QMouseEvent* event) 0298 { 0299 if (event->button() == Qt::LeftButton) 0300 m_DraggedWidget = nullptr; 0301 } 0302 0303 bool PartResizerWidget::updateFirstSector(qint64 newFirstSector) 0304 { 0305 if (maximumFirstSector(align()) > -1 && newFirstSector > maximumFirstSector(align())) 0306 newFirstSector = maximumFirstSector(align()); 0307 0308 if (minimumFirstSector(align()) > 0 && newFirstSector < minimumFirstSector(align())) 0309 newFirstSector = minimumFirstSector(align()); 0310 0311 const qint64 newLength = partition().lastSector() - newFirstSector + 1; 0312 0313 if (newLength < minimumLength()) 0314 newFirstSector -= minimumLength() - newLength; 0315 0316 if (newLength > maximumLength()) 0317 newFirstSector -= newLength - maximumLength(); 0318 0319 if (align()) 0320 newFirstSector = PartitionAlignment::alignedFirstSector(device(), partition(), newFirstSector, minimumFirstSector(align()), maximumFirstSector(align()), minimumLength(), maximumLength()); 0321 0322 if (newFirstSector != partition().firstSector() && (partition().children().size() == 0 || checkAlignment(*partition().children().first(), partition().firstSector() - newFirstSector))) { 0323 const qint64 deltaFirst = partition().firstSector() - newFirstSector; 0324 0325 partition().setFirstSector(newFirstSector); 0326 partition().fileSystem().setFirstSector(newFirstSector); 0327 0328 resizeLogicals(deltaFirst, 0); 0329 0330 updatePositions(); 0331 0332 Q_EMIT firstSectorChanged(partition().firstSector()); 0333 0334 return true; 0335 } 0336 0337 return false; 0338 } 0339 0340 bool PartResizerWidget::checkAlignment(const Partition& child, qint64 delta) const 0341 { 0342 // TODO: what is this exactly good for? and is it correct in non-cylinder-aligned 0343 // situations? 0344 if (!partition().roles().has(PartitionRole::Extended)) 0345 return true; 0346 0347 if (child.roles().has(PartitionRole::Unallocated)) 0348 return true; 0349 0350 return qAbs(delta) >= PartitionAlignment::sectorAlignment(device()); 0351 } 0352 0353 void PartResizerWidget::resizeLogicals(qint64 deltaFirst, qint64 deltaLast, bool force) 0354 { 0355 if (deltaFirst != 0 && partition().children().size() > 0 && partition().children().first()->roles().has(PartitionRole::Unallocated)) { 0356 qint64 start = partition().children().first()->firstSector() - deltaFirst; 0357 qint64 end = partition().children().first()->lastSector() + deltaLast; 0358 if (PartitionTable::getUnallocatedRange(device(), partition(), start, end)) { 0359 partition().children().first()->setFirstSector(start); 0360 deltaFirst = 0; 0361 } 0362 } 0363 0364 if (deltaLast != 0 && partition().children().size() > 0 && partition().children().last()->roles().has(PartitionRole::Unallocated)) { 0365 qint64 start = partition().children().last()->firstSector() - deltaFirst; 0366 qint64 end = partition().children().last()->lastSector() + deltaLast; 0367 if (PartitionTable::getUnallocatedRange(device(), partition(), start, end)) { 0368 partition().children().last()->setLastSector(end); 0369 deltaLast = 0; 0370 } 0371 } 0372 0373 if (force || deltaFirst != 0 || deltaLast != 0) { 0374 Q_ASSERT(device().partitionTable()); 0375 0376 device().partitionTable()->removeUnallocated(&partition()); 0377 0378 if (partition().roles().has(PartitionRole::Extended)) 0379 device().partitionTable()->insertUnallocated(device(), &partition(), partition().firstSector()); 0380 } 0381 0382 partWidget().updateChildren(); 0383 } 0384 0385 bool PartResizerWidget::updateLastSector(qint64 newLastSector) 0386 { 0387 if (minimumLastSector(align()) > -1 && newLastSector < minimumLastSector(align())) 0388 newLastSector = minimumLastSector(align()); 0389 0390 if (maximumLastSector(align()) > 0 && newLastSector > maximumLastSector(align())) 0391 newLastSector = maximumLastSector(align()); 0392 0393 const qint64 newLength = newLastSector - partition().firstSector() + 1; 0394 0395 if (newLength < minimumLength()) 0396 newLastSector += minimumLength() - newLength; 0397 0398 if (newLength > maximumLength()) 0399 newLastSector -= newLength - maximumLength(); 0400 0401 if (align()) 0402 newLastSector = PartitionAlignment::alignedLastSector(device(), partition(), newLastSector, minimumLastSector(align()), maximumLastSector(align()), minimumLength(), maximumLength()); 0403 0404 if (newLastSector != partition().lastSector() && (partition().children().size() == 0 || checkAlignment(*partition().children().last(), partition().lastSector() - newLastSector))) { 0405 const qint64 deltaLast = newLastSector - partition().lastSector(); 0406 0407 partition().setLastSector(newLastSector); 0408 partition().fileSystem().setLastSector(newLastSector); 0409 0410 resizeLogicals(0, deltaLast); 0411 updatePositions(); 0412 0413 Q_EMIT lastSectorChanged(partition().lastSector()); 0414 0415 return true; 0416 } 0417 0418 return false; 0419 } 0420 0421 /** Sets the minimum sectors the Partition can be long. 0422 @note This value can never be less than 0 and never be higher than totalSectors() 0423 @param s the new minimum length 0424 */ 0425 void PartResizerWidget::setMinimumLength(qint64 s) 0426 { 0427 m_MinimumLength = qBound(0LL, s, totalSectors()); 0428 } 0429 0430 /** Sets the maximum sectors the Partition can be long. 0431 @note This value can never be less than 0 and never by higher than totalSectors() 0432 @param s the new maximum length 0433 */ 0434 void PartResizerWidget::setMaximumLength(qint64 s) 0435 { 0436 m_MaximumLength = qBound(0LL, s, totalSectors()); 0437 } 0438 0439 /** Sets if moving the Partition is allowed. 0440 @param b true if moving is allowed 0441 */ 0442 void PartResizerWidget::setMoveAllowed(bool b) 0443 { 0444 m_MoveAllowed = b; 0445 0446 if (m_PartWidget != nullptr) 0447 partWidget().setCursor(b ? Qt::SizeAllCursor : Qt::ArrowCursor); 0448 } 0449 0450 qint64 PartResizerWidget::minimumFirstSector(bool aligned) const 0451 { 0452 if (!aligned || PartitionAlignment::firstDelta(device(), partition(), m_MinimumFirstSector) == 0) 0453 return m_MinimumFirstSector; 0454 0455 return m_MinimumFirstSector - PartitionAlignment::firstDelta(device(), partition(), m_MinimumFirstSector) + PartitionAlignment::sectorAlignment(device()); 0456 } 0457 0458 qint64 PartResizerWidget::maximumFirstSector(bool aligned) const 0459 { 0460 return (m_MaximumFirstSector != -1 && aligned) 0461 ? m_MaximumFirstSector - PartitionAlignment::firstDelta(device(), partition(), m_MaximumFirstSector) 0462 : m_MaximumFirstSector; 0463 } 0464 0465 qint64 PartResizerWidget::minimumLastSector(bool aligned) const 0466 { 0467 if (!aligned || PartitionAlignment::lastDelta(device(), partition(), m_MinimumLastSector) == 1) 0468 return m_MinimumLastSector; 0469 0470 return m_MinimumLastSector - PartitionAlignment::lastDelta(device(), partition(), m_MinimumLastSector) + 1 + PartitionAlignment::sectorAlignment(device()); 0471 } 0472 0473 qint64 PartResizerWidget::maximumLastSector(bool aligned) const 0474 { 0475 return (m_MaximumLastSector != 0 && aligned) 0476 ? m_MaximumLastSector - PartitionAlignment::lastDelta(device(), partition(), m_MaximumLastSector) 0477 : m_MaximumLastSector; 0478 } 0479 0480 #include "moc_partresizerwidget.cpp"