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

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

sync with github

File size: 16.7 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014, Aurélien Gâteau <agateau@kde.org>
4 *   Copyright 2015-2016, Teo Mrnjavac <teo@kde.org>
5 *
6 *   Calamares is free software: you can redistribute it and/or modify
7 *   it under the terms of the GNU General Public License as published by
8 *   the Free Software Foundation, either version 3 of the License, or
9 *   (at your option) any later version.
10 *
11 *   Calamares is distributed in the hope that it will be useful,
12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 *   GNU General Public License for more details.
15 *
16 *   You should have received a copy of the GNU General Public License
17 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
18 */
19#include "gui/PartitionBarsView.h"
20
21#include <core/PartitionModel.h>
22#include <core/ColorUtils.h>
23
24#include <kpmcore/core/device.h>
25
26#include <utils/CalamaresUtilsGui.h>
27#include <utils/Logger.h>
28
29
30// Qt
31#include <QDebug>
32#include <QGuiApplication>
33#include <QMouseEvent>
34#include <QPainter>
35
36
37static const int VIEW_HEIGHT = qMax( CalamaresUtils::defaultFontHeight() + 8, // wins out with big fonts
38                                     int( CalamaresUtils::defaultFontHeight() * 0.6 ) + 22 ); // wins out with small fonts
39static constexpr int CORNER_RADIUS = 3;
40static const int EXTENDED_PARTITION_MARGIN = qMax( 4, VIEW_HEIGHT / 6 );
41
42// The SELECTION_MARGIN is applied within a hardcoded 2px padding anyway, so
43// we start from EXTENDED_PARTITION_MARGIN - 2 in all cases.
44// Then we try to ensure the selection rectangle fits exactly between the extended
45// rectangle and the outer frame (the "/ 2" part), unless that's not possible, and in
46// that case we at least make sure we have a 1px gap between the selection rectangle
47// and the extended partition box (the "- 2" part).
48// At worst, on low DPI systems, this will mean in order:
49// 1px outer rect, 1 px gap, 1px selection rect, 1px gap, 1px extended partition rect.
50static const int SELECTION_MARGIN = qMin( ( EXTENDED_PARTITION_MARGIN - 2 ) / 2,
51                                          ( EXTENDED_PARTITION_MARGIN - 2 ) - 2 );
52
53
54PartitionBarsView::PartitionBarsView( QWidget* parent )
55    : QAbstractItemView( parent )
56    , m_nestedPartitionsMode( NoNestedPartitions )
57    , canBeSelected( []( const QModelIndex& ) { return true; } )
58    , m_hoveredIndex( QModelIndex() )
59{
60    this->setObjectName("partitionBarView");
61    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
62    setFrameStyle( QFrame::NoFrame );
63    setSelectionBehavior( QAbstractItemView::SelectRows );
64    setSelectionMode( QAbstractItemView::SingleSelection );
65
66    // Debug
67    connect( this, &PartitionBarsView::clicked,
68             this, [=]( const QModelIndex& index )
69    {
70        cDebug() << "Clicked row" << index.row();
71    } );
72    setMouseTracking( true );
73}
74
75
76PartitionBarsView::~PartitionBarsView()
77{
78}
79
80
81void
82PartitionBarsView::setNestedPartitionsMode( PartitionBarsView::NestedPartitionsMode mode )
83{
84    m_nestedPartitionsMode = mode;
85    viewport()->repaint();
86}
87
88
89QSize
90PartitionBarsView::minimumSizeHint() const
91{
92    return sizeHint();
93}
94
95
96QSize
97PartitionBarsView::sizeHint() const
98{
99    return QSize( -1, VIEW_HEIGHT );
100}
101
102
103void
104PartitionBarsView::paintEvent( QPaintEvent* event )
105{
106    QPainter painter( viewport() );
107    painter.fillRect( rect(), palette().window() );
108    painter.setRenderHint( QPainter::Antialiasing );
109
110    QRect partitionsRect = rect();
111    partitionsRect.setHeight( VIEW_HEIGHT );
112
113    painter.save();
114    drawPartitions( &painter, partitionsRect, QModelIndex() );
115    painter.restore();
116}
117
118
119void
120PartitionBarsView::drawSection( QPainter* painter, const QRect& rect_, int x, int width, const QModelIndex& index )
121{
122    QColor color = index.isValid() ?
123                   index.data( Qt::DecorationRole ).value< QColor >() :
124                   ColorUtils::unknownDisklabelColor();
125    bool isFreeSpace = index.isValid() ?
126                       index.data( PartitionModel::IsFreeSpaceRole ).toBool() :
127                       true;
128
129    QRect rect = rect_;
130    const int y = rect.y();
131    const int height = rect.height();
132    const int radius = qMax( 1, CORNER_RADIUS - ( VIEW_HEIGHT - height ) / 2 );
133    painter->setClipRect( x, y, width, height );
134    painter->translate( 0.5, 0.5 );
135
136    rect.adjust( 0, 0, -1, -1 );
137
138
139    if ( selectionMode() != QAbstractItemView::NoSelection && // no hover without selection
140         m_hoveredIndex.isValid() &&
141         index == m_hoveredIndex )
142    {
143        if ( canBeSelected( index ) )
144            painter->setBrush( color.lighter( 115 ) );
145        else
146            painter->setBrush( color );
147    }
148    else
149    {
150        painter->setBrush( color );
151    }
152
153    QColor borderColor = color.darker();
154
155    painter->setPen( borderColor );
156
157    painter->drawRoundedRect( rect, radius, radius );
158
159    // Draw shade
160    if ( !isFreeSpace )
161        rect.adjust( 2, 2, -2, -2 );
162
163    QLinearGradient gradient( 0, 0, 0, height / 2 );
164
165    qreal c = isFreeSpace ? 0 : 1;
166    gradient.setColorAt( 0, QColor::fromRgbF( c, c, c, 0.3 ) );
167    gradient.setColorAt( 1, QColor::fromRgbF( c, c, c, 0 ) );
168
169    painter->setPen( Qt::NoPen );
170
171    painter->setBrush( gradient );
172    painter->drawRoundedRect( rect, radius, radius );
173
174    if ( selectionMode() != QAbstractItemView::NoSelection &&
175         index.isValid() &&
176         selectionModel() &&
177         !selectionModel()->selectedIndexes().isEmpty() &&
178         selectionModel()->selectedIndexes().first() == index )
179    {
180        painter->setPen( QPen( borderColor, 1 ) );
181        QColor highlightColor = QPalette().highlight().color();
182        highlightColor = highlightColor.lighter( 500 );
183        highlightColor.setAlpha( 120 );
184        painter->setBrush( highlightColor );
185
186        QRect selectionRect = rect;
187        selectionRect.setX( x + 1 );
188        selectionRect.setWidth( width - 3 ); //account for the previous rect.adjust
189
190        if ( rect.x() > selectionRect.x() ) //hack for first item
191            selectionRect.adjust( rect.x() - selectionRect.x(), 0, 0, 0 );
192
193        if ( rect.right() < selectionRect.right() ) //hack for last item
194            selectionRect.adjust( 0, 0, - ( selectionRect.right() - rect.right() ), 0 );
195
196        selectionRect.adjust( SELECTION_MARGIN,
197                              SELECTION_MARGIN,
198                              -SELECTION_MARGIN,
199                              -SELECTION_MARGIN );
200
201        painter->drawRoundedRect( selectionRect,
202                                  radius - 1,
203                                  radius - 1 );
204    }
205
206    painter->translate( -0.5, -0.5 );
207}
208
209
210void
211PartitionBarsView::drawPartitions( QPainter* painter, const QRect& rect, const QModelIndex& parent )
212{
213    PartitionModel* modl = qobject_cast< PartitionModel* >( model() );
214    if ( !modl )
215        return;
216    const int totalWidth = rect.width();
217
218    auto pair = computeItemsVector( parent );
219    QVector< PartitionBarsView::Item >& items = pair.first;
220    qreal& total = pair.second;
221    int x = rect.x();
222    for ( int row = 0; row < items.count(); ++row )
223    {
224        const auto& item = items[ row ];
225        int width;
226        if ( row < items.count() - 1 )
227            width = totalWidth * ( item.size / total );
228        else
229            // Make sure we fill the last pixel column
230            width = rect.right() - x + 1;
231
232        drawSection( painter, rect, x, width, item.index );
233
234        if ( m_nestedPartitionsMode == DrawNestedPartitions &&
235             modl->hasChildren( item.index ) )
236        {
237            QRect subRect(
238                x + EXTENDED_PARTITION_MARGIN,
239                rect.y() + EXTENDED_PARTITION_MARGIN,
240                width - 2 * EXTENDED_PARTITION_MARGIN,
241                rect.height() - 2 * EXTENDED_PARTITION_MARGIN
242            );
243            drawPartitions( painter, subRect, item.index );
244        }
245        x += width;
246    }
247
248    if ( !items.count() &&
249         !modl->device()->partitionTable() ) // No disklabel or unknown
250    {
251        int width = rect.right() - rect.x() + 1;
252        drawSection( painter, rect, rect.x(), width, QModelIndex() );
253    }
254}
255
256
257QModelIndex
258PartitionBarsView::indexAt( const QPoint& point ) const
259{
260    return indexAt( point, rect(), QModelIndex() );
261}
262
263
264QModelIndex
265PartitionBarsView::indexAt( const QPoint &point,
266                            const QRect &rect,
267                            const QModelIndex& parent ) const
268{
269    PartitionModel* modl = qobject_cast< PartitionModel* >( model() );
270    if ( !modl )
271        return QModelIndex();
272    const int totalWidth = rect.width();
273
274    auto pair = computeItemsVector( parent );
275    QVector< PartitionBarsView::Item >& items = pair.first;
276    qreal& total = pair.second;
277    int x = rect.x();
278    for ( int row = 0; row < items.count(); ++row )
279    {
280        const auto& item = items[ row ];
281        int width;
282        if ( row < items.count() - 1 )
283            width = totalWidth * ( item.size / total );
284        else
285            // Make sure we fill the last pixel column
286            width = rect.right() - x + 1;
287
288        QRect thisItemRect( x, rect.y(), width, rect.height() );
289        if ( thisItemRect.contains( point ) )
290        {
291            if ( m_nestedPartitionsMode == DrawNestedPartitions &&
292                 modl->hasChildren( item.index ) )
293            {
294                QRect subRect(
295                    x + EXTENDED_PARTITION_MARGIN,
296                    rect.y() + EXTENDED_PARTITION_MARGIN,
297                    width - 2 * EXTENDED_PARTITION_MARGIN,
298                    rect.height() - 2 * EXTENDED_PARTITION_MARGIN
299                );
300
301                if ( subRect.contains( point ) )
302                {
303                    return indexAt( point, subRect, item.index );
304                }
305                return item.index;
306            }
307            else // contains but no children, we win
308            {
309                return item.index;
310            }
311        }
312        x += width;
313    }
314
315    return QModelIndex();
316}
317
318
319QRect
320PartitionBarsView::visualRect( const QModelIndex& index ) const
321{
322    return visualRect( index, rect(), QModelIndex() );
323}
324
325
326QRect
327PartitionBarsView::visualRect( const QModelIndex& index,
328                               const QRect& rect,
329                               const QModelIndex& parent ) const
330{
331    PartitionModel* modl = qobject_cast< PartitionModel* >( model() );
332    if ( !modl )
333        return QRect();
334    const int totalWidth = rect.width();
335
336    auto pair = computeItemsVector( parent );
337    QVector< PartitionBarsView::Item >& items = pair.first;
338    qreal& total = pair.second;
339    int x = rect.x();
340    for ( int row = 0; row < items.count(); ++row )
341    {
342        const auto& item = items[ row ];
343        int width;
344        if ( row < items.count() - 1 )
345            width = totalWidth * ( item.size / total );
346        else
347            // Make sure we fill the last pixel column
348            width = rect.right() - x + 1;
349
350        QRect thisItemRect( x, rect.y(), width, rect.height() );
351        if ( item.index == index )
352            return thisItemRect;
353
354        if ( m_nestedPartitionsMode == DrawNestedPartitions &&
355             modl->hasChildren( item.index ) &&
356             index.parent() == item.index )
357        {
358            QRect subRect(
359                x + EXTENDED_PARTITION_MARGIN,
360                rect.y() + EXTENDED_PARTITION_MARGIN,
361                width - 2 * EXTENDED_PARTITION_MARGIN,
362                rect.height() - 2 * EXTENDED_PARTITION_MARGIN
363            );
364
365            QRect candidateVisualRect = visualRect( index, subRect, item.index );
366            if ( !candidateVisualRect.isNull() )
367                return candidateVisualRect;
368        }
369
370        x += width;
371    }
372
373    return QRect();
374}
375
376
377QRegion
378PartitionBarsView::visualRegionForSelection( const QItemSelection& selection ) const
379{
380    return QRegion();
381}
382
383
384int
385PartitionBarsView::horizontalOffset() const
386{
387    return 0;
388}
389
390
391int
392PartitionBarsView::verticalOffset() const
393{
394    return 0;
395}
396
397
398void
399PartitionBarsView::scrollTo( const QModelIndex& index, ScrollHint hint )
400{
401    Q_UNUSED( index )
402    Q_UNUSED( hint )
403}
404
405
406void
407PartitionBarsView::setSelectionModel( QItemSelectionModel* selectionModel )
408{
409    QAbstractItemView::setSelectionModel( selectionModel );
410    connect( selectionModel, &QItemSelectionModel::selectionChanged,
411             this, [=]
412    {
413        viewport()->repaint();
414    } );
415}
416
417
418void
419PartitionBarsView::setSelectionFilter( std::function< bool ( const QModelIndex& ) > canBeSelected )
420{
421    this->canBeSelected = canBeSelected;
422}
423
424
425QModelIndex
426PartitionBarsView::moveCursor( CursorAction, Qt::KeyboardModifiers )
427{
428    return QModelIndex();
429}
430
431
432bool
433PartitionBarsView::isIndexHidden( const QModelIndex& ) const
434{
435    return false;
436}
437
438
439void
440PartitionBarsView::setSelection( const QRect& rect, QItemSelectionModel::SelectionFlags flags )
441{
442    //HACK: this is an utterly awful workaround, which is unfortunately necessary.
443    //      QAbstractItemView::mousePressedEvent calls setSelection, but before that,
444    //      for some mental reason, it works under the assumption that every item is a
445    //      rectangle. This rectangle is provided by visualRect, and the idea mostly
446    //      works, except when the item is an extended partition item, which is of course
447    //      a rectangle with a rectangular hole in the middle.
448    //      QAbstractItemView::mousePressEvent builds a QRect with x1, y1 in the center
449    //      of said visualRect, and x2, y2 in the real QMouseEvent position.
450    //      This may very well yield a QRect with negative size, which is meaningless.
451    //      Therefore the QRect we get here is totally bogus, and its topLeft is outside
452    //      the actual area of the item we need.
453    //      What we need are the real coordinates of the QMouseEvent, and the only way to
454    //      get them is by fetching the private x2, y2 from the rect.
455    //      TL;DR: this sucks, look away. -- Teo 12/2015
456    int x1, y1, x2, y2;
457    rect.getCoords( &x1, &y1, &x2, &y2 );
458
459    QModelIndex eventIndex = indexAt( QPoint( x2, y2 ) );
460    if ( canBeSelected( eventIndex ) )
461        selectionModel()->select( eventIndex, flags );
462
463    viewport()->repaint();
464}
465
466
467void
468PartitionBarsView::mouseMoveEvent( QMouseEvent* event )
469{
470    QModelIndex candidateIndex = indexAt( event->pos() );
471    QPersistentModelIndex oldHoveredIndex = m_hoveredIndex;
472    if ( candidateIndex.isValid() )
473    {
474        m_hoveredIndex = candidateIndex;
475    }
476    else
477    {
478        m_hoveredIndex = QModelIndex();
479        QGuiApplication::restoreOverrideCursor();
480    }
481
482    if ( oldHoveredIndex != m_hoveredIndex )
483    {
484        if ( m_hoveredIndex.isValid() && !canBeSelected( m_hoveredIndex ) )
485            QGuiApplication::setOverrideCursor( Qt::ForbiddenCursor );
486        else
487            QGuiApplication::restoreOverrideCursor();
488
489        viewport()->repaint();
490    }
491}
492
493
494void
495PartitionBarsView::leaveEvent( QEvent* )
496{
497    QGuiApplication::restoreOverrideCursor();
498    if ( m_hoveredIndex.isValid() )
499    {
500        m_hoveredIndex = QModelIndex();
501        viewport()->repaint();
502    }
503}
504
505
506void
507PartitionBarsView::mousePressEvent( QMouseEvent* event )
508{
509    QModelIndex candidateIndex = indexAt( event->pos() );
510    if ( canBeSelected( candidateIndex ) )
511        QAbstractItemView::mousePressEvent( event );
512    else
513        event->accept();
514}
515
516
517void
518PartitionBarsView::updateGeometries()
519{
520    updateGeometry(); //get a new rect() for redrawing all the labels
521}
522
523
524QPair< QVector< PartitionBarsView::Item >, qreal >
525PartitionBarsView::computeItemsVector( const QModelIndex& parent ) const
526{
527    int count = model()->rowCount( parent );
528    QVector< PartitionBarsView::Item > items;
529
530    qreal total = 0;
531    for ( int row = 0; row < count; ++row )
532    {
533        QModelIndex index = model()->index( row, 0, parent );
534        if ( m_nestedPartitionsMode == NoNestedPartitions &&
535             model()->hasChildren( index ) )
536        {
537            QPair< QVector< PartitionBarsView::Item >, qreal > childVect =
538                    computeItemsVector( index );
539            items += childVect.first;
540            total += childVect.second;
541        }
542        else
543        {
544            qreal size = index.data( PartitionModel::SizeRole ).toLongLong();
545            total += size;
546            items.append( { size, index } );
547        }
548    }
549
550    count = items.count();
551
552    // The sizes we have are perfect, but now we have to hardcode a minimum size for small
553    // partitions and compensate for it in the total.
554    qreal adjustedTotal = total;
555    for ( int row = 0; row < count; ++row )
556    {
557        if ( items[ row ].size < 0.01 * total ) // If this item is smaller than 1% of everything,
558        {                                       // force its width to 1%.
559            adjustedTotal -= items[ row ].size;
560            items[ row ].size = 0.01 * total;
561            adjustedTotal += items[ row ].size;
562        }
563    }
564
565    return qMakePair( items, adjustedTotal );
566}
567
Note: See TracBrowser for help on using the repository browser.