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"