source: calamares/trunk/fuentes/src/modules/partition/gui/PartitionSplitterWidget.cpp @ 7538

Last change on this file since 7538 was 7538, checked in by kbut, 13 months ago

sync with github

File size: 19.7 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014-2016, Teo Mrnjavac <teo@kde.org>
4 *
5 *   Calamares is free software: you can redistribute it and/or modify
6 *   it under the terms of the GNU General Public License as published by
7 *   the Free Software Foundation, either version 3 of the License, or
8 *   (at your option) any later version.
9 *
10 *   Calamares is distributed in the hope that it will be useful,
11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 *   GNU General Public License for more details.
14 *
15 *   You should have received a copy of the GNU General Public License
16 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "PartitionSplitterWidget.h"
20
21#include "core/ColorUtils.h"
22#include "core/PartitionIterator.h"
23#include "core/KPMHelpers.h"
24
25#include "utils/Logger.h"
26#include "utils/CalamaresUtilsGui.h"
27
28#include <kpmcore/core/device.h>
29#include <kpmcore/core/partition.h>
30
31#include <QApplication>
32#include <QPainter>
33#include <QMouseEvent>
34#include <QStyleOption>
35
36static const int VIEW_HEIGHT = qMax( CalamaresUtils::defaultFontHeight() + 8, // wins out with big fonts
37                                     int( CalamaresUtils::defaultFontHeight() * 0.6 ) + 22 ); // wins out with small fonts
38static const int CORNER_RADIUS = 3;
39static const int EXTENDED_PARTITION_MARGIN = qMax( 4, VIEW_HEIGHT / 6 );
40
41PartitionSplitterWidget::PartitionSplitterWidget( QWidget* parent )
42    : QWidget( parent )
43    , m_itemToResize( PartitionSplitterItem::null() )
44    , m_itemToResizeNext( PartitionSplitterItem::null() )
45    , m_itemMinSize( 0 )
46    , m_itemMaxSize( 0 )
47    , m_itemPrefSize( 0 )
48    , m_resizing( false )
49    , m_resizeHandleX( 0 )
50    , HANDLE_SNAP( QApplication::startDragDistance() )
51    , m_drawNestedPartitions( false )
52{
53    setMouseTracking( true );
54}
55
56
57void
58PartitionSplitterWidget::init( Device* dev, bool drawNestedPartitions )
59{
60    m_drawNestedPartitions = drawNestedPartitions;
61    QVector< PartitionSplitterItem > allPartitionItems;
62    PartitionSplitterItem* extendedPartitionItem = nullptr;
63    for ( auto it = PartitionIterator::begin( dev );
64          it != PartitionIterator::end( dev ); ++it )
65    {
66        PartitionSplitterItem newItem = {
67            ( *it )->partitionPath(),
68            ColorUtils::colorForPartition( *it ),
69            KPMHelpers::isPartitionFreeSpace( *it ),
70            ( *it )->capacity(),
71            PartitionSplitterItem::Normal,
72            {}
73        };
74
75        // If we don't draw child partitions of a partitions as child partitions, we
76        // need to flatten the items tree into an items list
77        if ( drawNestedPartitions )
78        {
79            if ( ( *it )->roles().has( PartitionRole::Logical ) && extendedPartitionItem )
80                extendedPartitionItem->children.append( newItem );
81            else
82            {
83                allPartitionItems.append( newItem );
84                if ( ( *it )->roles().has( PartitionRole::Extended ) )
85                    extendedPartitionItem = &allPartitionItems.last();
86            }
87        }
88        else
89        {
90            if ( !( *it )->roles().has( PartitionRole::Extended ) )
91                allPartitionItems.append( newItem );
92        }
93    }
94
95    setupItems( allPartitionItems );
96}
97
98void
99PartitionSplitterWidget::setupItems( const QVector<PartitionSplitterItem>& items )
100{
101    m_itemToResize = PartitionSplitterItem::null();
102    m_itemToResizeNext = PartitionSplitterItem::null();
103    m_itemToResizePath.clear();
104
105    m_items.clear();
106    m_items = items;
107    repaint();
108    for ( const PartitionSplitterItem& item : items )
109        cDebug() << "PSI added item" << item.itemPath << "size" << item.size;
110}
111
112
113void
114PartitionSplitterWidget::setSplitPartition( const QString& path,
115                                            qint64 minSize,
116                                            qint64 maxSize,
117                                            qint64 preferredSize )
118{
119    cDebug() << Q_FUNC_INFO << "path:" << path
120             << "\nminSize:" << minSize
121             << "\nmaxSize:" << maxSize
122             << "\nprfSize:" << preferredSize;
123
124    if ( m_itemToResize && m_itemToResizeNext )
125    {
126        cDebug() << "NOTICE: trying to split partition but partition to split is already set.";
127
128        // We need to remove the itemToResizeNext from wherever it is
129        for ( int i = 0; i < m_items.count(); ++i )
130        {
131            if ( m_items[ i ].itemPath == m_itemToResize.itemPath &&
132                 m_items[ i ].status == PartitionSplitterItem::Resizing &&
133                 i + 1 < m_items.count() )
134            {
135                m_items[ i ].size = m_items[ i ].size + m_itemToResizeNext.size;
136                m_items[ i ].status = PartitionSplitterItem::Normal;
137                m_items.removeAt( i + 1 );
138                m_itemToResizeNext = PartitionSplitterItem::null();
139                break;
140            }
141            else if ( !m_items[ i ].children.isEmpty() )
142            {
143                for ( int j = 0; j < m_items[ i ].children.count(); ++j )
144                {
145                    if ( m_items[ i ].children[ j ].itemPath == m_itemToResize.itemPath &&
146                         j + 1 < m_items[ i ].children.count() )
147                    {
148                        m_items[ i ].children[ j ].size =
149                                m_items[ i ].children[ j ].size + m_itemToResizeNext.size;
150                        m_items[ i ].children[ j ].status = PartitionSplitterItem::Normal;
151                        m_items[ i ].children.removeAt( j + 1 );
152                        m_itemToResizeNext = PartitionSplitterItem::null();
153                        break;
154                    }
155                }
156                if ( m_itemToResizeNext.isNull() )
157                    break;
158            }
159        }
160
161        m_itemToResize = PartitionSplitterItem::null();
162        m_itemToResizePath.clear();
163    }
164
165    PartitionSplitterItem itemToResize = _findItem( m_items,
166        [ path ]( PartitionSplitterItem& item ) -> bool
167    {
168        if ( path == item.itemPath )
169        {
170            item.status = PartitionSplitterItem::Resizing;
171            return true;
172        }
173        return false;
174    } );
175
176    if ( itemToResize.isNull() )
177        return;
178    cDebug() << "itemToResize:" << itemToResize.itemPath;
179
180    m_itemToResize = itemToResize;
181    m_itemToResizePath = path;
182
183    if ( preferredSize > maxSize )
184        preferredSize = maxSize;
185
186    qint64 newSize = m_itemToResize.size - preferredSize;
187    m_itemToResize.size = preferredSize;
188    int opCount = _eachItem( m_items,
189               [ preferredSize ]( PartitionSplitterItem& item ) -> bool
190    {
191        if ( item.status == PartitionSplitterItem::Resizing )
192        {
193            item.size = preferredSize;
194            return true;
195        }
196        return false;
197    } );
198    cDebug() << "each splitter item opcount:" << opCount;
199    m_itemMinSize = minSize;
200    m_itemMaxSize = maxSize;
201    m_itemPrefSize = preferredSize;
202
203    for ( int i = 0; i < m_items.count(); ++i )
204    {
205        if ( m_items[ i ].itemPath == itemToResize.itemPath )
206        {
207            m_items.insert( i+1,
208                            { "",
209                              QColor( "#c0392b" ),
210                              false,
211                              newSize,
212                              PartitionSplitterItem::ResizingNext,
213                              {} } );
214            m_itemToResizeNext = m_items[ i+1 ];
215            break;
216        }
217        else if ( !m_items[ i ].children.isEmpty() )
218        {
219            for ( int j = 0; j < m_items[ i ].children.count(); ++j )
220            {
221                if ( m_items[ i ].children[ j ].itemPath == itemToResize.itemPath )
222                {
223                    m_items[ i ].children.insert( j+1,
224                                                  { "",
225                                                    QColor( "#c0392b" ),
226                                                    false,
227                                                    newSize,
228                                                    PartitionSplitterItem::ResizingNext,
229                                                    {} } );
230                    m_itemToResizeNext = m_items[ i ].children[ j+1 ];
231                    break;
232                }
233            }
234            if ( !m_itemToResizeNext.isNull() )
235                break;
236        }
237    }
238
239    emit partitionResized( m_itemToResize.itemPath,
240                           m_itemToResize.size,
241                           m_itemToResizeNext.size );
242
243    cDebug() << "Items updated. Status:";
244    foreach ( const PartitionSplitterItem& item, m_items )
245        cDebug() << "item" << item.itemPath << "size" << item.size << "status:" << item.status;
246
247    cDebug() << "m_itemToResize:    " << !m_itemToResize.isNull() << m_itemToResize.itemPath;
248    cDebug() << "m_itemToResizeNext:" << !m_itemToResizeNext.isNull() << m_itemToResizeNext.itemPath;
249
250    repaint();
251}
252
253
254qint64
255PartitionSplitterWidget::splitPartitionSize() const
256{
257    if ( !m_itemToResize )
258        return -1;
259    return m_itemToResize.size;
260}
261
262
263qint64
264PartitionSplitterWidget::newPartitionSize() const
265{
266    if ( !m_itemToResizeNext )
267        return -1;
268    return m_itemToResizeNext.size;
269}
270
271
272QSize
273PartitionSplitterWidget::sizeHint() const
274{
275    return QSize( -1, VIEW_HEIGHT );
276}
277
278
279QSize
280PartitionSplitterWidget::minimumSizeHint() const
281{
282    return sizeHint();
283}
284
285
286void
287PartitionSplitterWidget::paintEvent( QPaintEvent* event )
288{
289    Q_UNUSED( event );
290
291    QPainter painter( this );
292    painter.fillRect( rect(), palette().window() );
293    painter.setRenderHint( QPainter::Antialiasing );
294
295    drawPartitions( &painter, rect(), m_items );
296}
297
298
299void
300PartitionSplitterWidget::mousePressEvent( QMouseEvent* event )
301{
302    if ( m_itemToResize &&
303         m_itemToResizeNext &&
304         event->button() == Qt::LeftButton )
305    {
306        if ( qAbs( event->x() - m_resizeHandleX ) < HANDLE_SNAP )
307            m_resizing = true;
308    }
309}
310
311
312void
313PartitionSplitterWidget::mouseMoveEvent( QMouseEvent* event )
314{
315    if ( m_resizing )
316    {
317        qint64 start = 0;
318        QString itemPath = m_itemToResize.itemPath;
319        for ( auto it = m_items.constBegin();
320              it != m_items.constEnd(); ++it )
321        {
322            if ( it->itemPath == itemPath )
323                break;
324            else if ( !it->children.isEmpty() )
325            {
326                bool done = false;
327                for ( auto jt = it->children.constBegin();
328                      jt != it->children.constEnd(); ++jt )
329                {
330                    if ( jt->itemPath == itemPath )
331                    {
332                        done = true;
333                        break;
334                    }
335                    start += jt->size;
336                }
337                if ( done )
338                    break;
339            }
340            else
341                start += it->size;
342        }
343
344        qint64 total = 0;
345        for ( auto it = m_items.constBegin(); it != m_items.constEnd(); ++it )
346        {
347            total += it->size;
348        }
349
350        int ew = rect().width(); //effective width
351        qreal bpp = total / static_cast< qreal >( ew );  //bytes per pixel
352
353        qreal mx = event->x() * bpp - start;
354
355        // make sure we are within resize range
356        mx = qBound( static_cast< qreal >( m_itemMinSize ),
357                     mx,
358                     static_cast< qreal >( m_itemMaxSize ) );
359
360        qint64 span = m_itemPrefSize;
361        qreal percent = mx / span;
362        qint64 oldsize = m_itemToResize.size;
363
364        m_itemToResize.size = qRound64( span * percent );
365        m_itemToResizeNext.size -= m_itemToResize.size - oldsize;
366        _eachItem( m_items,
367                   [ this ]( PartitionSplitterItem& item ) -> bool
368        {
369            if ( item.status == PartitionSplitterItem::Resizing )
370            {
371                item.size = m_itemToResize.size;
372                return true;
373            }
374            else if ( item.status == PartitionSplitterItem::ResizingNext )
375            {
376                item.size = m_itemToResizeNext.size;
377                return true;
378            }
379            return false;
380        } );
381
382        repaint();
383
384        emit partitionResized( itemPath,
385                               m_itemToResize.size,
386                               m_itemToResizeNext.size );
387    }
388    else
389    {
390        if ( m_itemToResize && m_itemToResizeNext )
391        {
392            if ( qAbs( event->x() - m_resizeHandleX ) < HANDLE_SNAP )
393                setCursor( Qt::SplitHCursor );
394            else if ( cursor().shape() != Qt::ArrowCursor )
395                setCursor( Qt::ArrowCursor );
396        }
397    }
398}
399
400
401void
402PartitionSplitterWidget::mouseReleaseEvent( QMouseEvent* event )
403{
404    Q_UNUSED( event );
405
406    m_resizing = false;
407}
408
409
410void
411PartitionSplitterWidget::drawSection( QPainter* painter, const QRect& rect_, int x, int width,
412                                      const PartitionSplitterItem& item )
413{
414    QColor color = item.color;
415    bool isFreeSpace = item.isFreeSpace;
416
417    QRect rect = rect_;
418    const int y = rect.y();
419    const int rectHeight = rect.height();
420    const int radius = qMax( 1, CORNER_RADIUS - ( height() - rectHeight ) / 2 );
421    painter->setClipRect( x, y, width, rectHeight );
422    painter->translate( 0.5, 0.5 );
423
424    rect.adjust( 0, 0, -1, -1 );
425    const QColor borderColor = color.darker();
426    painter->setPen( borderColor );
427    painter->setBrush( color );
428    painter->drawRoundedRect( rect, radius, radius );
429
430    // Draw shade
431    if ( !isFreeSpace )
432        rect.adjust( 2, 2, -2, -2 );
433
434    QLinearGradient gradient( 0, 0, 0, rectHeight / 2 );
435
436    qreal c = isFreeSpace ? 0 : 1;
437    gradient.setColorAt( 0, QColor::fromRgbF( c, c, c, 0.3 ) );
438    gradient.setColorAt( 1, QColor::fromRgbF( c, c, c, 0 ) );
439
440    painter->setPen( Qt::NoPen );
441    painter->setBrush( gradient );
442    painter->drawRoundedRect( rect, radius, radius );
443
444    painter->translate( -0.5, -0.5 );
445}
446
447void
448PartitionSplitterWidget::drawResizeHandle( QPainter* painter,
449                                           const QRect& rect_,
450                                           int x )
451{
452    if ( !m_itemToResize )
453        return;
454
455    painter->setPen( Qt::NoPen );
456    painter->setBrush( Qt::black );
457    painter->setClipRect( rect_ );
458
459    painter->setRenderHint( QPainter::Antialiasing, true );
460
461    qreal h = VIEW_HEIGHT; // Put the arrow in the center regardless of inner box height
462    int scaleFactor = qRound( height() / static_cast< qreal >( VIEW_HEIGHT ) );
463    QList< QPair< qreal, qreal > > arrow_offsets = {
464        qMakePair( 0, h / 2 - 1 ),
465        qMakePair( 4, h / 2 - 1 ),
466        qMakePair( 4, h / 2 - 3 ),
467        qMakePair( 8, h / 2 ),
468        qMakePair( 4, h / 2 + 3 ),
469        qMakePair( 4, h / 2 + 1 ),
470        qMakePair( 0, h / 2 + 1 )
471    };
472    for ( int i = 0; i < arrow_offsets.count(); ++i )
473    {
474        arrow_offsets[ i ] = qMakePair( arrow_offsets[ i ].first * scaleFactor,
475                                        ( arrow_offsets[ i ].second - h/2 ) * scaleFactor + h/2 );
476    }
477
478    auto p1 = arrow_offsets[ 0 ];
479    if ( m_itemToResize.size > m_itemMinSize )
480    {
481        auto arrow = QPainterPath( QPointF( x + -1 * p1.first, p1.second ) );
482        for ( auto p : arrow_offsets )
483            arrow.lineTo( x + -1 * p.first + 1, p.second );
484        painter->drawPath( arrow );
485    }
486
487    if ( m_itemToResize.size < m_itemMaxSize )
488    {
489        auto arrow = QPainterPath( QPointF( x + p1.first, p1.second ) );
490        for ( auto p : arrow_offsets )
491            arrow.lineTo( x + p.first, p.second );
492        painter->drawPath( arrow );
493    }
494
495    painter->setRenderHint( QPainter::Antialiasing, false );
496    painter->setPen( Qt::black );
497    painter->drawLine( x, 0, x, int(h) - 1 );
498}
499
500
501void
502PartitionSplitterWidget::drawPartitions( QPainter* painter,
503                                         const QRect& rect,
504                                         const QVector< PartitionSplitterItem >& itemList )
505{
506    const int count = itemList.count();
507    const int totalWidth = rect.width();
508
509    auto pair = computeItemsVector( itemList );
510    QVector< PartitionSplitterItem >& items = pair.first;
511    qreal total = pair.second;
512
513    int x = rect.x();
514    for ( int row = 0; row < count; ++row )
515    {
516        const PartitionSplitterItem& item = items[ row ];
517        qreal width;
518        if ( row < count - 1 )
519            width = totalWidth * ( item.size / total );
520        else
521            // Make sure we fill the last pixel column
522            width = rect.right() - x + 1;
523
524        drawSection( painter, rect, x, int(width), item );
525        if ( !item.children.isEmpty() )
526        {
527            QRect subRect(
528                x + EXTENDED_PARTITION_MARGIN,
529                rect.y() + EXTENDED_PARTITION_MARGIN,
530                int(width) - 2 * EXTENDED_PARTITION_MARGIN,
531                rect.height() - 2 * EXTENDED_PARTITION_MARGIN
532            );
533            drawPartitions( painter, subRect, item.children );
534        }
535
536        // If an item to resize and the following new item both exist,
537        // and this is not the very first partition,
538        // and the partition preceding this one is the item to resize...
539        if ( m_itemToResize &&
540             m_itemToResizeNext &&
541             row > 0 &&
542             !items[ row - 1 ].isFreeSpace &&
543             !items[ row - 1 ].itemPath.isEmpty() &&
544             items[ row - 1 ].itemPath == m_itemToResize.itemPath )
545        {
546            m_resizeHandleX = x;
547            drawResizeHandle( painter, rect, m_resizeHandleX );
548        }
549
550        x += width;
551    }
552}
553
554
555PartitionSplitterItem
556PartitionSplitterWidget::_findItem( QVector< PartitionSplitterItem >& items,
557                                    std::function< bool ( PartitionSplitterItem& ) > condition ) const
558{
559    for ( auto it = items.begin(); it != items.end(); ++it)
560    {
561        if ( condition( *it ) )
562            return *it;
563
564        PartitionSplitterItem candidate = _findItem( it->children, condition );
565        if ( !candidate.isNull() )
566            return candidate;
567    }
568    return PartitionSplitterItem::null();
569}
570
571
572int
573PartitionSplitterWidget::_eachItem( QVector< PartitionSplitterItem >& items,
574                                    std::function< bool ( PartitionSplitterItem& ) > operation ) const
575{
576    int opCount = 0;
577    for ( auto it = items.begin(); it != items.end(); ++it)
578    {
579        if ( operation( *it ) )
580            opCount++;
581
582        opCount += _eachItem( it->children, operation );
583    }
584    return opCount;
585}
586
587
588QPair< QVector< PartitionSplitterItem >, qreal >
589PartitionSplitterWidget::computeItemsVector( const QVector< PartitionSplitterItem >& originalItems ) const
590{
591    QVector< PartitionSplitterItem > items;
592
593    qreal total = 0;
594    for ( int row = 0; row < originalItems.count(); ++row )
595    {
596        if ( originalItems[ row ].children.isEmpty() )
597        {
598            items += originalItems[ row ];
599            total += originalItems[ row ].size;
600        }
601        else
602        {
603            PartitionSplitterItem thisItem = originalItems[ row ];
604            QPair< QVector< PartitionSplitterItem >, qreal > pair = computeItemsVector( thisItem.children );
605            thisItem.children = pair.first;
606            thisItem.size = qint64(pair.second);
607            items += thisItem;
608            total += thisItem.size;
609        }
610    }
611
612    // The sizes we have are perfect, but now we have to hardcode a minimum size for small
613    // partitions and compensate for it in the total.
614    qreal adjustedTotal = total;
615    for ( int row = 0; row < items.count(); ++row )
616    {
617        if ( items[ row ].size < 0.01 * total ) // If this item is smaller than 1% of everything,
618        {                                       // force its width to 1%.
619            adjustedTotal -= items[ row ].size;
620            items[ row ].size = qint64(0.01 * total);
621            adjustedTotal += items[ row ].size;
622        }
623    }
624
625    return qMakePair( items, adjustedTotal );
626}
Note: See TracBrowser for help on using the repository browser.