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

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

sync with github

File size: 17.3 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
20#include "PartitionLabelsView.h"
21
22#include <core/PartitionModel.h>
23#include <core/ColorUtils.h>
24
25#include <utils/CalamaresUtilsGui.h>
26#include <utils/Logger.h>
27
28#include <kpmcore/core/device.h>
29#include <kpmcore/fs/filesystem.h>
30
31#include <KFormat>
32
33// Qt
34#include <QGuiApplication>
35#include <QMouseEvent>
36#include <QPainter>
37
38
39static const int LAYOUT_MARGIN = 4;
40static const int LABEL_PARTITION_SQUARE_MARGIN =
41        qMax( QFontMetrics( CalamaresUtils::defaultFont() ).ascent() - 2, 18 );
42static const int LABELS_MARGIN = LABEL_PARTITION_SQUARE_MARGIN;
43static const int CORNER_RADIUS = 2;
44
45
46static QStringList
47buildUnknownDisklabelTexts( Device* dev )
48{
49    QStringList texts = { QObject::tr( "Unpartitioned space or unknown partition table" ),
50                          KFormat().formatByteSize( dev->totalLogical() * dev->logicalSize() ) };
51    return texts;
52}
53
54
55PartitionLabelsView::PartitionLabelsView( QWidget* parent )
56    : QAbstractItemView( parent )
57    , m_canBeSelected( []( const QModelIndex& ) { return true; } )
58    , m_extendedPartitionHidden( false )
59{
60    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
61    setFrameStyle( QFrame::NoFrame );
62    setSelectionBehavior( QAbstractItemView::SelectRows );
63    setSelectionMode( QAbstractItemView::SingleSelection );
64    this->setObjectName("partitionLabel");
65    // Debug
66    connect( this, &PartitionLabelsView::clicked,
67             this, [=]( const QModelIndex& index )
68    {
69        cDebug() << "Clicked row" << index.row();
70    } );
71    setMouseTracking( true );
72}
73
74
75PartitionLabelsView::~PartitionLabelsView()
76{
77}
78
79
80QSize
81PartitionLabelsView::minimumSizeHint() const
82{
83    return sizeHint();
84}
85
86
87
88QSize
89PartitionLabelsView::sizeHint() const
90{
91    QAbstractItemModel* modl = model();
92    if ( modl )
93    {
94        return QSize( -1, LAYOUT_MARGIN + sizeForAllLabels( rect().width() ).height() );
95    }
96    return QSize();
97}
98
99
100void
101PartitionLabelsView::paintEvent( QPaintEvent* event )
102{
103    Q_UNUSED( event );
104
105    QPainter painter( viewport() );
106    painter.fillRect( rect(), palette().window() );
107    painter.setRenderHint( QPainter::Antialiasing );
108
109    QRect lRect = labelsRect();
110
111    drawLabels( &painter, lRect, QModelIndex() );
112}
113
114
115QRect
116PartitionLabelsView::labelsRect() const
117{
118    return rect().adjusted( 0, LAYOUT_MARGIN, 0, 0 );
119}
120
121
122static void
123drawPartitionSquare( QPainter* painter, const QRect& rect, const QBrush& brush )
124{
125    painter->fillRect( rect.adjusted( 1, 1, -1, -1 ), brush );
126    painter->setRenderHint( QPainter::Antialiasing, true );
127    painter->setPen( QPalette().shadow().color() );
128    painter->translate( .5, .5 );
129    painter->drawRoundedRect( rect.adjusted( 0, 0, -1, -1 ), CORNER_RADIUS, CORNER_RADIUS );
130    painter->translate( -.5, -.5 );
131}
132
133
134static void
135drawSelectionSquare( QPainter* painter, const QRect& rect, const QBrush& brush )
136{
137    painter->save();
138    painter->setPen( QPen( brush.color().darker(), 1 ) );
139    QColor highlightColor = QPalette().highlight().color();
140    highlightColor = highlightColor.lighter( 500 );
141    highlightColor.setAlpha( 120 );
142    painter->setBrush( highlightColor );
143    painter->translate( .5, .5 );
144    painter->drawRoundedRect( rect.adjusted( 0, 0, -1, -1 ), CORNER_RADIUS, CORNER_RADIUS );
145    painter->translate( -.5, -.5 );
146    painter->restore();
147}
148
149
150QModelIndexList
151PartitionLabelsView::getIndexesToDraw( const QModelIndex& parent ) const
152{
153    QModelIndexList list;
154
155    QAbstractItemModel* modl = model();
156    if ( !modl )
157        return list;
158
159    for ( int row = 0; row < modl->rowCount( parent ); ++row )
160    {
161        QModelIndex index = modl->index( row, 0, parent );
162
163        //HACK: horrible special casing follows.
164        //      To save vertical space, we choose to hide short instances of free space.
165        //      Arbitrary limit: 10MB.
166        const qint64 maxHiddenB = 10000000;
167        if ( index.data( PartitionModel::IsFreeSpaceRole ).toBool() &&
168             index.data( PartitionModel::SizeRole ).toLongLong() <  maxHiddenB )
169            continue;
170
171        if ( !modl->hasChildren( index ) || !m_extendedPartitionHidden )
172            list.append( index );
173
174        if ( modl->hasChildren( index ) )
175            list.append( getIndexesToDraw( index ) );
176    }
177    return list;
178}
179
180
181QStringList
182PartitionLabelsView::buildTexts( const QModelIndex& index ) const
183{
184    QString firstLine, secondLine;
185
186    if ( index.data( PartitionModel::IsPartitionNewRole ).toBool() )
187    {
188        QString mountPoint = index.sibling( index.row(),
189                                            PartitionModel::MountPointColumn )
190                                  .data().toString();
191        if ( mountPoint == "/" )
192            firstLine = m_customNewRootLabel.isEmpty() ?
193                            tr( "Root" ) :
194                            m_customNewRootLabel;
195        else if ( mountPoint == "/home" )
196            firstLine = tr( "Home" );
197        else if ( mountPoint == "/boot" )
198            firstLine = tr( "Boot" );
199        else if ( mountPoint.contains( "/efi" ) &&
200                  index.data( PartitionModel::FileSystemTypeRole ).toInt() == FileSystem::Fat32 )
201            firstLine = tr( "EFI system" );
202        else if ( index.data( PartitionModel::FileSystemTypeRole ).toInt() == FileSystem::LinuxSwap )
203            firstLine = tr( "Swap" );
204        else if ( !mountPoint.isEmpty() )
205            firstLine = tr( "New partition for %1" ).arg( mountPoint );
206        else
207            firstLine = tr( "New partition" );
208    }
209    else if ( index.data( PartitionModel::OsproberNameRole ).toString().isEmpty() )
210    {
211        firstLine = index.data().toString();
212        if ( firstLine.startsWith( "/dev/" ) )
213            firstLine.remove( 0, 5 );   // "/dev/"
214    }
215    else
216        firstLine = index.data( PartitionModel::OsproberNameRole ).toString();
217
218    if ( index.data( PartitionModel::IsFreeSpaceRole ).toBool() ||
219         index.data( PartitionModel::FileSystemTypeRole ).toInt() == FileSystem::Extended )
220        secondLine = index.sibling( index.row(),
221                                    PartitionModel::SizeColumn )
222                          .data().toString();
223    else
224        secondLine = tr( "%1  %2" )
225                     .arg( index.sibling( index.row(),
226                                          PartitionModel::SizeColumn )
227                                .data().toString() )
228                     .arg( index.sibling( index.row(),
229                                          PartitionModel::FileSystemColumn )
230                                .data().toString() );
231
232    return { firstLine, secondLine };
233}
234
235
236void
237PartitionLabelsView::drawLabels( QPainter* painter,
238                                 const QRect& rect,
239                                 const QModelIndex& parent )
240{
241    PartitionModel* modl = qobject_cast< PartitionModel* >( model() );
242    if ( !modl )
243        return;
244
245    const QModelIndexList indexesToDraw = getIndexesToDraw( parent );
246
247    int label_x = rect.x();
248    int label_y = rect.y();
249    for ( const QModelIndex& index : indexesToDraw )
250    {
251        QStringList texts = buildTexts( index );
252
253        QSize labelSize = sizeForLabel( texts );
254
255        QColor labelColor = index.data( Qt::DecorationRole ).value< QColor >();
256
257        if ( label_x + labelSize.width() > rect.width() ) //wrap to new line if overflow
258        {
259            label_x = rect.x();
260            label_y += labelSize.height() + labelSize.height() / 4;
261        }
262
263        // Draw hover
264        if ( selectionMode() != QAbstractItemView::NoSelection && // no hover without selection
265             m_hoveredIndex.isValid() &&
266             index == m_hoveredIndex )
267        {
268            painter->save();
269            QRect labelRect( QPoint( label_x, label_y ), labelSize );
270            labelRect.adjust( 0, -LAYOUT_MARGIN, 0, -2*LAYOUT_MARGIN );
271            painter->translate( 0.5, 0.5 );
272            QRect hoverRect = labelRect.adjusted( 0, 0, -1, -1 );
273            painter->setBrush( QPalette().background().color().lighter( 102 ) );
274            painter->setPen( Qt::NoPen );
275            painter->drawRoundedRect( hoverRect, CORNER_RADIUS, CORNER_RADIUS );
276
277            painter->translate( -0.5, -0.5 );
278            painter->restore();
279        }
280
281        // Is this element the selected one?
282        bool sel = selectionMode() != QAbstractItemView::NoSelection &&
283                   index.isValid() &&
284                   selectionModel() &&
285                   !selectionModel()->selectedIndexes().isEmpty() &&
286                   selectionModel()->selectedIndexes().first() == index;
287
288        drawLabel( painter, texts, labelColor, QPoint( label_x, label_y ), sel );
289
290        label_x += labelSize.width() + LABELS_MARGIN;
291    }
292
293    if ( !modl->rowCount() &&
294         !modl->device()->partitionTable() ) // No disklabel or unknown
295    {
296        QStringList texts = buildUnknownDisklabelTexts( modl->device() );
297        QColor labelColor = ColorUtils::unknownDisklabelColor();
298        drawLabel( painter, texts, labelColor, QPoint( rect.x(), rect.y() ), false /*can't be selected*/ );
299    }
300}
301
302
303QSize
304PartitionLabelsView::sizeForAllLabels( int maxLineWidth ) const
305{
306    PartitionModel* modl = qobject_cast< PartitionModel* >( model() );
307    if ( !modl )
308        return QSize();
309
310    const QModelIndexList indexesToDraw = getIndexesToDraw( QModelIndex() );
311
312    int lineLength = 0;
313    int numLines = 1;
314    int singleLabelHeight = 0;
315    for ( const QModelIndex& index : indexesToDraw )
316    {
317        QStringList texts = buildTexts( index );
318
319        QSize labelSize = sizeForLabel( texts );
320
321        if ( lineLength + labelSize.width() > maxLineWidth )
322        {
323            numLines++;
324            lineLength = labelSize.width();
325        }
326        else
327        {
328            lineLength += LABELS_MARGIN + labelSize.width();
329        }
330
331        singleLabelHeight = qMax( singleLabelHeight, labelSize.height() );
332    }
333
334    if ( !modl->rowCount() &&
335         !modl->device()->partitionTable() ) // Unknown or no disklabel
336    {
337        singleLabelHeight = sizeForLabel( buildUnknownDisklabelTexts( modl->device() ) )
338                            .height();
339    }
340
341    int totalHeight = numLines * singleLabelHeight +
342                      ( numLines - 1 ) * singleLabelHeight / 4; //spacings
343
344    return QSize( maxLineWidth, totalHeight );
345}
346
347
348QSize
349PartitionLabelsView::sizeForLabel( const QStringList& text ) const
350{
351    int vertOffset = 0;
352    int width = 0;
353    for ( const QString& textLine : text )
354    {
355        QSize textSize = fontMetrics().size( Qt::TextSingleLine, textLine );
356
357        vertOffset += textSize.height();
358        width = qMax( width, textSize.width() );
359    }
360    width += LABEL_PARTITION_SQUARE_MARGIN; //for the color square
361    return QSize( width, vertOffset );
362}
363
364
365void
366PartitionLabelsView::drawLabel( QPainter* painter,
367                                const QStringList& text,
368                                const QColor& color,
369                                const QPoint& pos,
370                                bool selected )
371{
372    painter->setPen( Qt::black );
373    int vertOffset = 0;
374    int width = 0;
375    for ( const QString& textLine : text )
376    {
377        QSize textSize = painter->fontMetrics().size( Qt::TextSingleLine, textLine );
378        painter->drawText( pos.x()+LABEL_PARTITION_SQUARE_MARGIN,
379                           pos.y() + vertOffset + textSize.height() / 2,
380                           textLine );
381        vertOffset += textSize.height();
382        painter->setPen( Qt::gray );
383        width = qMax( width, textSize.width() );
384    }
385
386    QRect partitionSquareRect( pos.x(),
387                               pos.y() - 3,
388                               LABEL_PARTITION_SQUARE_MARGIN - 5,
389                               LABEL_PARTITION_SQUARE_MARGIN - 5 );
390    drawPartitionSquare( painter, partitionSquareRect, color );
391
392    if ( selected )
393        drawSelectionSquare( painter, partitionSquareRect.adjusted( 2, 2, -2, -2 ), color );
394
395    painter->setPen( Qt::black );
396}
397
398
399QModelIndex
400PartitionLabelsView::indexAt( const QPoint& point ) const
401{
402    PartitionModel* modl = qobject_cast< PartitionModel* >( model() );
403    if ( !modl )
404        return QModelIndex();
405
406    const QModelIndexList indexesToDraw = getIndexesToDraw( QModelIndex() );
407
408    QRect rect = this->rect();
409    int label_x = rect.x();
410    int label_y = rect.y();
411    for ( const QModelIndex& index : indexesToDraw )
412    {
413        QStringList texts = buildTexts( index );
414
415        QSize labelSize = sizeForLabel( texts );
416
417        if ( label_x + labelSize.width() > rect.width() ) //wrap to new line if overflow
418        {
419            label_x = rect.x();
420            label_y += labelSize.height() + labelSize.height() / 4;
421        }
422
423        QRect labelRect( QPoint( label_x, label_y ), labelSize );
424        if ( labelRect.contains( point ) )
425            return index;
426
427        label_x += labelSize.width() + LABELS_MARGIN;
428    }
429
430    return QModelIndex();
431}
432
433
434QRect
435PartitionLabelsView::visualRect( const QModelIndex& idx ) const
436{
437    PartitionModel* modl = qobject_cast< PartitionModel* >( model() );
438    if ( !modl )
439        return QRect();
440
441    const QModelIndexList indexesToDraw = getIndexesToDraw( QModelIndex() );
442
443    QRect rect = this->rect();
444    int label_x = rect.x();
445    int label_y = rect.y();
446    for ( const QModelIndex& index : indexesToDraw )
447    {
448        QStringList texts = buildTexts( index );
449
450        QSize labelSize = sizeForLabel( texts );
451
452        if ( label_x + labelSize.width() > rect.width() ) //wrap to new line if overflow
453        {
454            label_x = rect.x();
455            label_y += labelSize.height() + labelSize.height() / 4;
456        }
457
458        if ( idx.isValid() && idx == index )
459            return QRect( QPoint( label_x, label_y ), labelSize );
460
461        label_x += labelSize.width() + LABELS_MARGIN;
462    }
463
464    return QRect();
465}
466
467
468QRegion
469PartitionLabelsView::visualRegionForSelection( const QItemSelection& selection ) const
470{
471    Q_UNUSED( selection );
472
473    return QRegion();
474}
475
476
477int
478PartitionLabelsView::horizontalOffset() const
479{
480    return 0;
481}
482
483
484int
485PartitionLabelsView::verticalOffset() const
486{
487    return 0;
488}
489
490
491void
492PartitionLabelsView::scrollTo( const QModelIndex& index, ScrollHint hint )
493{
494    Q_UNUSED( index )
495    Q_UNUSED( hint )
496}
497
498
499void
500PartitionLabelsView::setCustomNewRootLabel( const QString& text )
501{
502    m_customNewRootLabel = text;
503    viewport()->repaint();
504}
505
506
507void
508PartitionLabelsView::setSelectionModel( QItemSelectionModel* selectionModel )
509{
510    QAbstractItemView::setSelectionModel( selectionModel );
511    connect( selectionModel, &QItemSelectionModel::selectionChanged,
512             this, [=]
513    {
514        viewport()->repaint();
515    } );
516}
517
518
519void
520PartitionLabelsView::setSelectionFilter( SelectionFilter canBeSelected )
521{
522    m_canBeSelected = canBeSelected;
523}
524
525
526void
527PartitionLabelsView::setExtendedPartitionHidden( bool hidden )
528{
529    m_extendedPartitionHidden = hidden;
530}
531
532
533QModelIndex
534PartitionLabelsView::moveCursor( CursorAction cursorAction, Qt::KeyboardModifiers modifiers )
535{
536    Q_UNUSED( cursorAction );
537    Q_UNUSED( modifiers );
538
539    return QModelIndex();
540}
541
542
543bool
544PartitionLabelsView::isIndexHidden( const QModelIndex& index ) const
545{
546    Q_UNUSED( index );
547
548    return false;
549}
550
551
552void
553PartitionLabelsView::setSelection( const QRect& rect, QItemSelectionModel::SelectionFlags flags )
554{
555    QModelIndex eventIndex = indexAt( rect.topLeft() );
556    if ( m_canBeSelected( eventIndex ) )
557        selectionModel()->select( eventIndex, flags );
558}
559
560
561void
562PartitionLabelsView::mouseMoveEvent( QMouseEvent* event )
563{
564    QModelIndex candidateIndex = indexAt( event->pos() );
565    QPersistentModelIndex oldHoveredIndex = m_hoveredIndex;
566    if ( candidateIndex.isValid() )
567    {
568        m_hoveredIndex = candidateIndex;
569    }
570    else
571    {
572        m_hoveredIndex = QModelIndex();
573        QGuiApplication::restoreOverrideCursor();
574    }
575
576    if ( oldHoveredIndex != m_hoveredIndex )
577    {
578        if ( m_hoveredIndex.isValid() && !m_canBeSelected( m_hoveredIndex ) )
579            QGuiApplication::setOverrideCursor( Qt::ForbiddenCursor );
580        else
581            QGuiApplication::restoreOverrideCursor();
582
583        viewport()->repaint();
584    }
585}
586
587
588void
589PartitionLabelsView::leaveEvent( QEvent* event )
590{
591    Q_UNUSED( event );
592
593    QGuiApplication::restoreOverrideCursor();
594    if ( m_hoveredIndex.isValid() )
595    {
596        m_hoveredIndex = QModelIndex();
597        viewport()->repaint();
598    }
599}
600
601
602void
603PartitionLabelsView::mousePressEvent( QMouseEvent* event )
604{
605    QModelIndex candidateIndex = indexAt( event->pos() );
606    if ( m_canBeSelected( candidateIndex ) )
607        QAbstractItemView::mousePressEvent( event );
608    else
609        event->accept();
610}
611
612
613void
614PartitionLabelsView::updateGeometries()
615{
616    updateGeometry(); //get a new rect() for redrawing all the labels
617}
Note: See TracBrowser for help on using the repository browser.