diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2066eba..eb237f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright (C) 2008, 2012 by Volker Lanz +# Copyright (C) 2015 by Teo Mrnjavac # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -21,6 +22,7 @@ include(util/CMakeLists.txt) include(ops/CMakeLists.txt) include(jobs/CMakeLists.txt) include(fs/CMakeLists.txt) +include(gui/CMakeLists.txt) set(kpmcore_SRCS ${BACKEND_SRC} @@ -29,6 +31,7 @@ set(kpmcore_SRCS ${OPS_SRC} ${JOBS_SRC} ${UTIL_SRC} + ${GUI_SRC} ) file(GLOB kpmcore_UIFILES config/*.ui) @@ -57,6 +60,7 @@ install(FILES ${FS_LIB_HDRS} DESTINATION ${INCLUDE_INSTALL_DIR}/kpmcore/fs/ COMP install(FILES ${JOBS_LIB_HDRS} DESTINATION ${INCLUDE_INSTALL_DIR}/kpmcore/jobs/ COMPONENT Devel) install(FILES ${OPS_LIB_HDRS} DESTINATION ${INCLUDE_INSTALL_DIR}/kpmcore/ops/ COMPONENT Devel) install(FILES ${UTIL_LIB_HDRS} DESTINATION ${INCLUDE_INSTALL_DIR}/kpmcore/util/ COMPONENT Devel) +install(FILES ${GUI_LIB_HDRS} DESTINATION ${INCLUDE_INSTALL_DIR}/kpmcore/gui/ COMPONENT Devel) ############################################ diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt new file mode 100644 index 0000000..19c580a --- /dev/null +++ b/src/gui/CMakeLists.txt @@ -0,0 +1,11 @@ +set(GUI_SRC + gui/partresizerwidget.cpp + gui/partwidget.cpp + gui/partwidgetbase.cpp +) + +set(GUI_LIB_HDRS + gui/partresizerwidget.h + gui/partwidget.h + gui/partwidgetbase.h +) \ No newline at end of file diff --git a/src/gui/partresizerwidget.cpp b/src/gui/partresizerwidget.cpp new file mode 100644 index 0000000..6629fec --- /dev/null +++ b/src/gui/partresizerwidget.cpp @@ -0,0 +1,501 @@ +/************************************************************************* + * Copyright (C) 2008, 2010 by Volker Lanz * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#include "gui/partresizerwidget.h" +#include "gui/partwidget.h" + +#include "core/partition.h" +#include "core/device.h" +#include "core/partitiontable.h" +#include "core/partitionalignment.h" + +#include "fs/filesystem.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +const qint32 PartResizerWidget::m_HandleHeight = 59; + +/** Creates a new PartResizerWidget + + Initializing is mostly done in init(). + + @param parent pointer to the parent widget +*/ +PartResizerWidget::PartResizerWidget(QWidget* parent) : + QWidget(parent), + m_Device(NULL), + m_Partition(NULL), + m_PartWidget(NULL), + m_MinimumFirstSector(0), + m_MaximumFirstSector(-1), + m_MinimumLastSector(-1), + m_MaximumLastSector(0), + m_MinimumLength(-1), + m_MaximumLength(-1), + m_LeftHandle(this), + m_RightHandle(this), + m_DraggedWidget(NULL), + m_Hotspot(0), + m_MoveAllowed(true), + m_ReadOnly(false), + m_Align(true) +{ +} + +/** Intializes the PartResizerWidget + @param d the Device the Partition is on + @param p the Partition to show and/or resize + @param minFirst the minimum value for the first sector + @param maxLast the maximum value for the last sector +*/ +void PartResizerWidget::init(Device& d, Partition& p, qint64 minFirst, qint64 maxLast, bool read_only, bool move_allowed) +{ + setDevice(d); + setPartition(p); + + setMinimumFirstSector(minFirst); + setMaximumLastSector(maxLast); + + setReadOnly(read_only); + setMoveAllowed(move_allowed); + + setMinimumLength(qMax(partition().sectorsUsed(), partition().minimumSectors())); + setMaximumLength(qMin(totalSectors(), partition().maximumSectors())); + + // set margins to accommodate to top/bottom button asymmetric layouts + QStyleOptionButton bOpt; + bOpt.initFrom(this); + + QRect buttonRect(style()->subElementRect(QStyle::SE_PushButtonContents, &bOpt)); + + int asym = (rect().bottom() - buttonRect.bottom()) - (buttonRect.top() - rect().top()); + if (asym > 0) + setContentsMargins(0, asym, 0, 0); + else + setContentsMargins(0, 0, 0, asym); + + if (!readOnly()) + { + QPixmap pixmap(handleWidth(), handleHeight()); + pixmap.fill(Qt::transparent); + QPainter p(&pixmap); + QStyleOption opt; + opt.state |= QStyle::State_Horizontal; + opt.rect = pixmap.rect().adjusted(0, 2, 0, -2); + style()->drawControl(QStyle::CE_Splitter, &opt, &p, this); + + leftHandle().setPixmap(pixmap); + rightHandle().setPixmap(pixmap); + + leftHandle().setFixedSize(handleWidth(), handleHeight()); + rightHandle().setFixedSize(handleWidth(), handleHeight()); + } + + delete m_PartWidget; + m_PartWidget = new PartWidget(this, &partition()); + + if (!readOnly()) + { + leftHandle().setCursor(Qt::SizeHorCursor); + rightHandle().setCursor(Qt::SizeHorCursor); + } + + if (moveAllowed()) + partWidget().setCursor(Qt::SizeAllCursor); + + partWidget().setToolTip(QString()); + + updatePositions(); +} + +qint32 PartResizerWidget::handleWidth() const +{ + return style()->pixelMetric(QStyle::PM_SplitterWidth); +} + +qint64 PartResizerWidget::sectorsPerPixel() const +{ + return totalSectors() / (width() - 2 * handleWidth()); +} + +int PartResizerWidget::partWidgetStart() const +{ + return handleWidth() + (partition().firstSector() - minimumFirstSector()) / sectorsPerPixel(); +} + +int PartResizerWidget::partWidgetWidth() const +{ + return partition().length() / sectorsPerPixel(); +} + +void PartResizerWidget::updatePositions() +{ + QMargins margins(contentsMargins()); + + partWidget().move(partWidgetStart() + margins.left(), margins.top()); + partWidget().resize(partWidgetWidth() - margins.left() - margins.right(), height() - margins.top() - margins.bottom()); + + leftHandle().move(partWidgetStart() - leftHandle().width(), 0); + + rightHandle().move(partWidgetStart() + partWidgetWidth(), 0); + + partWidget().update(); +} + +void PartResizerWidget::resizeEvent(QResizeEvent* event) +{ + updatePositions(); + QWidget::resizeEvent(event); +} + +void PartResizerWidget::paintEvent(QPaintEvent*) +{ + // draw sunken frame + QPainter painter(this); + QStyleOptionFrameV3 opt; + opt.initFrom(this); + opt.frameShape = QFrame::StyledPanel; + opt.rect = contentsRect(); + opt.lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); + opt.midLineWidth = 0; + opt.state |= QStyle::State_Sunken; + + style()->drawPrimitive(QStyle::PE_PanelLineEdit, &opt, &painter, this); +} + +void PartResizerWidget::mousePressEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) + { + m_DraggedWidget = static_cast(childAt(event->pos())); + + if (m_DraggedWidget != NULL) + { + if (partWidget().isAncestorOf(m_DraggedWidget)) + m_DraggedWidget = &partWidget(); + + m_Hotspot = m_DraggedWidget->mapFromParent(event->pos()).x(); + } + } +} + +bool PartResizerWidget::checkConstraints(qint64 first, qint64 last) const +{ + return (maximumFirstSector() == -1 || first <= maximumFirstSector()) && + (minimumFirstSector() == 0 || first >= minimumFirstSector()) && + (minimumLastSector() == -1 || last >= minimumLastSector()) && + (maximumLastSector() == 0 || last <= maximumLastSector()); +} + +bool PartResizerWidget::movePartition(qint64 newFirstSector) +{ + const qint64 originalLength = partition().length(); + const bool isLengthAligned = PartitionAlignment::isLengthAligned(device(), partition()); + + if (maximumFirstSector(align()) > -1 && newFirstSector > maximumFirstSector(align())) + newFirstSector = maximumFirstSector(align()); + + if (minimumFirstSector(align()) > 0 && newFirstSector < minimumFirstSector(align())) + newFirstSector = minimumFirstSector(align()); + + if (align()) + newFirstSector = PartitionAlignment::alignedFirstSector(device(), partition(), newFirstSector, minimumFirstSector(align()), maximumFirstSector(align()), -1, -1); + + qint64 delta = newFirstSector - partition().firstSector(); + + if (delta == 0) + return false; + + qint64 newLastSector = partition().lastSector() + delta; + + if (minimumLastSector(align()) > -1 && newLastSector < minimumLastSector(align())) + { + const qint64 deltaLast = minimumLastSector(align()) - newLastSector; + newFirstSector += deltaLast; + newLastSector += deltaLast; + } + + if (maximumLastSector(align()) > 0 && newLastSector > maximumLastSector(align())) + { + const qint64 deltaLast = newLastSector - maximumLastSector(align()); + newFirstSector -= deltaLast; + newLastSector -= deltaLast; + } + + if (align()) + newLastSector = PartitionAlignment::alignedLastSector(device(), partition(), newLastSector, minimumLastSector(align()), maximumLastSector(align()), -1, -1, originalLength, isLengthAligned); + + if (newLastSector == partition().lastSector()) + return false; + + if (isLengthAligned && newLastSector - newFirstSector + 1 != partition().length()) + { + 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; + return false; + } + + if (!checkConstraints(newFirstSector, newLastSector)) + { + qDebug() << "constraints not satisfied while trying to move partition " << partition().deviceNode() << ". new first: " << newFirstSector << ", new last: " << newLastSector; + return false; + } + + if (align() && !PartitionAlignment::isAligned(device(), partition(), newFirstSector, newLastSector, true)) + { + 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); + return false; + } + + if (partition().children().size() > 0 && + (!checkAlignment(*partition().children().first(), partition().firstSector() - newFirstSector) || + !checkAlignment(*partition().children().last(), partition().lastSector() - newLastSector))) + { + qDebug() << "cannot align children while trying to move partition " << partition().deviceNode(); + return false; + } + + partition().setFirstSector(newFirstSector); + partition().fileSystem().setFirstSector(newFirstSector); + + partition().setLastSector(newLastSector); + partition().fileSystem().setLastSector(newLastSector); + + updatePositions(); + + emit firstSectorChanged(partition().firstSector()); + emit lastSectorChanged(partition().lastSector()); + + return true; +} + +void PartResizerWidget::mouseMoveEvent(QMouseEvent* event) +{ + int x = event->pos().x() - m_Hotspot; + + if (draggedWidget() == &leftHandle()) + { + const qint64 newFirstSector = qMax(minimumFirstSector() + x * sectorsPerPixel(), 0LL); + updateFirstSector(newFirstSector); + } + else if (draggedWidget() == &rightHandle()) + { + const qint64 newLastSector = qMin(minimumFirstSector() + (x - rightHandle().width()) * sectorsPerPixel(), maximumLastSector()); + updateLastSector(newLastSector); + } + else if (draggedWidget() == &partWidget() && moveAllowed()) + { + const qint64 newFirstSector = qMax(minimumFirstSector() + (x - handleWidth()) * sectorsPerPixel(), 0LL); + movePartition(newFirstSector); + } +} + +void PartResizerWidget::mouseReleaseEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) + m_DraggedWidget = NULL; +} + +bool PartResizerWidget::updateFirstSector(qint64 newFirstSector) +{ + if (maximumFirstSector(align()) > -1 && newFirstSector > maximumFirstSector(align())) + newFirstSector = maximumFirstSector(align()); + + if (minimumFirstSector(align()) > 0 && newFirstSector < minimumFirstSector(align())) + newFirstSector = minimumFirstSector(align()); + + const qint64 newLength = partition().lastSector() - newFirstSector + 1; + + if (newLength < minimumLength()) + newFirstSector -= minimumLength() - newLength; + + if (newLength > maximumLength()) + newFirstSector -= newLength - maximumLength(); + + if (align()) + newFirstSector = PartitionAlignment::alignedFirstSector(device(), partition(), newFirstSector, minimumFirstSector(align()), maximumFirstSector(align()), minimumLength(), maximumLength()); + + if (newFirstSector != partition().firstSector() && (partition().children().size() == 0 || checkAlignment(*partition().children().first(), partition().firstSector() - newFirstSector))) + { + const qint64 deltaFirst = partition().firstSector() - newFirstSector; + + partition().setFirstSector(newFirstSector); + partition().fileSystem().setFirstSector(newFirstSector); + + resizeLogicals(deltaFirst, 0); + + updatePositions(); + + emit firstSectorChanged(partition().firstSector()); + + return true; + } + + return false; +} + +bool PartResizerWidget::checkAlignment(const Partition& child, qint64 delta) const +{ + // TODO: what is this exactly good for? and is it correct in non-cylinder-aligned + // situations? + if (!partition().roles().has(PartitionRole::Extended)) + return true; + + if (child.roles().has(PartitionRole::Unallocated)) + return true; + + return qAbs(delta) >= PartitionAlignment::sectorAlignment(device()); +} + +void PartResizerWidget::resizeLogicals(qint64 deltaFirst, qint64 deltaLast, bool force) +{ + if (deltaFirst != 0 && partition().children().size() > 0 && partition().children().first()->roles().has(PartitionRole::Unallocated)) + { + qint64 start = partition().children().first()->firstSector() - deltaFirst; + qint64 end = partition().children().first()->lastSector() + deltaLast; + if (PartitionTable::getUnallocatedRange(device(), partition(), start, end)) + { + partition().children().first()->setFirstSector(start); + deltaFirst = 0; + } + } + + if (deltaLast != 0 && partition().children().size() > 0 && partition().children().last()->roles().has(PartitionRole::Unallocated)) + { + qint64 start = partition().children().last()->firstSector() - deltaFirst; + qint64 end = partition().children().last()->lastSector() + deltaLast; + if (PartitionTable::getUnallocatedRange(device(), partition(), start, end)) + { + partition().children().last()->setLastSector(end); + deltaLast = 0; + } + } + + if (force || deltaFirst != 0 || deltaLast != 0) + { + Q_ASSERT(device().partitionTable()); + + device().partitionTable()->removeUnallocated(&partition()); + + if (partition().roles().has(PartitionRole::Extended)) + device().partitionTable()->insertUnallocated(device(), &partition(), partition().firstSector()); + } + + partWidget().updateChildren(); +} + +bool PartResizerWidget::updateLastSector(qint64 newLastSector) +{ + if (minimumLastSector(align()) > -1 && newLastSector < minimumLastSector(align())) + newLastSector = minimumLastSector(align()); + + if (maximumLastSector(align()) > 0 && newLastSector > maximumLastSector(align())) + newLastSector = maximumLastSector(align()); + + const qint64 newLength = newLastSector - partition().firstSector() + 1; + + if (newLength < minimumLength()) + newLastSector += minimumLength() - newLength; + + if (newLength > maximumLength()) + newLastSector -= newLength - maximumLength(); + + if (align()) + newLastSector = PartitionAlignment::alignedLastSector(device(), partition(), newLastSector, minimumLastSector(align()), maximumLastSector(align()), minimumLength(), maximumLength()); + + if (newLastSector != partition().lastSector() && (partition().children().size() == 0 || checkAlignment(*partition().children().last(), partition().lastSector() - newLastSector))) + { + const qint64 deltaLast = newLastSector - partition().lastSector(); + + partition().setLastSector(newLastSector); + partition().fileSystem().setLastSector(newLastSector); + + resizeLogicals(0, deltaLast); + updatePositions(); + + emit lastSectorChanged(partition().lastSector()); + + return true; + } + + return false; +} + +/** Sets the minimum sectors the Partition can be long. + @note This value can never be less than 0 and never be higher than totalSectors() + @param s the new minimum length +*/ +void PartResizerWidget::setMinimumLength(qint64 s) +{ + m_MinimumLength = qBound(0LL, s, totalSectors()); +} + +/** Sets the maximum sectors the Partition can be long. + @note This value can never be less than 0 and never by higher than totalSectors() + @param s the new maximum length +*/ +void PartResizerWidget::setMaximumLength(qint64 s) +{ + m_MaximumLength = qBound(0LL, s, totalSectors()); +} + +/** Sets if moving the Partition is allowed. + @param b true if moving is allowed +*/ +void PartResizerWidget::setMoveAllowed(bool b) +{ + m_MoveAllowed = b; + + if (m_PartWidget != NULL) + partWidget().setCursor(b ? Qt::SizeAllCursor : Qt::ArrowCursor); +} + +qint64 PartResizerWidget::minimumFirstSector(bool aligned) const +{ + if (!aligned || PartitionAlignment::firstDelta(device(), partition(), m_MinimumFirstSector) == 0) + return m_MinimumFirstSector; + + return m_MinimumFirstSector - PartitionAlignment::firstDelta(device(), partition(), m_MinimumFirstSector) + PartitionAlignment::sectorAlignment(device()); +} + +qint64 PartResizerWidget::maximumFirstSector(bool aligned) const +{ + return (m_MaximumFirstSector != -1 && aligned) + ? m_MaximumFirstSector - PartitionAlignment::firstDelta(device(), partition(), m_MaximumFirstSector) + : m_MaximumFirstSector; +} + +qint64 PartResizerWidget::minimumLastSector(bool aligned) const +{ + if (!aligned || PartitionAlignment::lastDelta(device(), partition(), m_MinimumLastSector) == 1) + return m_MinimumLastSector; + + return m_MinimumLastSector - PartitionAlignment::lastDelta(device(), partition(), m_MinimumLastSector) + 1 + PartitionAlignment::sectorAlignment(device()); +} + +qint64 PartResizerWidget::maximumLastSector(bool aligned) const +{ + return (m_MaximumLastSector != 0 && aligned) + ? m_MaximumLastSector - PartitionAlignment::lastDelta(device(), partition(), m_MaximumLastSector) + : m_MaximumLastSector; +} diff --git a/src/gui/partresizerwidget.h b/src/gui/partresizerwidget.h new file mode 100644 index 0000000..c20a5b5 --- /dev/null +++ b/src/gui/partresizerwidget.h @@ -0,0 +1,159 @@ +/************************************************************************* + * Copyright (C) 2008, 2010 by Volker Lanz * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#if !defined(PARTRESIZERWIDGET__H) + +#define PARTRESIZERWIDGET__H + +#include "../util/libpartitionmanagerexport.h" + +#include +#include + +class Partition; +class PartWidget; +class Device; + +class NewDialog; + +class QPaintEvent; +class QResizeEvent; +class QMouseEvent; + +/** Widget that allows the user to resize a Partition. + @author Volker Lanz +*/ +class LIBKPMCORE_EXPORT PartResizerWidget : public QWidget +{ + friend class NewDialog; + + Q_OBJECT + Q_DISABLE_COPY(PartResizerWidget) + + public: + PartResizerWidget(QWidget* parent); + + public: + void init(Device& d, Partition& p, qint64 minFirst, qint64 maxLast, bool read_only = false, bool move_allowed = true); + + qint64 totalSectors() const { return maximumLastSector() - minimumFirstSector() + 1; } /**< @return total sectors (free + Partition's length) */ + + qint64 minimumFirstSector(bool aligned = false) const; /**< @return the lowest allowed first sector */ + void setMinimumFirstSector(qint64 s) { m_MinimumFirstSector = s; } /**< @param s the new lowest allowed first sector */ + + qint64 maximumFirstSector(bool aligned = false) const; /**< @return the highest allowed first sector */ + void setMaximumFirstSector(qint64 s) { m_MaximumFirstSector = s; } /**< @param s the new highest allowed first sector */ + + qint64 minimumLastSector(bool aligned = false) const; /**< @return the lowest allowed last sector */ + void setMinimumLastSector(qint64 s) { m_MinimumLastSector = s; } /**< @param s the new lowest allowed last sector */ + + qint64 maximumLastSector(bool aligned = false) const; /**< @return the highest allowed last sector */ + void setMaximumLastSector(qint64 s) { m_MaximumLastSector = s; } /**< @param s the new highest allowed last sector */ + + void setMinimumLength(qint64 s); + qint64 minimumLength() const { return m_MinimumLength; } /**< @return minimum length for Partition */ + + void setMaximumLength(qint64 s); + qint64 maximumLength() const { return m_MaximumLength; } /**< @return maximum length for the Partition */ + + void setMoveAllowed(bool b); + bool moveAllowed() const { return m_MoveAllowed; } /**< @return true if moving the Partition is allowed */ + + bool readOnly() const { return m_ReadOnly; } /**< @return true if the widget is read only */ + void setReadOnly(bool b) { m_ReadOnly = b; } /**< @param b the new value for read only */ + + bool align() const { return m_Align; } /**< @return true if the Partition is to be aligned */ + void setAlign(bool b) { m_Align = b; } /**< @param b the new value for aligning the Partition */ + + qint32 handleWidth() const; /**< @return the handle width in pixels */ + static qint32 handleHeight() { return m_HandleHeight; } /**< @return the handle height in pixels */ + + Q_SIGNALS: + void firstSectorChanged(qint64); + void lastSectorChanged(qint64); + + public Q_SLOTS: + bool updateFirstSector(qint64 newFirstSector); + bool updateLastSector(qint64 newLastSector); + bool movePartition(qint64 newFirstSector); + + protected: + Partition& partition() { Q_ASSERT(m_Partition); return *m_Partition; } + const Partition& partition() const { Q_ASSERT(m_Partition); return *m_Partition; } + void setPartition(Partition& p) { m_Partition = &p; } + + Device& device() { Q_ASSERT(m_Device); return *m_Device; } + const Device& device() const { Q_ASSERT(m_Device); return *m_Device; } + void setDevice(Device& d) { m_Device = &d; } + + void paintEvent(QPaintEvent* event); + void resizeEvent(QResizeEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + + PartWidget& partWidget() { Q_ASSERT(m_PartWidget); return *m_PartWidget; } + const PartWidget& partWidget() const { Q_ASSERT(m_PartWidget); return *m_PartWidget; } + + void updatePositions(); + + int partWidgetStart() const; + int partWidgetWidth() const; + + QLabel& leftHandle() { return m_LeftHandle; } + QLabel& rightHandle() { return m_RightHandle; } + + qint64 sectorsPerPixel() const; + + void set(qint64 newCap, qint64 newFreeBefore, qint64 newFreeAfter); + + void resizeLogicals(qint64 deltaFirst, qint64 deltaLast, bool force = false); + + bool checkAlignment(const Partition& child, qint64 delta) const; + + QWidget* draggedWidget() { return m_DraggedWidget; } + const QWidget* draggedWidget() const { return m_DraggedWidget; } + + bool checkConstraints(qint64 first, qint64 last) const; + + private: + Device* m_Device; + Partition* m_Partition; + PartWidget* m_PartWidget; + + qint64 m_MinimumFirstSector; + qint64 m_MaximumFirstSector; + qint64 m_MinimumLastSector; + qint64 m_MaximumLastSector; + qint64 m_MinimumLength; + qint64 m_MaximumLength; + + QLabel m_LeftHandle; + QLabel m_RightHandle; + + QWidget* m_DraggedWidget; + int m_Hotspot; + + bool m_MoveAllowed; + bool m_ReadOnly; + bool m_Align; + + static const qint32 m_HandleHeight; +}; + +#endif + diff --git a/src/gui/partwidget.cpp b/src/gui/partwidget.cpp new file mode 100644 index 0000000..7eedd0b --- /dev/null +++ b/src/gui/partwidget.cpp @@ -0,0 +1,152 @@ +/************************************************************************* + * Copyright (C) 2008, 2010 by Volker Lanz * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#include "gui/partwidget.h" + +#include "core/partition.h" +#include "fs/filesystem.h" +#include "util/capacity.h" + +#include +#include +#include +#include + +#include + +/** Creates a new PartWidget + @param parent pointer to the parent widget + @param p pointer to the Partition this widget will show. must not be NULL. +*/ +PartWidget::PartWidget(QWidget* parent, const Partition* p) : + PartWidgetBase(parent), + m_Partition(NULL), + m_Active(false) +{ + setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); + init(p); +} + +void PartWidget::init(const Partition* p) +{ + m_Partition = p; + + if (partition()) + setToolTip(partition()->deviceNode() + QStringLiteral("\n") + partition()->fileSystem().name() + QStringLiteral(" ") + QString(Capacity::formatByteSize(partition()->capacity()))); + else + setToolTip(QString()); + + updateChildren(); +} + +/** Updates the widget's children */ +void PartWidget::updateChildren() +{ + if (partition()) + { + foreach (QWidget* w, childWidgets()) + { + w->setVisible(false); + w->deleteLater(); + w->setParent(NULL); + } + + foreach(const Partition* child, partition()->children()) + { + QWidget* w = new PartWidget(this, child); + w->setVisible(true); + } + + positionChildren(this, partition()->children(), childWidgets()); + } +} + +void PartWidget::resizeEvent(QResizeEvent*) +{ + if (partition()) + positionChildren(this, partition()->children(), childWidgets()); +} + +QColor PartWidget::activeColor(const QColor& col) const +{ + return isActive() ? col.darker(190) : col; +} + +void PartWidget::paintEvent(QPaintEvent*) +{ + if (partition() == NULL) + return; + + const int usedPercentage = partition()->used() * 100 / partition()->capacity(); + const int w = width() * usedPercentage / 100; + + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing); + + if (partition()->roles().has(PartitionRole::Extended)) + { + drawGradient(&painter, activeColor(Config::fileSystemColorCode(partition()->fileSystem().type())), QRect(0, 0, width(), height())); + return; + } + + const QColor base = activeColor(Config::fileSystemColorCode(partition()->fileSystem().type())); + + if (!partition()->roles().has(PartitionRole::Unallocated)) + { + const QColor dark = base.darker(105); + const QColor light = base.lighter(120); + + // draw free space background + drawGradient(&painter, light, QRect(0, 0, width(), height()), isActive()); + + // draw used space in front of that + drawGradient(&painter, dark, QRect(0, 0, w, height() - 1)); + } + else + drawGradient(&painter, base, QRect(0, 0, width(), height()), isActive()); + + // draw name and size + QString text = partition()->deviceNode().remove(QStringLiteral("/dev/")) + QStringLiteral("\n") + QString(Capacity::formatByteSize(partition()->capacity())); + + const QRect textRect(0, 0, width() - 1, height() - 1); + const QRect boundingRect = painter.boundingRect(textRect, Qt::AlignVCenter | Qt::AlignHCenter, text); + if (boundingRect.x() > PartWidgetBase::borderWidth() && boundingRect.y() > PartWidgetBase::borderHeight()) + { + if (isActive()) + painter.setPen(QColor(255, 255, 255)); + painter.drawText(textRect, Qt::AlignVCenter | Qt::AlignHCenter, text); + } +} + +void PartWidget::drawGradient(QPainter* painter, const QColor& color, const QRect& rect, bool active) const +{ + if (rect.width() < 8) + return; + + QStyleOptionButton option; + option.initFrom(this); + option.rect = rect; + option.palette.setColor(QPalette::Button, color); + option.palette.setColor(QPalette::Window, color); + option.state |= QStyle::State_Raised; + if (!active) + option.state &= ~QStyle::State_MouseOver; + else + option.state |= QStyle::State_MouseOver; + + style()->drawControl(QStyle::CE_PushButtonBevel, &option, painter, this); +} diff --git a/src/gui/partwidget.h b/src/gui/partwidget.h new file mode 100644 index 0000000..04cc6c5 --- /dev/null +++ b/src/gui/partwidget.h @@ -0,0 +1,66 @@ +/************************************************************************* + * Copyright (C) 2008, 2010 by Volker Lanz * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#if !defined(PARTWIDGET__H) + +#define PARTWIDGET__H + +#include "../util/libpartitionmanagerexport.h" + +#include "partwidgetbase.h" + +class Partition; + +class QPaintEvent; +class QResizeEvent; + +/** Widget that represents a Partition. + + Represents a single Partition (possibly with its children, in case of an extended Partition) in the GUI. + + @author Volker Lanz +*/ +class LIBKPMCORE_EXPORT PartWidget : public PartWidgetBase +{ + Q_OBJECT + + public: + explicit PartWidget(QWidget* parent, const Partition* p = NULL); + + public: + void init(const Partition* p); + void setActive(bool b) { m_Active = b; } + bool isActive() const { return m_Active; } /**< @return true if this is the currently active widget */ + void updateChildren(); + + const Partition* partition() const { return m_Partition; } /**< @return the widget's Partition */ + + protected: + void paintEvent(QPaintEvent* event); + void resizeEvent(QResizeEvent* event); + + QColor activeColor(const QColor& col) const; + + void drawGradient(QPainter* painter, const QColor& color, const QRect& rect, bool active = false) const; + + private: + const Partition* m_Partition; + bool m_Active; +}; + +#endif + diff --git a/src/gui/partwidgetbase.cpp b/src/gui/partwidgetbase.cpp new file mode 100644 index 0000000..509912a --- /dev/null +++ b/src/gui/partwidgetbase.cpp @@ -0,0 +1,155 @@ +/************************************************************************* + * Copyright (C) 2008 by Volker Lanz * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#include "gui/partwidgetbase.h" +#include "gui/partwidget.h" + +#include "core/partition.h" + +#include + +const qint32 PartWidgetBase::m_Spacing = 2; +const qint32 PartWidgetBase::m_BorderWidth = 3; +const qint32 PartWidgetBase::m_BorderHeight = 3; +const qint32 PartWidgetBase::m_MinWidth = 30; + +template +T sum(const QList& list) +{ + T rval = 0; + foreach(const T& val, list) + rval += val; + return rval; +} + +bool distributeLostPixels(QList& childrenWidth, qint32 lostPixels) +{ + if (lostPixels == 0 || childrenWidth.size() == 0) + return false; + + while (lostPixels > 0) + for (qint32 i = 0; i < childrenWidth.size() && lostPixels > 0; i++) + { + childrenWidth[i]++; + lostPixels--; + } + + return true; +} + +bool levelChildrenWidths(QList& childrenWidth, const QList& minChildrenWidth, const qint32 destWidgetWidth) +{ + if (childrenWidth.size() == 0) + return false; + + distributeLostPixels(childrenWidth, destWidgetWidth - sum(childrenWidth)); + + // if we find out a partition is too narrow, adjust its screen + // width to its minimum width and increase adjust by how much we had to increase the + // screen width. thus, in the end, we have the number of pixels we need + // to find somewhere else in adjust. + qint32 adjust = 0; + for (qint32 i = 0; i < childrenWidth.size(); i++) + if (childrenWidth[i] < minChildrenWidth[i]) + { + adjust += minChildrenWidth[i] - childrenWidth[i]; + childrenWidth[i] = minChildrenWidth[i]; + } + + // find out how many partitions are wide enough to have their width reduced; we'd love to + // check for w > minWidth - (pixels_to_reduce_by), but that last value _depends_ on the + // number we're trying to find here... + qint32 numReducable = 0; + for (qint32 i = 0; i < childrenWidth.size(); i++) + if (childrenWidth[i] > minChildrenWidth[i]) + numReducable++; + + // no need to do anything... or nothing can be done because all are too narrow + if (adjust == 0 || numReducable == 0) + return false; + + // if we have adjusted one or more partitions (and not ALL of them, because in that + // case, nothing will help us), go through the partitions again and reduce the + // on screen widths of those big enough anyway + const qint32 reduce = ceil(1.0 * adjust / numReducable); + for (qint32 i = 0; i < childrenWidth.size(); i++) + if (childrenWidth[i] > minChildrenWidth[i]) + childrenWidth[i] -= reduce; + + // distribute pixels lost due to rounding errors + distributeLostPixels(childrenWidth, destWidgetWidth - sum(childrenWidth)); + + return true; +} + +void PartWidgetBase::positionChildren(const QWidget* destWidget, const PartitionNode::Partitions& partitions, QList widgets) const +{ + if (partitions.size() == 0) + return; + + QList childrenWidth; + QList minChildrenWidth; + const qint32 destWidgetWidth = destWidget->width() - 2 * borderWidth() - (partitions.size() - 1) * spacing(); + + if (destWidgetWidth < 0) + return; + + qint64 totalLength = 0; + foreach (const Partition* p, partitions) + totalLength += p->length(); + + // calculate unleveled width for each child and store it + for (int i = 0; i < partitions.size(); i++) + { + childrenWidth.append(partitions[i]->length() * destWidgetWidth / totalLength); + + // Calculate the minimum width for the widget. This is easy for primary and logical partitions: they + // just have a fixed min width (configured in m_MinWidth). But for extended partitions things + // are not quite as simple. We need to calc the sum of the min widths for each child, taking + // spacing and borders into account, and add our own min width. + qint32 min = (minWidth() + 2 * borderWidth() + spacing()) * partitions[i]->children().size() - spacing() + 2 * borderWidth(); + + // if it's too small, this partition is a primary or logical so just use the configured value + if (min < minWidth()) + min = minWidth(); + minChildrenWidth.append(min); + } + + // now go level the widths as long as required + while (levelChildrenWidths(childrenWidth, minChildrenWidth, destWidgetWidth)) + ; + + // move the children to their positions and resize them + for (int i = 0, x = borderWidth(); i < widgets.size(); i++) + { + widgets[i]->setMinimumWidth(minChildrenWidth[i]); + widgets[i]->move(x, borderHeight()); + widgets[i]->resize(childrenWidth[i], destWidget->height() - 2 * borderHeight()); + x += childrenWidth[i] + spacing(); + } +} + +QList PartWidgetBase::childWidgets() +{ + QList rval; + + foreach(QObject* o, children()) + if (PartWidget* w = qobject_cast(o)) + rval.append(w); + + return rval; +} diff --git a/src/gui/partwidgetbase.h b/src/gui/partwidgetbase.h new file mode 100644 index 0000000..04f2e0f --- /dev/null +++ b/src/gui/partwidgetbase.h @@ -0,0 +1,64 @@ +/************************************************************************* + * Copyright (C) 2008 by Volker Lanz * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#if !defined(PARTWIDGETBASE__H) + +#define PARTWIDGETBASE__H + +#include "../util/libpartitionmanagerexport.h" + +#include + +#include +#include + +class Partition; +class PartWidget; +class QWidget; + +/** Base class for all widgets that need to position Partitions. + @author Volker Lanz +*/ +class LIBKPMCORE_EXPORT PartWidgetBase : public QWidget +{ + Q_OBJECT + Q_DISABLE_COPY(PartWidgetBase) + + protected: + PartWidgetBase(QWidget* parent) : QWidget(parent) {} + virtual ~PartWidgetBase() {} + + public: + virtual qint32 borderWidth() const { return m_BorderWidth; } /**< @return border width */ + virtual qint32 borderHeight() const { return m_BorderHeight; } /**< @return border height */ + static qint32 spacing() { return m_Spacing; } /**< @return spacing between Partitions */ + static qint32 minWidth() { return m_MinWidth; } /**< @return minimum width for a Partition widget */ + + virtual QList childWidgets(); + + protected: + virtual void positionChildren(const QWidget* destWidget, const PartitionNode::Partitions& partitions, QList widgets) const; + + private: + static const qint32 m_Spacing; + static const qint32 m_BorderWidth; + static const qint32 m_BorderHeight; + static const qint32 m_MinWidth; +}; + +#endif +