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

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

sync with github

File size: 50.6 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014-2017, Teo Mrnjavac <teo@kde.org>
4 *   Copyright 2017, Adriaan de Groot <groot@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 "ChoicePage.h"
21
22#include "core/BootLoaderModel.h"
23#include "core/PartitionActions.h"
24#include "core/PartitionCoreModule.h"
25#include "core/DeviceModel.h"
26#include "core/PartitionModel.h"
27#include "core/OsproberEntry.h"
28#include "core/PartUtils.h"
29#include "core/PartitionIterator.h"
30
31#include "ReplaceWidget.h"
32#include "PrettyRadioButton.h"
33#include "PartitionBarsView.h"
34#include "PartitionLabelsView.h"
35#include "PartitionSplitterWidget.h"
36#include "BootInfoWidget.h"
37#include "DeviceInfoWidget.h"
38#include "ScanningDialog.h"
39
40#include "utils/CalamaresUtilsGui.h"
41#include "utils/Logger.h"
42#include "utils/Retranslator.h"
43#include "Branding.h"
44#include "core/KPMHelpers.h"
45#include "JobQueue.h"
46#include "GlobalStorage.h"
47#include "core/PartitionInfo.h"
48
49#include <kpmcore/core/device.h>
50#include <kpmcore/core/partition.h>
51
52#include <QBoxLayout>
53#include <QButtonGroup>
54#include <QComboBox>
55#include <QDir>
56#include <QLabel>
57#include <QListView>
58#include <QFutureWatcher>
59#include <QtConcurrent/QtConcurrent>
60
61
62
63/**
64 * @brief ChoicePage::ChoicePage is the default constructor. Called on startup as part of
65 *      the module loading code path.
66 * @param compactMode if true, the drive selector will be a combo box on top, otherwise it
67 *      will show up as a list view.
68 * @param parent the QWidget parent.
69 */
70ChoicePage::ChoicePage( QWidget* parent )
71    : QWidget( parent )
72    , m_nextEnabled( false )
73    , m_core( nullptr )
74    , m_choice( NoChoice )
75    , m_isEfi( false )
76    , m_grp( nullptr )
77    , m_alongsideButton( nullptr )
78    , m_eraseButton( nullptr )
79    , m_replaceButton( nullptr )
80    , m_somethingElseButton( nullptr )
81    , m_deviceInfoWidget( nullptr )
82    , m_beforePartitionBarsView( nullptr )
83    , m_beforePartitionLabelsView( nullptr )
84    , m_bootloaderComboBox( nullptr )
85    , m_lastSelectedDeviceIndex( -1 )
86    , m_enableEncryptionWidget( true )
87{
88    setupUi( this );
89
90    m_defaultFsType = Calamares::JobQueue::instance()->
91                        globalStorage()->
92                        value( "defaultFileSystemType" ).toString();
93    m_enableEncryptionWidget = Calamares::JobQueue::instance()->
94                               globalStorage()->
95                               value( "enableLuksAutomatedPartitioning" ).toBool();
96    if ( FileSystem::typeForName( m_defaultFsType ) == FileSystem::Unknown )
97        m_defaultFsType = "ext4";
98
99    // Set up drives combo
100    m_mainLayout->setDirection( QBoxLayout::TopToBottom );
101    m_drivesLayout->setDirection( QBoxLayout::LeftToRight );
102
103    BootInfoWidget* bootInfoWidget = new BootInfoWidget( this );
104    m_drivesLayout->insertWidget( 0, bootInfoWidget );
105    m_drivesLayout->insertSpacing( 1, CalamaresUtils::defaultFontHeight() / 2 );
106
107    m_drivesCombo = new QComboBox( this );
108    m_mainLayout->setStretchFactor( m_drivesLayout, 0 );
109    m_mainLayout->setStretchFactor( m_rightLayout, 1 );
110    m_drivesLabel->setBuddy( m_drivesCombo );
111
112    m_drivesLayout->addWidget( m_drivesCombo );
113
114    m_deviceInfoWidget = new DeviceInfoWidget;
115    m_drivesLayout->addWidget( m_deviceInfoWidget );
116    m_drivesLayout->addStretch();
117
118    m_messageLabel->setWordWrap( true );
119    m_messageLabel->hide();
120
121    CalamaresUtils::unmarginLayout( m_itemsLayout );
122
123    // Drive selector + preview
124    CALAMARES_RETRANSLATE(
125        retranslateUi( this );
126        m_drivesLabel->setText( tr( "Select storage de&vice:" ) );
127        m_previewBeforeLabel->setText( tr( "Current:" ) );
128        m_previewAfterLabel->setText(  tr( "After:" ) );
129    )
130
131    m_previewBeforeFrame->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
132    m_previewAfterFrame->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
133    m_previewAfterLabel->hide();
134    m_previewAfterFrame->hide();
135    m_encryptWidget->hide();
136    m_reuseHomeCheckBox->hide();
137    Calamares::JobQueue::instance()->globalStorage()->insert( "reuseHome", false );
138}
139
140
141ChoicePage::~ChoicePage()
142{}
143
144
145void
146ChoicePage::init( PartitionCoreModule* core )
147{
148    m_core = core;
149    m_isEfi = PartUtils::isEfiSystem();
150
151    setupChoices();
152
153
154    // We need to do this because a PCM revert invalidates the deviceModel.
155    connect( core, &PartitionCoreModule::reverted,
156             this, [=]
157    {
158        m_drivesCombo->setModel( core->deviceModel() );
159        m_drivesCombo->setCurrentIndex( m_lastSelectedDeviceIndex );
160    } );
161    m_drivesCombo->setModel( core->deviceModel() );
162
163    connect( m_drivesCombo,
164             static_cast< void ( QComboBox::* )( int ) >( &QComboBox::currentIndexChanged ),
165             this, &ChoicePage::applyDeviceChoice );
166
167    connect( m_encryptWidget, &EncryptWidget::stateChanged,
168             this, &ChoicePage::onEncryptWidgetStateChanged );
169    connect( m_reuseHomeCheckBox, &QCheckBox::stateChanged,
170             this, &ChoicePage::onHomeCheckBoxStateChanged );
171
172    ChoicePage::applyDeviceChoice();
173}
174
175
176/**
177 * @brief ChoicePage::setupChoices creates PrettyRadioButton objects for the action
178 *      choices.
179 * @warning This must only run ONCE because it creates signal-slot connections for the
180 *      actions. When an action is triggered, it runs action-specific code that may
181 *      change the internal state of the PCM, and it updates the bottom preview (or
182 *      split) widget.
183 *      Synchronous loading ends here.
184 */
185void
186ChoicePage::setupChoices()
187{
188    // sample os-prober output:
189    // /dev/sda2:Windows 7 (loader):Windows:chain
190    // /dev/sda6::Arch:linux
191    //
192    // There are three possibilities we have to consider:
193    //  - There are no operating systems present
194    //  - There is one operating system present
195    //  - There are multiple operating systems present
196    //
197    // There are three outcomes we have to provide:
198    //  1) Wipe+autopartition
199    //  2) Resize+autopartition
200    //  3) Manual
201    //  TBD: upgrade option?
202
203    QSize iconSize( CalamaresUtils::defaultIconSize().width() * 2,
204                    CalamaresUtils::defaultIconSize().height() * 2 );
205    m_grp = new QButtonGroup( this );
206
207    m_alongsideButton = new PrettyRadioButton;
208    m_alongsideButton->setIconSize( iconSize );
209    m_alongsideButton->setIcon( CalamaresUtils::defaultPixmap( CalamaresUtils::PartitionAlongside,
210                                                               CalamaresUtils::Original,
211                                                               iconSize ) );
212    m_grp->addButton( m_alongsideButton->buttonWidget(), Alongside );
213
214    m_eraseButton = new PrettyRadioButton;
215    m_eraseButton->setIconSize( iconSize );
216    m_eraseButton->setIcon( CalamaresUtils::defaultPixmap( CalamaresUtils::PartitionEraseAuto,
217                                                           CalamaresUtils::Original,
218                                                           iconSize ) );
219    m_grp->addButton( m_eraseButton->buttonWidget(), Erase );
220
221    m_replaceButton = new PrettyRadioButton;
222
223    m_replaceButton->setIconSize( iconSize );
224    m_replaceButton->setIcon( CalamaresUtils::defaultPixmap( CalamaresUtils::PartitionReplaceOs,
225                                                             CalamaresUtils::Original,
226                                                             iconSize ) );
227    m_grp->addButton( m_replaceButton->buttonWidget(), Replace );
228
229    m_itemsLayout->addWidget( m_alongsideButton );
230    m_itemsLayout->addWidget( m_replaceButton );
231    m_itemsLayout->addWidget( m_eraseButton );
232
233    m_somethingElseButton = new PrettyRadioButton;
234    CALAMARES_RETRANSLATE(
235        m_somethingElseButton->setText( tr( "<strong>Manual partitioning</strong><br/>"
236                                            "You can create or resize partitions yourself." ) );
237    )
238    m_somethingElseButton->setIconSize( iconSize );
239    m_somethingElseButton->setIcon( CalamaresUtils::defaultPixmap( CalamaresUtils::PartitionManual,
240                                                                   CalamaresUtils::Original,
241                                                                   iconSize ) );
242    m_itemsLayout->addWidget( m_somethingElseButton );
243    m_grp->addButton( m_somethingElseButton->buttonWidget(), Manual );
244
245    m_itemsLayout->addStretch();
246
247    connect( m_grp, static_cast< void( QButtonGroup::* )( int, bool ) >( &QButtonGroup::buttonToggled ),
248             this, [ this ]( int id, bool checked )
249    {
250        if ( checked )  // An action was picked.
251        {
252            m_choice = static_cast< Choice >( id );
253            updateNextEnabled();
254
255            emit actionChosen();
256        }
257        else    // An action was unpicked, either on its own or because of another selection.
258        {
259            if ( m_grp->checkedButton() == nullptr )  // If no other action is chosen, we must
260            {                                         // set m_choice to NoChoice and reset previews.
261                m_choice = NoChoice;
262                updateNextEnabled();
263
264                emit actionChosen();
265            }
266        }
267    } );
268
269    m_rightLayout->setStretchFactor( m_itemsLayout, 1 );
270    m_rightLayout->setStretchFactor( m_previewBeforeFrame, 0 );
271    m_rightLayout->setStretchFactor( m_previewAfterFrame, 0 );
272
273    connect( this, &ChoicePage::actionChosen,
274             this, [=]
275    {
276        Device* currd = selectedDevice();
277        if ( currd )
278        {
279            applyActionChoice( currentChoice() );
280        }
281    } );
282}
283
284
285/**
286 * @brief ChoicePage::selectedDevice queries the device picker (which may be a combo or
287 *      a list view) to get a pointer to the currently selected Device.
288 * @return a Device pointer, valid in the current state of the PCM, or nullptr if
289 *      something goes wrong.
290 */
291Device*
292ChoicePage::selectedDevice()
293{
294    Device* currentDevice = nullptr;
295    currentDevice = m_core->deviceModel()->deviceForIndex(
296              m_core->deviceModel()->index(
297                  m_drivesCombo->currentIndex() ) );
298
299    return currentDevice;
300}
301
302
303void
304ChoicePage::hideButtons()
305{
306    m_eraseButton->hide();
307    m_replaceButton->hide();
308    m_alongsideButton->hide();
309    m_somethingElseButton->hide();
310}
311
312
313/**
314 * @brief ChoicePage::applyDeviceChoice handler for the selected event of the device
315 *      picker. Calls ChoicePage::selectedDevice() to get the current Device*, then
316 *      updates the preview widget for the on-disk state (calls ChoicePage::
317 *      updateDeviceStatePreview()) and finally sets up the available actions and their
318 *      text by calling ChoicePage::setupActions().
319 */
320void
321ChoicePage::applyDeviceChoice()
322{
323    if ( !selectedDevice() )
324    {
325        hideButtons();
326        return;
327    }
328
329    if ( m_core->isDirty() )
330    {
331        ScanningDialog::run( QtConcurrent::run( [ = ]
332        {
333            QMutexLocker locker( &m_coreMutex );
334            m_core->revertAllDevices();
335        } ),
336        [ this ]
337        {
338            continueApplyDeviceChoice();
339        },
340        this );
341    }
342    else
343    {
344        continueApplyDeviceChoice();
345    }
346}
347
348
349void
350ChoicePage::continueApplyDeviceChoice()
351{
352    Device* currd = selectedDevice();
353
354    // The device should only be nullptr immediately after a PCM reset.
355    // applyDeviceChoice() will be called again momentarily as soon as we handle the
356    // PartitionCoreModule::reverted signal.
357    if ( !currd )
358    {
359        hideButtons();
360        return;
361    }
362
363    updateDeviceStatePreview();
364
365    // Preview setup done. Now we show/hide choices as needed.
366    setupActions();
367
368    m_lastSelectedDeviceIndex = m_drivesCombo->currentIndex();
369
370    emit actionChosen();
371    emit deviceChosen();
372}
373
374
375void
376ChoicePage::applyActionChoice( ChoicePage::Choice choice )
377{
378    m_beforePartitionBarsView->selectionModel()->
379            disconnect( SIGNAL( currentRowChanged( QModelIndex, QModelIndex ) ) );
380    m_beforePartitionBarsView->selectionModel()->clearSelection();
381    m_beforePartitionBarsView->selectionModel()->clearCurrentIndex();
382
383    switch ( choice )
384    {
385    case Erase:
386        if ( m_core->isDirty() )
387        {
388            ScanningDialog::run( QtConcurrent::run( [ = ]
389            {
390                QMutexLocker locker( &m_coreMutex );
391                m_core->revertDevice( selectedDevice() );
392            } ),
393            [ = ]
394            {
395                PartitionActions::doAutopartition( m_core,
396                                                   selectedDevice(),
397                                                   m_encryptWidget->passphrase() );
398                emit deviceChosen();
399            },
400            this );
401        }
402        else
403        {
404            PartitionActions::doAutopartition( m_core,
405                                               selectedDevice(),
406                                               m_encryptWidget->passphrase() );
407            emit deviceChosen();
408        }
409
410        break;
411    case Replace:
412        if ( m_core->isDirty() )
413        {
414            ScanningDialog::run( QtConcurrent::run( [ = ]
415            {
416                QMutexLocker locker( &m_coreMutex );
417                m_core->revertDevice( selectedDevice() );
418            } ),
419            []{},
420            this );
421        }
422        updateNextEnabled();
423
424        connect( m_beforePartitionBarsView->selectionModel(), SIGNAL( currentRowChanged( QModelIndex, QModelIndex ) ),
425                 this, SLOT( onPartitionToReplaceSelected( QModelIndex, QModelIndex ) ),
426                 Qt::UniqueConnection );
427        break;
428
429    case Alongside:
430        if ( m_core->isDirty() )
431        {
432            ScanningDialog::run( QtConcurrent::run( [ = ]
433            {
434                QMutexLocker locker( &m_coreMutex );
435                m_core->revertDevice( selectedDevice() );
436            } ),
437            [this]
438            {
439                // We need to reupdate after reverting because the splitter widget is
440                // not a true view.
441                updateActionChoicePreview( currentChoice() );
442                updateNextEnabled();
443            },
444            this );
445        }
446        updateNextEnabled();
447
448        connect( m_beforePartitionBarsView->selectionModel(), SIGNAL( currentRowChanged( QModelIndex, QModelIndex ) ),
449                 this, SLOT( doAlongsideSetupSplitter( QModelIndex, QModelIndex ) ),
450                 Qt::UniqueConnection );
451        break;
452    case NoChoice:
453    case Manual:
454        break;
455    }
456    updateActionChoicePreview( currentChoice() );
457}
458
459
460void
461ChoicePage::doAlongsideSetupSplitter( const QModelIndex& current,
462                                      const QModelIndex& previous )
463{
464    Q_UNUSED( previous );
465    if ( !current.isValid() )
466        return;
467
468    if ( !m_afterPartitionSplitterWidget )
469        return;
470
471    const PartitionModel* modl = qobject_cast< const PartitionModel* >( current.model() );
472    if ( !modl )
473        return;
474
475    Partition* part = modl->partitionForIndex( current );
476    if ( !part )
477    {
478        cDebug() << Q_FUNC_INFO << "Partition not found for index" << current;
479        return;
480    }
481
482    double requiredStorageGB = Calamares::JobQueue::instance()
483                                    ->globalStorage()
484                                    ->value( "requiredStorageGB" )
485                                    .toDouble();
486
487    qint64 requiredStorageB = qRound64( requiredStorageGB + 0.1 + 2.0 ) * 1024 * 1024 * 1024;
488
489    m_afterPartitionSplitterWidget->setSplitPartition(
490                part->partitionPath(),
491                qRound64( part->used() * 1.1 ),
492                part->capacity() - requiredStorageB,
493                part->capacity() / 2 );
494
495    if ( m_isEfi )
496        setupEfiSystemPartitionSelector();
497
498    cDebug() << "Partition selected for Alongside.";
499
500    updateNextEnabled();
501}
502
503
504void
505ChoicePage::onEncryptWidgetStateChanged()
506{
507    EncryptWidget::State state = m_encryptWidget->state();
508    if ( m_choice == Erase )
509    {
510        if ( state == EncryptWidget::EncryptionConfirmed ||
511             state == EncryptWidget::EncryptionDisabled )
512            applyActionChoice( m_choice );
513    }
514    else if ( m_choice == Replace )
515    {
516        if ( m_beforePartitionBarsView &&
517             m_beforePartitionBarsView->selectionModel()->currentIndex().isValid() &&
518             ( state == EncryptWidget::EncryptionConfirmed ||
519               state == EncryptWidget::EncryptionDisabled ) )
520        {
521            doReplaceSelectedPartition( m_beforePartitionBarsView->
522                                            selectionModel()->
523                                                currentIndex() );
524        }
525    }
526    updateNextEnabled();
527}
528
529
530void
531ChoicePage::onHomeCheckBoxStateChanged()
532{
533    if ( currentChoice() == Replace &&
534         m_beforePartitionBarsView->selectionModel()->currentIndex().isValid() )
535    {
536        doReplaceSelectedPartition( m_beforePartitionBarsView->
537                                        selectionModel()->
538                                            currentIndex() );
539    }
540}
541
542
543void
544ChoicePage::onLeave()
545{
546    if ( m_choice == Alongside )
547        doAlongsideApply();
548
549    if ( m_isEfi && ( m_choice == Alongside || m_choice == Replace ) )
550    {
551        QList< Partition* > efiSystemPartitions = m_core->efiSystemPartitions();
552        if ( efiSystemPartitions.count() == 1 )
553        {
554            PartitionInfo::setMountPoint(
555                    efiSystemPartitions.first(),
556                    Calamares::JobQueue::instance()->
557                        globalStorage()->
558                            value( "efiSystemPartition" ).toString() );
559        }
560        else if ( efiSystemPartitions.count() > 1 && m_efiComboBox )
561        {
562            PartitionInfo::setMountPoint(
563                    efiSystemPartitions.at( m_efiComboBox->currentIndex() ),
564                    Calamares::JobQueue::instance()->
565                        globalStorage()->
566                            value( "efiSystemPartition" ).toString() );
567        }
568        else
569        {
570            cError() << "cannot set up EFI system partition.\nESP count:"
571                     << efiSystemPartitions.count() << "\nm_efiComboBox:"
572                     << m_efiComboBox;
573        }
574    }
575    else    // installPath is then passed to the bootloader module for MBR setup
576    {
577        if ( !m_isEfi )
578        {
579            if ( m_bootloaderComboBox.isNull() )
580            {
581                auto d_p = selectedDevice();
582                if ( d_p )
583                    m_core->setBootLoaderInstallPath( d_p->deviceNode() );
584                else
585                    cWarning() << "No device selected for bootloader.";
586            }
587            else
588            {
589                QVariant var = m_bootloaderComboBox->currentData( BootLoaderModel::BootLoaderPathRole );
590                if ( !var.isValid() )
591                    return;
592                m_core->setBootLoaderInstallPath( var.toString() );
593            }
594        }
595    }
596}
597
598
599void
600ChoicePage::doAlongsideApply()
601{
602    Q_ASSERT( m_afterPartitionSplitterWidget->splitPartitionSize() >= 0 );
603    Q_ASSERT( m_afterPartitionSplitterWidget->newPartitionSize()   >= 0 );
604
605    QMutexLocker locker( &m_coreMutex );
606
607    QString path = m_beforePartitionBarsView->
608                   selectionModel()->
609                   currentIndex().data( PartitionModel::PartitionPathRole ).toString();
610
611    DeviceModel* dm = m_core->deviceModel();
612    for ( int i = 0; i < dm->rowCount(); ++i )
613    {
614        Device* dev = dm->deviceForIndex( dm->index( i ) );
615        Partition* candidate = KPMHelpers::findPartitionByPath( { dev }, path );
616        if ( candidate )
617        {
618            qint64 firstSector = candidate->firstSector();
619            qint64 oldLastSector = candidate->lastSector();
620            qint64 newLastSector = firstSector +
621                                   m_afterPartitionSplitterWidget->splitPartitionSize() /
622                                   dev->logicalSize();
623
624            m_core->resizePartition( dev, candidate, firstSector, newLastSector );
625            Partition* newPartition = nullptr;
626            QString luksPassphrase = m_encryptWidget->passphrase();
627            if ( luksPassphrase.isEmpty() )
628            {
629                newPartition = KPMHelpers::createNewPartition(
630                    candidate->parent(),
631                    *dev,
632                    candidate->roles(),
633                    FileSystem::typeForName( m_defaultFsType ),
634                    newLastSector + 2, // *
635                    oldLastSector
636                );
637            }
638            else
639            {
640                newPartition = KPMHelpers::createNewEncryptedPartition(
641                    candidate->parent(),
642                    *dev,
643                    candidate->roles(),
644                    FileSystem::typeForName( m_defaultFsType ),
645                    newLastSector + 2, // *
646                    oldLastSector,
647                    luksPassphrase
648                );
649            }
650            PartitionInfo::setMountPoint( newPartition, "/" );
651            PartitionInfo::setFormat( newPartition, true );
652            // * for some reason ped_disk_add_partition refuses to create a new partition
653            //   if it starts on the sector immediately after the last used sector, so we
654            //   have to push it one sector further, therefore + 2 instead of + 1.
655
656            m_core->createPartition( dev, newPartition );
657
658            m_core->dumpQueue();
659
660            break;
661        }
662    }
663}
664
665
666void
667ChoicePage::onPartitionToReplaceSelected( const QModelIndex& current,
668                                          const QModelIndex& previous )
669{
670    Q_UNUSED( previous );
671    if ( !current.isValid() )
672        return;
673
674    // Reset state on selection regardless of whether this will be used.
675    m_reuseHomeCheckBox->setChecked( false );
676
677    doReplaceSelectedPartition( current );
678}
679
680
681void
682ChoicePage::doReplaceSelectedPartition( const QModelIndex& current )
683{
684    if ( !current.isValid() )
685        return;
686
687    QString* homePartitionPath = new QString();
688    bool doReuseHomePartition = m_reuseHomeCheckBox->isChecked();
689
690    // NOTE: using by-ref captures because we need to write homePartitionPath and
691    //       doReuseHomePartition *after* the device revert, for later use.
692    ScanningDialog::run( QtConcurrent::run(
693    [ this, current ]( QString* homePartitionPath, bool doReuseHomePartition )
694    {
695        QMutexLocker locker( &m_coreMutex );
696
697        if ( m_core->isDirty() )
698        {
699            m_core->revertDevice( selectedDevice() );
700        }
701
702        // if the partition is unallocated(free space), we don't replace it but create new one
703        // with the same first and last sector
704        Partition* selectedPartition =
705            static_cast< Partition* >( current.data( PartitionModel::PartitionPtrRole )
706                                       .value< void* >() );
707        if ( KPMHelpers::isPartitionFreeSpace( selectedPartition ) )
708        {
709            //NOTE: if the selected partition is free space, we don't deal with
710            //      a separate /home partition at all because there's no existing
711            //      rootfs to read it from.
712            PartitionRole newRoles = PartitionRole( PartitionRole::Primary );
713            PartitionNode* newParent = selectedDevice()->partitionTable();
714
715            if ( selectedPartition->parent() )
716            {
717                Partition* parent = dynamic_cast< Partition* >( selectedPartition->parent() );
718                if ( parent && parent->roles().has( PartitionRole::Extended ) )
719                {
720                    newRoles = PartitionRole( PartitionRole::Logical );
721                    newParent = KPMHelpers::findPartitionByPath( { selectedDevice() }, parent->partitionPath() );
722                }
723            }
724
725            Partition* newPartition = nullptr;
726            if ( m_encryptWidget->state() == EncryptWidget::EncryptionConfirmed )
727            {
728                newPartition = KPMHelpers::createNewEncryptedPartition(
729                    newParent,
730                    *selectedDevice(),
731                    newRoles,
732                    FileSystem::typeForName( m_defaultFsType ),
733                    selectedPartition->firstSector(),
734                    selectedPartition->lastSector(),
735                    m_encryptWidget->passphrase() );
736            }
737            else
738            {
739                newPartition = KPMHelpers::createNewPartition(
740                    newParent,
741                    *selectedDevice(),
742                    newRoles,
743                    FileSystem::typeForName( m_defaultFsType ),
744                    selectedPartition->firstSector(),
745                    selectedPartition->lastSector() );
746            }
747
748            PartitionInfo::setMountPoint( newPartition, "/" );
749            PartitionInfo::setFormat( newPartition, true );
750
751            m_core->createPartition( selectedDevice(), newPartition);
752        }
753        else
754        {
755            // We can't use the PartitionPtrRole because we need to make changes to the
756            // main DeviceModel, not the immutable copy.
757            QString partPath = current.data( PartitionModel::PartitionPathRole ).toString();
758            selectedPartition = KPMHelpers::findPartitionByPath( { selectedDevice() },
759                                                                 partPath );
760            if ( selectedPartition )
761            {
762                // Find out is the selected partition has a rootfs. If yes, then make the
763                // m_reuseHomeCheckBox visible and set its text to something meaningful.
764                homePartitionPath->clear();
765                for ( const OsproberEntry& osproberEntry : m_core->osproberEntries() )
766                    if ( osproberEntry.path == partPath )
767                        *homePartitionPath = osproberEntry.homePath;
768                if ( homePartitionPath->isEmpty() )
769                    doReuseHomePartition = false;
770
771                PartitionActions::doReplacePartition( m_core,
772                                                      selectedDevice(),
773                                                      selectedPartition,
774                                                      m_encryptWidget->passphrase() );
775                Partition* homePartition = KPMHelpers::findPartitionByPath( { selectedDevice() },
776                                                                            *homePartitionPath );
777
778                Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
779                if ( homePartition && doReuseHomePartition )
780                {
781                    PartitionInfo::setMountPoint( homePartition, "/home" );
782                    gs->insert( "reuseHome", true );
783                }
784                else
785                {
786                    gs->insert( "reuseHome", false );
787                }
788            }
789        }
790    }, homePartitionPath, doReuseHomePartition ),
791    [ = ]
792    {
793        m_reuseHomeCheckBox->setVisible( !homePartitionPath->isEmpty() );
794        if ( !homePartitionPath->isEmpty() )
795            m_reuseHomeCheckBox->setText( tr( "Reuse %1 as home partition for %2." )
796                                          .arg( *homePartitionPath )
797                                          .arg( *Calamares::Branding::ShortProductName ) );
798        delete homePartitionPath;
799
800        if ( m_isEfi )
801            setupEfiSystemPartitionSelector();
802
803        updateNextEnabled();
804        if ( !m_bootloaderComboBox.isNull() &&
805             m_bootloaderComboBox->currentIndex() < 0 )
806            m_bootloaderComboBox->setCurrentIndex( m_lastSelectedDeviceIndex );
807    }, this );
808}
809
810
811/**
812 * @brief ChoicePage::updateDeviceStatePreview clears and rebuilds the contents of the
813 *      preview widget for the current on-disk state. This also triggers a rescan in the
814 *      PCM to get a Device* copy that's unaffected by subsequent PCM changes.
815 * @param currentDevice a pointer to the selected Device.
816 */
817void
818ChoicePage::updateDeviceStatePreview()
819{
820    //FIXME: this needs to be made async because the rescan can block the UI thread for
821    //       a while. --Teo 10/2015
822    Device* currentDevice = selectedDevice();
823    Q_ASSERT( currentDevice );
824    QMutexLocker locker( &m_previewsMutex );
825
826    cDebug() << "Updating partitioning state widgets.";
827    qDeleteAll( m_previewBeforeFrame->children() );
828
829    auto layout = m_previewBeforeFrame->layout();
830    if ( layout )
831        layout->deleteLater();  // Doesn't like nullptr
832
833    layout = new QVBoxLayout;
834    m_previewBeforeFrame->setLayout( layout );
835    CalamaresUtils::unmarginLayout( layout );
836    layout->setSpacing( 6 );
837
838    PartitionBarsView::NestedPartitionsMode mode = Calamares::JobQueue::instance()->globalStorage()->
839                                                   value( "drawNestedPartitions" ).toBool() ?
840                                                       PartitionBarsView::DrawNestedPartitions :
841                                                       PartitionBarsView::NoNestedPartitions;
842    m_beforePartitionBarsView = new PartitionBarsView( m_previewBeforeFrame );
843    m_beforePartitionBarsView->setNestedPartitionsMode( mode );
844    m_beforePartitionLabelsView = new PartitionLabelsView( m_previewBeforeFrame );
845    m_beforePartitionLabelsView->setExtendedPartitionHidden( mode == PartitionBarsView::NoNestedPartitions );
846
847    Device* deviceBefore = m_core->immutableDeviceCopy( currentDevice );
848
849    PartitionModel* model = new PartitionModel( m_beforePartitionBarsView );
850    model->init( deviceBefore, m_core->osproberEntries() );
851
852    // The QObject parents tree is meaningful for memory management here,
853    // see qDeleteAll above.
854    deviceBefore->setParent( model );  // Can't reparent across threads
855    model->setParent( m_beforePartitionBarsView );
856
857    m_beforePartitionBarsView->setModel( model );
858    m_beforePartitionLabelsView->setModel( model );
859
860    // Make the bars and labels view use the same selectionModel.
861    auto sm = m_beforePartitionLabelsView->selectionModel();
862    m_beforePartitionLabelsView->setSelectionModel( m_beforePartitionBarsView->selectionModel() );
863    if ( sm )
864        sm->deleteLater();
865
866    switch ( m_choice )
867    {
868    case Replace:
869    case Alongside:
870        m_beforePartitionBarsView->setSelectionMode( QAbstractItemView::SingleSelection );
871        m_beforePartitionLabelsView->setSelectionMode( QAbstractItemView::SingleSelection );
872        break;
873    default:
874        m_beforePartitionBarsView->setSelectionMode( QAbstractItemView::NoSelection );
875        m_beforePartitionLabelsView->setSelectionMode( QAbstractItemView::NoSelection );
876    }
877
878    layout->addWidget( m_beforePartitionBarsView );
879    layout->addWidget( m_beforePartitionLabelsView );
880}
881
882
883/**
884 * @brief ChoicePage::updateActionChoicePreview clears and rebuilds the contents of the
885 *      preview widget for the current PCM-proposed state. No rescans here, this should
886 *      be immediate.
887 * @param currentDevice a pointer to the selected Device.
888 * @param choice the chosen partitioning action.
889 */
890void
891ChoicePage::updateActionChoicePreview( ChoicePage::Choice choice )
892{
893    Device* currentDevice = selectedDevice();
894    Q_ASSERT( currentDevice );
895
896    QMutexLocker locker( &m_previewsMutex );
897
898    cDebug() << "Updating partitioning preview widgets.";
899    qDeleteAll( m_previewAfterFrame->children() );
900
901    auto oldlayout = m_previewAfterFrame->layout();
902    if ( oldlayout )
903        oldlayout->deleteLater();
904
905    QVBoxLayout* layout = new QVBoxLayout;
906    m_previewAfterFrame->setLayout( layout );
907    CalamaresUtils::unmarginLayout( layout );
908    layout->setSpacing( 6 );
909
910    PartitionBarsView::NestedPartitionsMode mode = Calamares::JobQueue::instance()->globalStorage()->
911                                                   value( "drawNestedPartitions" ).toBool() ?
912                                                       PartitionBarsView::DrawNestedPartitions :
913                                                       PartitionBarsView::NoNestedPartitions;
914
915    m_reuseHomeCheckBox->hide();
916    Calamares::JobQueue::instance()->globalStorage()->insert( "reuseHome", false );
917
918    switch ( choice )
919    {
920    case Alongside:
921        {
922            if ( m_enableEncryptionWidget )
923                m_encryptWidget->show();
924            m_previewBeforeLabel->setText( tr( "Current:" ) );
925            m_selectLabel->setText( tr( "<strong>Select a partition to shrink, "
926                                        "then drag the bottom bar to resize</strong>" ) );
927            m_selectLabel->show();
928
929            m_afterPartitionSplitterWidget = new PartitionSplitterWidget( m_previewAfterFrame );
930            m_afterPartitionSplitterWidget->init( selectedDevice(), mode == PartitionBarsView::DrawNestedPartitions );
931            layout->addWidget( m_afterPartitionSplitterWidget );
932
933            QLabel* sizeLabel = new QLabel( m_previewAfterFrame );
934            layout->addWidget( sizeLabel );
935            sizeLabel->setWordWrap( true );
936            connect( m_afterPartitionSplitterWidget, &PartitionSplitterWidget::partitionResized,
937                     this, [ this, sizeLabel ]( const QString& path,
938                                                qint64 size,
939                                                qint64 sizeNext )
940            {
941                Q_UNUSED( path )
942                sizeLabel->setText( tr( "%1 will be shrunk to %2MB and a new "
943                                        "%3MB partition will be created for %4." )
944                                    .arg( m_beforePartitionBarsView->selectionModel()->currentIndex().data().toString() )
945                                    .arg( size / ( 1024 * 1024 ) )
946                                    .arg( sizeNext / ( 1024 * 1024 ) )
947                                    .arg( *Calamares::Branding::ShortProductName ) );
948            } );
949
950            m_previewAfterFrame->show();
951            m_previewAfterLabel->show();
952
953            SelectionFilter filter = [ this ]( const QModelIndex& index )
954            {
955                return PartUtils::canBeResized(
956                    static_cast< Partition* >(
957                        index.data( PartitionModel::PartitionPtrRole )
958                            .value< void* >() ) );
959            };
960            m_beforePartitionBarsView->setSelectionFilter( filter );
961            m_beforePartitionLabelsView->setSelectionFilter( filter );
962
963            break;
964        }
965    case Erase:
966    case Replace:
967        {
968            if ( m_enableEncryptionWidget )
969                m_encryptWidget->show();
970            m_previewBeforeLabel->setText( tr( "Current:" ) );
971            m_afterPartitionBarsView = new PartitionBarsView( m_previewAfterFrame );
972            m_afterPartitionBarsView->setNestedPartitionsMode( mode );
973            m_afterPartitionLabelsView = new PartitionLabelsView( m_previewAfterFrame );
974            m_afterPartitionLabelsView->setExtendedPartitionHidden( mode == PartitionBarsView::NoNestedPartitions );
975            m_afterPartitionLabelsView->setCustomNewRootLabel( *Calamares::Branding::BootloaderEntryName );
976
977            PartitionModel* model = m_core->partitionModelForDevice( selectedDevice() );
978
979            // The QObject parents tree is meaningful for memory management here,
980            // see qDeleteAll above.
981            m_afterPartitionBarsView->setModel( model );
982            m_afterPartitionLabelsView->setModel( model );
983            m_afterPartitionBarsView->setSelectionMode( QAbstractItemView::NoSelection );
984            m_afterPartitionLabelsView->setSelectionMode( QAbstractItemView::NoSelection );
985
986            layout->addWidget( m_afterPartitionBarsView );
987            layout->addWidget( m_afterPartitionLabelsView );
988
989            if ( !m_isEfi )
990            {
991                QWidget* eraseWidget = new QWidget;
992
993                QHBoxLayout* eraseLayout = new QHBoxLayout;
994                eraseWidget->setLayout( eraseLayout );
995                eraseLayout->setContentsMargins( 0, 0, 0, 0 );
996                QLabel* eraseBootloaderLabel = new QLabel( eraseWidget );
997                eraseLayout->addWidget( eraseBootloaderLabel );
998                eraseBootloaderLabel->setText( tr( "Boot loader location:" ) );
999
1000                m_bootloaderComboBox = createBootloaderComboBox( eraseWidget );
1001                connect( m_core, &PartitionCoreModule::deviceReverted,
1002                         this, [ this ]( Device* dev )
1003                {
1004                    Q_UNUSED( dev )
1005                    if ( !m_bootloaderComboBox.isNull() )
1006                    {
1007                        if ( m_bootloaderComboBox->model() != m_core->bootLoaderModel() )
1008                            m_bootloaderComboBox->setModel( m_core->bootLoaderModel() );
1009
1010                        m_bootloaderComboBox->setCurrentIndex( m_lastSelectedDeviceIndex );
1011                    }
1012                }, Qt::QueuedConnection );
1013                // ^ Must be Queued so it's sure to run when the widget is already visible.
1014
1015                eraseLayout->addWidget( m_bootloaderComboBox );
1016                eraseBootloaderLabel->setBuddy( m_bootloaderComboBox );
1017                eraseLayout->addStretch();
1018
1019                layout->addWidget( eraseWidget );
1020            }
1021
1022            m_previewAfterFrame->show();
1023            m_previewAfterLabel->show();
1024
1025            if ( m_choice == Erase )
1026                m_selectLabel->hide();
1027            else
1028            {
1029                SelectionFilter filter = [ this ]( const QModelIndex& index )
1030                {
1031                    return PartUtils::canBeReplaced(
1032                        static_cast< Partition* >(
1033                            index.data( PartitionModel::PartitionPtrRole )
1034                                .value< void* >() ) );
1035                };
1036                m_beforePartitionBarsView->setSelectionFilter( filter );
1037                m_beforePartitionLabelsView->setSelectionFilter( filter );
1038
1039                m_selectLabel->show();
1040                m_selectLabel->setText( tr( "<strong>Select a partition to install on</strong>" ) );
1041            }
1042
1043            break;
1044        }
1045    case NoChoice:
1046    case Manual:
1047        m_selectLabel->hide();
1048        m_previewAfterFrame->hide();
1049        m_previewBeforeLabel->setText( tr( "Current:" ) );
1050        m_previewAfterLabel->hide();
1051        m_encryptWidget->hide();
1052        break;
1053    }
1054
1055    if ( m_isEfi && ( m_choice == Alongside || m_choice == Replace ) )
1056    {
1057        QHBoxLayout* efiLayout = new QHBoxLayout;
1058        layout->addLayout( efiLayout );
1059        m_efiLabel = new QLabel( m_previewAfterFrame );
1060        efiLayout->addWidget( m_efiLabel );
1061        m_efiComboBox = new QComboBox( m_previewAfterFrame );
1062        efiLayout->addWidget( m_efiComboBox );
1063        m_efiLabel->setBuddy( m_efiComboBox );
1064        m_efiComboBox->hide();
1065        efiLayout->addStretch();
1066    }
1067
1068    // Also handle selection behavior on beforeFrame.
1069    QAbstractItemView::SelectionMode previewSelectionMode;
1070    switch ( m_choice )
1071    {
1072    case Replace:
1073    case Alongside:
1074        previewSelectionMode = QAbstractItemView::SingleSelection;
1075        break;
1076    default:
1077        previewSelectionMode = QAbstractItemView::NoSelection;
1078    }
1079
1080    m_beforePartitionBarsView->setSelectionMode( previewSelectionMode );
1081    m_beforePartitionLabelsView->setSelectionMode( previewSelectionMode );
1082}
1083
1084
1085void
1086ChoicePage::setupEfiSystemPartitionSelector()
1087{
1088    Q_ASSERT( m_isEfi );
1089
1090    // Only the already existing ones:
1091    QList< Partition* > efiSystemPartitions = m_core->efiSystemPartitions();
1092
1093    if ( efiSystemPartitions.count() == 0 ) //should never happen
1094    {
1095        m_efiLabel->setText(
1096                    tr( "An EFI system partition cannot be found anywhere "
1097                        "on this system. Please go back and use manual "
1098                        "partitioning to set up %1." )
1099                    .arg( *Calamares::Branding::ShortProductName ) );
1100        updateNextEnabled();
1101    }
1102    else if ( efiSystemPartitions.count() == 1 ) //probably most usual situation
1103    {
1104        m_efiLabel->setText(
1105                    tr( "The EFI system partition at %1 will be used for "
1106                        "starting %2." )
1107                    .arg( efiSystemPartitions.first()->partitionPath() )
1108                    .arg( *Calamares::Branding::ShortProductName ) );
1109    }
1110    else
1111    {
1112        m_efiComboBox->show();
1113        m_efiLabel->setText( tr( "EFI system partition:" ) );
1114        for ( int i = 0; i < efiSystemPartitions.count(); ++i )
1115        {
1116            Partition* efiPartition = efiSystemPartitions.at( i );
1117            m_efiComboBox->addItem( efiPartition->partitionPath(), i );
1118
1119            // We pick an ESP on the currently selected device, if possible
1120            if ( efiPartition->devicePath() == selectedDevice()->deviceNode() &&
1121                 efiPartition->number() == 1 )
1122                m_efiComboBox->setCurrentIndex( i );
1123        }
1124    }
1125}
1126
1127
1128QComboBox*
1129ChoicePage::createBootloaderComboBox( QWidget* parent )
1130{
1131    QComboBox* bcb = new QComboBox( parent );
1132    bcb->setModel( m_core->bootLoaderModel() );
1133
1134    // When the chosen bootloader device changes, we update the choice in the PCM
1135    connect( bcb, static_cast< void (QComboBox::*)(int) >( &QComboBox::currentIndexChanged ),
1136             this, [this]( int newIndex )
1137    {
1138        QComboBox* bcb = qobject_cast< QComboBox* >( sender() );
1139        if ( bcb )
1140        {
1141            QVariant var = bcb->itemData( newIndex, BootLoaderModel::BootLoaderPathRole );
1142            if ( !var.isValid() )
1143                return;
1144            m_core->setBootLoaderInstallPath( var.toString() );
1145        }
1146    } );
1147
1148    return bcb;
1149}
1150
1151
1152static inline void
1153force_uncheck(QButtonGroup* grp, PrettyRadioButton* button)
1154{
1155    button->hide();
1156    grp->setExclusive( false );
1157    button->buttonWidget()->setChecked( false );
1158    grp->setExclusive( true );
1159}
1160
1161/**
1162 * @brief ChoicePage::setupActions happens every time a new Device* is selected in the
1163 *      device picker. Sets up the text and visibility of the partitioning actions based
1164 *      on the currently selected Device*, bootloader and os-prober output.
1165 * @param currentDevice
1166 */
1167void
1168ChoicePage::setupActions()
1169{
1170    Device* currentDevice = selectedDevice();
1171    OsproberEntryList osproberEntriesForCurrentDevice =
1172            getOsproberEntriesForDevice( currentDevice );
1173
1174    if ( currentDevice->partitionTable() )
1175        m_deviceInfoWidget->setPartitionTableType( currentDevice->partitionTable()->type() );
1176    else
1177        m_deviceInfoWidget->setPartitionTableType( PartitionTable::unknownTableType );
1178
1179    // Manual partitioning is always a possibility
1180    m_somethingElseButton->show();
1181
1182    bool atLeastOneCanBeResized = false;
1183    bool atLeastOneCanBeReplaced = false;
1184    bool atLeastOneIsMounted = false;  // Suppress 'erase' if so
1185
1186    for ( auto it = PartitionIterator::begin( currentDevice );
1187          it != PartitionIterator::end( currentDevice ); ++it )
1188    {
1189        if ( PartUtils::canBeResized( *it ) )
1190            atLeastOneCanBeResized = true;
1191        if ( PartUtils::canBeReplaced( *it ) )
1192            atLeastOneCanBeReplaced = true;
1193        if ( (*it)->isMounted() )
1194            atLeastOneIsMounted = true;
1195    }
1196
1197    if ( osproberEntriesForCurrentDevice.count() == 0 )
1198    {
1199        CALAMARES_RETRANSLATE(
1200            m_messageLabel->setText( tr( "This storage device does not seem to have an operating system on it. "
1201                                         "What would you like to do?<br/>"
1202                                         "You will be able to review and confirm your choices "
1203                                         "before any change is made to the storage device." ) );
1204
1205            m_eraseButton->setText( tr( "<strong>Erase disk</strong><br/>"
1206                                        "This will <font color=\"red\">delete</font> all data "
1207                                        "currently present on the selected storage device." ) );
1208
1209            m_alongsideButton->setText( tr( "<strong>Install alongside</strong><br/>"
1210                                            "The installer will shrink a partition to make room for %1." )
1211                                        .arg( *Calamares::Branding::ShortVersionedName ) );
1212
1213            m_replaceButton->setText( tr( "<strong>Replace a partition</strong><br/>"
1214                                          "Replaces a partition with %1." )
1215                                      .arg( *Calamares::Branding::ShortVersionedName ) );
1216        )
1217
1218        m_replaceButton->hide();
1219        m_alongsideButton->hide();
1220        m_grp->setExclusive( false );
1221        m_replaceButton->buttonWidget()->setChecked( false );
1222        m_alongsideButton->buttonWidget()->setChecked( false );
1223        m_grp->setExclusive( true );
1224    }
1225    else if ( osproberEntriesForCurrentDevice.count() == 1 )
1226    {
1227        QString osName = osproberEntriesForCurrentDevice.first().prettyName;
1228
1229        if ( !osName.isEmpty() )
1230        {
1231            CALAMARES_RETRANSLATE(
1232                m_messageLabel->setText( tr( "This storage device has %1 on it. "
1233                                             "What would you like to do?<br/>"
1234                                             "You will be able to review and confirm your choices "
1235                                             "before any change is made to the storage device." )
1236                                            .arg( osName ) );
1237
1238                m_alongsideButton->setText( tr( "<strong>Install alongside</strong><br/>"
1239                                                "The installer will shrink a partition to make room for %1." )
1240                                            .arg( *Calamares::Branding::ShortVersionedName ) );
1241
1242                m_eraseButton->setText( tr( "<strong>Erase disk</strong><br/>"
1243                                            "This will <font color=\"red\">delete</font> all data "
1244                                            "currently present on the selected storage device." ) );
1245
1246
1247                m_replaceButton->setText( tr( "<strong>Replace a partition</strong><br/>"
1248                                              "Replaces a partition with %1." )
1249                                          .arg( *Calamares::Branding::ShortVersionedName ) );
1250            )
1251        }
1252        else
1253        {
1254            CALAMARES_RETRANSLATE(
1255                m_messageLabel->setText( tr( "This storage device already has an operating system on it. "
1256                                             "What would you like to do?<br/>"
1257                                             "You will be able to review and confirm your choices "
1258                                             "before any change is made to the storage device." ) );
1259
1260                m_alongsideButton->setText( tr( "<strong>Install alongside</strong><br/>"
1261                                                "The installer will shrink a partition to make room for %1." )
1262                                            .arg( *Calamares::Branding::ShortVersionedName ) );
1263
1264                m_eraseButton->setText( tr( "<strong>Erase disk</strong><br/>"
1265                                            "This will <font color=\"red\">delete</font> all data "
1266                                            "currently present on the selected storage device." ) );
1267
1268                m_replaceButton->setText( tr( "<strong>Replace a partition</strong><br/>"
1269                                              "Replaces a partition with %1." )
1270                                          .arg( *Calamares::Branding::ShortVersionedName ) );
1271            )
1272        }
1273    }
1274    else
1275    {
1276        // osproberEntriesForCurrentDevice has at least 2 items.
1277
1278        CALAMARES_RETRANSLATE(
1279            m_messageLabel->setText( tr( "This storage device has multiple operating systems on it. "
1280                                         "What would you like to do?<br/>"
1281                                         "You will be able to review and confirm your choices "
1282                                         "before any change is made to the storage device." ) );
1283
1284            m_alongsideButton->setText( tr( "<strong>Install alongside</strong><br/>"
1285                                            "The installer will shrink a partition to make room for %1." )
1286                                        .arg( *Calamares::Branding::ShortVersionedName ) );
1287
1288            m_eraseButton->setText( tr( "<strong>Erase disk</strong><br/>"
1289                                        "This will <font color=\"red\">delete</font> all data "
1290                                        "currently present on the selected storage device." ) );
1291
1292            m_replaceButton->setText( tr( "<strong>Replace a partition</strong><br/>"
1293                                          "Replaces a partition with %1." )
1294                                      .arg( *Calamares::Branding::ShortVersionedName ) );
1295        )
1296    }
1297
1298    if ( atLeastOneCanBeReplaced )
1299        m_replaceButton->show();
1300    else
1301        force_uncheck( m_grp, m_replaceButton );
1302
1303    if ( atLeastOneCanBeResized )
1304        m_alongsideButton->show();
1305    else
1306        force_uncheck( m_grp, m_alongsideButton );
1307
1308    if ( !atLeastOneIsMounted )
1309        m_eraseButton->show();  // None mounted
1310    else
1311        force_uncheck( m_grp, m_eraseButton );
1312
1313    bool isEfi = PartUtils::isEfiSystem();
1314    bool efiSystemPartitionFound = !m_core->efiSystemPartitions().isEmpty();
1315
1316    if ( isEfi && !efiSystemPartitionFound )
1317    {
1318        cWarning() << "System is EFI but there's no EFI system partition, "
1319                    "DISABLING alongside and replace features.";
1320        m_alongsideButton->hide();
1321        m_replaceButton->hide();
1322    }
1323}
1324
1325
1326OsproberEntryList
1327ChoicePage::getOsproberEntriesForDevice( Device* device ) const
1328{
1329    OsproberEntryList eList;
1330    for ( const OsproberEntry& entry : m_core->osproberEntries() )
1331    {
1332        if ( entry.path.startsWith( device->deviceNode() ) )
1333            eList.append( entry );
1334    }
1335    return eList;
1336}
1337
1338
1339bool
1340ChoicePage::isNextEnabled() const
1341{
1342    return m_nextEnabled;
1343}
1344
1345
1346ChoicePage::Choice
1347ChoicePage::currentChoice() const
1348{
1349    return m_choice;
1350}
1351
1352
1353void
1354ChoicePage::updateNextEnabled()
1355{
1356    bool enabled = false;
1357
1358    auto sm_p = m_beforePartitionBarsView ? m_beforePartitionBarsView->selectionModel() : nullptr;
1359
1360    switch ( m_choice )
1361    {
1362    case NoChoice:
1363        enabled = false;
1364        break;
1365    case Replace:
1366    case Alongside:
1367        enabled = sm_p && sm_p->currentIndex().isValid();
1368        break;
1369    case Erase:
1370    case Manual:
1371        enabled = true;
1372    }
1373
1374    if ( m_isEfi &&
1375         ( m_choice == Alongside ||
1376           m_choice == Replace ) )
1377    {
1378        if ( m_core->efiSystemPartitions().count() == 0 )
1379            enabled = false;
1380    }
1381
1382    if ( m_choice != Manual &&
1383         m_encryptWidget->isVisible() &&
1384         m_encryptWidget->state() == EncryptWidget::EncryptionUnconfirmed )
1385        enabled = false;
1386
1387    if ( enabled == m_nextEnabled )
1388        return;
1389
1390    m_nextEnabled = enabled;
1391    emit nextStatusChanged( enabled );
1392}
1393
Note: See TracBrowser for help on using the repository browser.