calamares/src/modules/partition/gui/ChoicePage.cpp

1595 lines
56 KiB
C++
Raw Normal View History

/* === This file is part of Calamares - <https://calamares.io> ===
*
2020-08-22 01:19:58 +02:00
* SPDX-FileCopyrightText: 2014-2017 Teo Mrnjavac <teo@kde.org>
* SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2019 Collabora Ltd
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "ChoicePage.h"
#include "BootInfoWidget.h"
#include "DeviceInfoWidget.h"
#include "PartitionBarsView.h"
#include "PartitionLabelsView.h"
#include "PartitionSplitterWidget.h"
#include "ReplaceWidget.h"
#include "ScanningDialog.h"
#include "core/BootLoaderModel.h"
#include "core/Config.h"
#include "core/DeviceModel.h"
2018-09-13 11:50:46 +02:00
#include "core/KPMHelpers.h"
#include "core/OsproberEntry.h"
#include "core/PartUtils.h"
2018-09-13 11:50:46 +02:00
#include "core/PartitionActions.h"
#include "core/PartitionCoreModule.h"
#include "core/PartitionInfo.h"
#include "core/PartitionModel.h"
#include "Branding.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "partition/PartitionIterator.h"
#include "partition/PartitionQuery.h"
#include "utils/CalamaresUtilsGui.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
#include "utils/Units.h"
#include "widgets/PrettyRadioButton.h"
#include <kpmcore/core/device.h>
#include <kpmcore/core/partition.h>
#ifdef WITH_KPMCORE4API
#include <kpmcore/core/softwareraid.h>
#endif
#include <QBoxLayout>
#include <QButtonGroup>
#include <QComboBox>
#include <QDir>
2020-08-22 01:19:58 +02:00
#include <QFutureWatcher>
#include <QLabel>
#include <QListView>
#include <QtConcurrent/QtConcurrent>
using Calamares::PrettyRadioButton;
2020-08-22 01:19:58 +02:00
using CalamaresUtils::Partition::findPartitionByPath;
using CalamaresUtils::Partition::isPartitionFreeSpace;
using CalamaresUtils::Partition::PartitionIterator;
using PartitionActions::Choices::SwapChoice;
/**
* @brief ChoicePage::ChoicePage is the default constructor. Called on startup as part of
* the module loading code path.
* @param parent the QWidget parent.
*/
ChoicePage::ChoicePage( Config* config, QWidget* parent )
: QWidget( parent )
, m_config( config )
, m_nextEnabled( false )
2015-06-14 00:55:26 +02:00
, m_core( nullptr )
, m_choice( InstallChoice::NoChoice )
, m_isEfi( false )
, m_grp( nullptr )
, m_alongsideButton( nullptr )
, m_eraseButton( nullptr )
, m_replaceButton( nullptr )
, m_somethingElseButton( nullptr )
, m_eraseSwapChoiceComboBox( nullptr )
2015-11-27 17:25:47 +01:00
, m_deviceInfoWidget( nullptr )
, m_beforePartitionBarsView( nullptr )
, m_beforePartitionLabelsView( nullptr )
2016-01-29 17:07:08 +01:00
, m_bootloaderComboBox( nullptr )
2016-09-26 13:14:54 +02:00
, m_enableEncryptionWidget( true )
, m_availableSwapChoices( config->swapChoices() )
2020-09-28 15:32:47 +02:00
, m_eraseSwapChoice( config->initialSwapChoice() )
2019-02-06 10:08:21 +01:00
, m_allowManualPartitioning( true )
{
2015-09-30 17:24:37 +02:00
setupUi( this );
2015-10-22 18:37:09 +02:00
auto gs = Calamares::JobQueue::instance()->globalStorage();
m_defaultFsType = gs->value( "defaultFileSystemType" ).toString();
m_enableEncryptionWidget = gs->value( "enableLuksAutomatedPartitioning" ).toBool();
2019-01-24 21:20:06 +01:00
m_allowManualPartitioning = gs->value( "allowManualPartitioning" ).toBool();
if ( FileSystem::typeForName( m_defaultFsType ) == FileSystem::Unknown )
2020-08-22 01:19:58 +02:00
{
m_defaultFsType = "ext4";
2020-08-22 01:19:58 +02:00
}
// Set up drives combo
m_mainLayout->setDirection( QBoxLayout::TopToBottom );
m_drivesLayout->setDirection( QBoxLayout::LeftToRight );
2016-01-27 18:23:56 +01:00
BootInfoWidget* bootInfoWidget = new BootInfoWidget( this );
2016-01-27 18:27:03 +01:00
m_drivesLayout->insertWidget( 0, bootInfoWidget );
m_drivesLayout->insertSpacing( 1, CalamaresUtils::defaultFontHeight() / 2 );
2016-01-27 18:23:56 +01:00
m_drivesCombo = new QComboBox( this );
m_mainLayout->setStretchFactor( m_drivesLayout, 0 );
m_mainLayout->setStretchFactor( m_rightLayout, 1 );
m_drivesLabel->setBuddy( m_drivesCombo );
m_drivesLayout->addWidget( m_drivesCombo );
2015-11-27 17:25:47 +01:00
m_deviceInfoWidget = new DeviceInfoWidget;
m_drivesLayout->addWidget( m_deviceInfoWidget );
m_drivesLayout->addStretch();
2015-10-22 18:37:09 +02:00
m_messageLabel->setWordWrap( true );
2015-12-15 17:18:10 +01:00
m_messageLabel->hide();
CalamaresUtils::unmarginLayout( m_itemsLayout );
2015-09-30 17:24:37 +02:00
// Drive selector + preview
2020-08-22 01:19:58 +02:00
CALAMARES_RETRANSLATE( retranslateUi( this ); m_drivesLabel->setText( tr( "Select storage de&vice:" ) );
m_previewBeforeLabel->setText( tr( "Current:" ) );
m_previewAfterLabel->setText( tr( "After:" ) ); )
m_previewBeforeFrame->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
m_previewAfterFrame->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
2015-12-31 16:55:18 +01:00
m_previewAfterLabel->hide();
m_previewAfterFrame->hide();
m_encryptWidget->hide();
m_reuseHomeCheckBox->hide();
gs->insert( "reuseHome", false );
}
2020-08-22 01:19:58 +02:00
ChoicePage::~ChoicePage() {}
void
ChoicePage::init( PartitionCoreModule* core )
{
m_core = core;
m_isEfi = PartUtils::isEfiSystem();
setupChoices();
// We need to do this because a PCM revert invalidates the deviceModel.
connect( core, &PartitionCoreModule::reverted, this, [ = ] {
m_drivesCombo->setModel( core->deviceModel() );
m_drivesCombo->setCurrentIndex( m_lastSelectedDeviceIndex );
} );
m_drivesCombo->setModel( core->deviceModel() );
connect( m_drivesCombo,
static_cast< void ( QComboBox::* )( int ) >( &QComboBox::currentIndexChanged ),
2020-08-22 01:19:58 +02:00
this,
&ChoicePage::applyDeviceChoice );
2020-08-22 01:19:58 +02:00
connect( m_encryptWidget, &EncryptWidget::stateChanged, this, &ChoicePage::onEncryptWidgetStateChanged );
connect( m_reuseHomeCheckBox, &QCheckBox::stateChanged, this, &ChoicePage::onHomeCheckBoxStateChanged );
ChoicePage::applyDeviceChoice();
}
/** @brief Creates a combobox with the given choices in it.
*
* Pre-selects the choice given by @p dflt.
* No texts are set -- that happens later by the translator functions.
*/
static inline QComboBox*
createCombo( const QSet< SwapChoice >& s, SwapChoice dflt )
{
QComboBox* box = new QComboBox;
2020-08-22 01:19:58 +02:00
for ( SwapChoice c : { SwapChoice::NoSwap,
SwapChoice::SmallSwap,
SwapChoice::FullSwap,
SwapChoice::ReuseSwap,
SwapChoice::SwapFile } )
if ( s.contains( c ) )
2020-08-22 01:19:58 +02:00
{
box->addItem( QString(), c );
2020-08-22 01:19:58 +02:00
}
int dfltIndex = box->findData( dflt );
if ( dfltIndex >= 0 )
2020-08-22 01:19:58 +02:00
{
box->setCurrentIndex( dfltIndex );
2020-08-22 01:19:58 +02:00
}
return box;
}
/**
* @brief ChoicePage::setupChoices creates PrettyRadioButton objects for the action
* choices.
* @warning This must only run ONCE because it creates signal-slot connections for the
* actions. When an action is triggered, it runs action-specific code that may
* change the internal state of the PCM, and it updates the bottom preview (or
* split) widget.
* Synchronous loading ends here.
*/
void
ChoicePage::setupChoices()
{
// sample os-prober output:
// /dev/sda2:Windows 7 (loader):Windows:chain
// /dev/sda6::Arch:linux
//
// There are three possibilities we have to consider:
// - There are no operating systems present
// - There is one operating system present
// - There are multiple operating systems present
//
// There are three outcomes we have to provide:
// 1) Wipe+autopartition
// 2) Resize+autopartition
// 3) Manual
// TBD: upgrade option?
2020-08-22 01:19:58 +02:00
QSize iconSize( CalamaresUtils::defaultIconSize().width() * 2, CalamaresUtils::defaultIconSize().height() * 2 );
m_grp = new QButtonGroup( this );
m_alongsideButton = new PrettyRadioButton;
m_alongsideButton->setIconSize( iconSize );
2020-08-22 01:19:58 +02:00
m_alongsideButton->setIcon(
CalamaresUtils::defaultPixmap( CalamaresUtils::PartitionAlongside, CalamaresUtils::Original, iconSize ) );
m_alongsideButton->addToGroup( m_grp, InstallChoice::Alongside );
m_eraseButton = new PrettyRadioButton;
m_eraseButton->setIconSize( iconSize );
2020-08-22 01:19:58 +02:00
m_eraseButton->setIcon(
CalamaresUtils::defaultPixmap( CalamaresUtils::PartitionEraseAuto, CalamaresUtils::Original, iconSize ) );
m_eraseButton->addToGroup( m_grp, InstallChoice::Erase );
m_replaceButton = new PrettyRadioButton;
2015-11-20 14:49:37 +01:00
m_replaceButton->setIconSize( iconSize );
2020-08-22 01:19:58 +02:00
m_replaceButton->setIcon(
CalamaresUtils::defaultPixmap( CalamaresUtils::PartitionReplaceOs, CalamaresUtils::Original, iconSize ) );
m_replaceButton->addToGroup( m_grp, InstallChoice::Replace );
// Fill up swap options
// .. TODO: only if enabled in the config
if ( m_availableSwapChoices.count() > 1 )
{
m_eraseSwapChoiceComboBox = createCombo( m_availableSwapChoices, m_eraseSwapChoice );
m_eraseButton->addOptionsComboBox( m_eraseSwapChoiceComboBox );
}
m_itemsLayout->addWidget( m_alongsideButton );
m_itemsLayout->addWidget( m_replaceButton );
m_itemsLayout->addWidget( m_eraseButton );
m_somethingElseButton = new PrettyRadioButton;
m_somethingElseButton->setIconSize( iconSize );
2020-08-22 01:19:58 +02:00
m_somethingElseButton->setIcon(
CalamaresUtils::defaultPixmap( CalamaresUtils::PartitionManual, CalamaresUtils::Original, iconSize ) );
m_itemsLayout->addWidget( m_somethingElseButton );
m_somethingElseButton->addToGroup( m_grp, InstallChoice::Manual );
m_itemsLayout->addStretch();
#if ( QT_VERSION < QT_VERSION_CHECK( 5, 15, 0 ) )
auto buttonSignal = QOverload< int, bool >::of( &QButtonGroup::buttonToggled );
#else
auto buttonSignal = &QButtonGroup::idToggled;
#endif
connect( m_grp, buttonSignal, this, [ this ]( int id, bool checked ) {
if ( checked ) // An action was picked.
{
m_choice = static_cast< InstallChoice >( id );
updateNextEnabled();
emit actionChosen();
}
2020-08-22 01:19:58 +02:00
else // An action was unpicked, either on its own or because of another selection.
{
if ( m_grp->checkedButton() == nullptr ) // If no other action is chosen, we must
2020-08-22 01:19:58 +02:00
{
// set m_choice to NoChoice and reset previews.
m_choice = InstallChoice::NoChoice;
updateNextEnabled();
emit actionChosen();
}
}
} );
m_rightLayout->setStretchFactor( m_itemsLayout, 1 );
m_rightLayout->setStretchFactor( m_previewBeforeFrame, 0 );
m_rightLayout->setStretchFactor( m_previewAfterFrame, 0 );
2020-08-22 01:19:58 +02:00
connect( this, &ChoicePage::actionChosen, this, &ChoicePage::onActionChanged );
if ( m_eraseSwapChoiceComboBox )
2020-08-22 01:19:58 +02:00
connect( m_eraseSwapChoiceComboBox,
QOverload< int >::of( &QComboBox::currentIndexChanged ),
this,
&ChoicePage::onEraseSwapChoiceChanged );
CALAMARES_RETRANSLATE( m_somethingElseButton->setText( tr( "<strong>Manual partitioning</strong><br/>"
"You can create or resize partitions yourself." ) );
updateSwapChoicesTr( m_eraseSwapChoiceComboBox ); )
}
/**
* @brief ChoicePage::selectedDevice queries the device picker (which may be a combo or
* a list view) to get a pointer to the currently selected Device.
* @return a Device pointer, valid in the current state of the PCM, or nullptr if
* something goes wrong.
*/
Device*
ChoicePage::selectedDevice()
{
Device* currentDevice = nullptr;
2020-08-22 01:19:58 +02:00
currentDevice
= m_core->deviceModel()->deviceForIndex( m_core->deviceModel()->index( m_drivesCombo->currentIndex() ) );
return currentDevice;
}
void
ChoicePage::hideButtons()
{
m_eraseButton->hide();
m_replaceButton->hide();
m_alongsideButton->hide();
m_somethingElseButton->hide();
}
void
ChoicePage::checkInstallChoiceRadioButton( InstallChoice c )
{
QSignalBlocker b( m_grp );
m_grp->setExclusive( false );
2020-09-29 12:22:50 +02:00
// If c == InstallChoice::NoChoice none will match and all are deselected
m_eraseButton->setChecked( InstallChoice::Erase == c);
m_replaceButton->setChecked( InstallChoice::Replace == c );
m_alongsideButton->setChecked( InstallChoice::Alongside == c );
m_somethingElseButton->setChecked( InstallChoice::Manual == c );
m_grp->setExclusive( true );
}
/**
* @brief ChoicePage::applyDeviceChoice handler for the selected event of the device
* picker. Calls ChoicePage::selectedDevice() to get the current Device*, then
* updates the preview widget for the on-disk state (calls ChoicePage::
* updateDeviceStatePreview()) and finally sets up the available actions and their
* text by calling ChoicePage::setupActions().
*/
void
ChoicePage::applyDeviceChoice()
{
if ( !selectedDevice() )
{
hideButtons();
return;
}
if ( m_core->isDirty() )
{
2020-08-22 01:19:58 +02:00
ScanningDialog::run(
QtConcurrent::run( [ = ] {
2020-08-22 01:19:58 +02:00
QMutexLocker locker( &m_coreMutex );
m_core->revertAllDevices();
} ),
[ this ] { continueApplyDeviceChoice(); },
2020-08-22 01:19:58 +02:00
this );
}
2016-02-09 13:23:23 +01:00
else
{
continueApplyDeviceChoice();
}
}
2016-02-09 13:23:23 +01:00
void
ChoicePage::continueApplyDeviceChoice()
{
Device* currd = selectedDevice();
// The device should only be nullptr immediately after a PCM reset.
// applyDeviceChoice() will be called again momentarily as soon as we handle the
// PartitionCoreModule::reverted signal.
if ( !currd )
{
hideButtons();
return;
}
updateDeviceStatePreview();
// Preview setup done. Now we show/hide choices as needed.
setupActions();
cDebug() << "Previous device" << m_lastSelectedDeviceIndex << "new device" << m_drivesCombo->currentIndex();
if ( m_lastSelectedDeviceIndex != m_drivesCombo->currentIndex() )
{
m_lastSelectedDeviceIndex = m_drivesCombo->currentIndex();
m_lastSelectedActionIndex = -1;
m_choice = m_config->initialInstallChoice();
checkInstallChoiceRadioButton( m_choice );
}
emit actionChosen();
emit deviceChosen();
}
void
ChoicePage::onActionChanged()
{
Device* currd = selectedDevice();
if ( currd )
{
applyActionChoice( currentChoice() );
}
}
void
ChoicePage::onEraseSwapChoiceChanged()
{
if ( m_eraseSwapChoiceComboBox )
{
2020-08-22 01:19:58 +02:00
m_eraseSwapChoice
= static_cast< PartitionActions::Choices::SwapChoice >( m_eraseSwapChoiceComboBox->currentData().toInt() );
onActionChanged();
}
}
void
ChoicePage::applyActionChoice( ChoicePage::InstallChoice choice )
{
cDebug() << "Prev" << m_lastSelectedActionIndex << "InstallChoice" << choice
<< PartitionActions::Choices::installChoiceNames().find( choice );
2020-08-22 01:19:58 +02:00
m_beforePartitionBarsView->selectionModel()->disconnect( SIGNAL( currentRowChanged( QModelIndex, QModelIndex ) ) );
m_beforePartitionBarsView->selectionModel()->clearSelection();
m_beforePartitionBarsView->selectionModel()->clearCurrentIndex();
switch ( choice )
{
case InstallChoice::Erase:
2020-08-22 01:19:58 +02:00
{
auto gs = Calamares::JobQueue::instance()->globalStorage();
2020-08-22 01:19:58 +02:00
PartitionActions::Choices::AutoPartitionOptions options { gs->value( "defaultFileSystemType" ).toString(),
m_encryptWidget->passphrase(),
gs->value( "efiSystemPartition" ).toString(),
CalamaresUtils::GiBtoBytes(
gs->value( "requiredStorageGiB" ).toDouble() ),
m_eraseSwapChoice };
if ( m_core->isDirty() )
{
ScanningDialog::run(
QtConcurrent::run( [ = ] {
QMutexLocker locker( &m_coreMutex );
m_core->revertDevice( selectedDevice() );
} ),
[ = ] {
PartitionActions::doAutopartition( m_core, selectedDevice(), options );
emit deviceChosen();
},
this );
}
2020-08-22 01:19:58 +02:00
else
{
PartitionActions::doAutopartition( m_core, selectedDevice(), options );
emit deviceChosen();
}
}
break;
case InstallChoice::Replace:
if ( m_core->isDirty() )
{
2020-08-22 01:19:58 +02:00
ScanningDialog::run(
QtConcurrent::run( [ = ] {
2020-08-22 01:19:58 +02:00
QMutexLocker locker( &m_coreMutex );
m_core->revertDevice( selectedDevice() );
} ),
[] {},
this );
}
updateNextEnabled();
2020-08-22 01:19:58 +02:00
connect( m_beforePartitionBarsView->selectionModel(),
SIGNAL( currentRowChanged( QModelIndex, QModelIndex ) ),
this,
SLOT( onPartitionToReplaceSelected( QModelIndex, QModelIndex ) ),
Qt::UniqueConnection );
break;
case InstallChoice::Alongside:
if ( m_core->isDirty() )
{
2020-08-22 01:19:58 +02:00
ScanningDialog::run(
QtConcurrent::run( [ = ] {
2020-08-22 01:19:58 +02:00
QMutexLocker locker( &m_coreMutex );
m_core->revertDevice( selectedDevice() );
} ),
[ this ] {
2020-08-22 01:19:58 +02:00
// We need to reupdate after reverting because the splitter widget is
// not a true view.
updateActionChoicePreview( currentChoice() );
updateNextEnabled();
},
this );
}
updateNextEnabled();
2020-08-22 01:19:58 +02:00
connect( m_beforePartitionBarsView->selectionModel(),
SIGNAL( currentRowChanged( QModelIndex, QModelIndex ) ),
this,
SLOT( doAlongsideSetupSplitter( QModelIndex, QModelIndex ) ),
Qt::UniqueConnection );
break;
case InstallChoice::NoChoice:
case InstallChoice::Manual:
break;
}
updateActionChoicePreview( choice );
}
void
2020-08-22 01:19:58 +02:00
ChoicePage::doAlongsideSetupSplitter( const QModelIndex& current, const QModelIndex& previous )
{
Q_UNUSED( previous )
if ( !current.isValid() )
2020-08-22 01:19:58 +02:00
{
return;
2020-08-22 01:19:58 +02:00
}
if ( !m_afterPartitionSplitterWidget )
2020-08-22 01:19:58 +02:00
{
return;
2020-08-22 01:19:58 +02:00
}
const PartitionModel* modl = qobject_cast< const PartitionModel* >( current.model() );
if ( !modl )
2020-08-22 01:19:58 +02:00
{
return;
2020-08-22 01:19:58 +02:00
}
Partition* part = modl->partitionForIndex( current );
if ( !part )
{
cDebug() << "Partition not found for index" << current;
return;
}
2020-08-22 01:19:58 +02:00
double requiredStorageGB
= Calamares::JobQueue::instance()->globalStorage()->value( "requiredStorageGiB" ).toDouble();
qint64 requiredStorageB = CalamaresUtils::GiBtoBytes( requiredStorageGB + 0.1 + 2.0 );
2020-08-22 01:19:58 +02:00
m_afterPartitionSplitterWidget->setSplitPartition( part->partitionPath(),
qRound64( part->used() * 1.1 ),
part->capacity() - requiredStorageB,
part->capacity() / 2 );
2016-02-19 16:57:49 +01:00
if ( m_isEfi )
2020-08-22 01:19:58 +02:00
{
2016-02-19 16:57:49 +01:00
setupEfiSystemPartitionSelector();
2020-08-22 01:19:58 +02:00
}
2016-02-19 16:51:24 +01:00
cDebug() << "Partition selected for Alongside.";
2016-05-17 08:46:56 +02:00
updateNextEnabled();
2016-01-13 17:43:59 +01:00
}
void
ChoicePage::onEncryptWidgetStateChanged()
{
EncryptWidget::Encryption state = m_encryptWidget->state();
if ( m_choice == InstallChoice::Erase )
{
2020-08-22 01:19:58 +02:00
if ( state == EncryptWidget::Encryption::Confirmed || state == EncryptWidget::Encryption::Disabled )
{
applyActionChoice( m_choice );
2020-08-22 01:19:58 +02:00
}
}
else if ( m_choice == InstallChoice::Replace )
{
2020-08-22 01:19:58 +02:00
if ( m_beforePartitionBarsView && m_beforePartitionBarsView->selectionModel()->currentIndex().isValid()
&& ( state == EncryptWidget::Encryption::Confirmed || state == EncryptWidget::Encryption::Disabled ) )
{
2020-08-22 01:19:58 +02:00
doReplaceSelectedPartition( m_beforePartitionBarsView->selectionModel()->currentIndex() );
}
}
updateNextEnabled();
}
void
ChoicePage::onHomeCheckBoxStateChanged()
{
2020-08-22 01:19:58 +02:00
if ( currentChoice() == InstallChoice::Replace
&& m_beforePartitionBarsView->selectionModel()->currentIndex().isValid() )
{
2020-08-22 01:19:58 +02:00
doReplaceSelectedPartition( m_beforePartitionBarsView->selectionModel()->currentIndex() );
}
}
void
ChoicePage::onLeave()
{
if ( m_choice == InstallChoice::Alongside )
2020-08-22 01:19:58 +02:00
{
doAlongsideApply();
2020-08-22 01:19:58 +02:00
}
if ( m_isEfi && ( m_choice == InstallChoice::Alongside || m_choice == InstallChoice::Replace ) )
{
QList< Partition* > efiSystemPartitions = m_core->efiSystemPartitions();
if ( efiSystemPartitions.count() == 1 )
{
PartitionInfo::setMountPoint(
2020-08-22 01:19:58 +02:00
efiSystemPartitions.first(),
Calamares::JobQueue::instance()->globalStorage()->value( "efiSystemPartition" ).toString() );
}
else if ( efiSystemPartitions.count() > 1 && m_efiComboBox )
{
PartitionInfo::setMountPoint(
2020-08-22 01:19:58 +02:00
efiSystemPartitions.at( m_efiComboBox->currentIndex() ),
Calamares::JobQueue::instance()->globalStorage()->value( "efiSystemPartition" ).toString() );
}
else
{
2020-08-22 01:19:58 +02:00
cError() << "cannot set up EFI system partition.\nESP count:" << efiSystemPartitions.count()
<< "\nm_efiComboBox:" << m_efiComboBox;
}
}
2020-08-22 01:19:58 +02:00
else // installPath is then passed to the bootloader module for MBR setup
{
2017-01-18 19:38:12 +01:00
if ( !m_isEfi )
{
if ( m_bootloaderComboBox.isNull() )
{
auto d_p = selectedDevice();
if ( d_p )
2020-08-22 01:19:58 +02:00
{
m_core->setBootLoaderInstallPath( d_p->deviceNode() );
2020-08-22 01:19:58 +02:00
}
else
2020-08-22 01:19:58 +02:00
{
cWarning() << "No device selected for bootloader.";
2020-08-22 01:19:58 +02:00
}
2017-01-18 19:38:12 +01:00
}
else
{
QVariant var = m_bootloaderComboBox->currentData( BootLoaderModel::BootLoaderPathRole );
if ( !var.isValid() )
2020-08-22 01:19:58 +02:00
{
2017-01-18 19:38:12 +01:00
return;
2020-08-22 01:19:58 +02:00
}
2017-01-18 19:38:12 +01:00
m_core->setBootLoaderInstallPath( var.toString() );
}
}
}
}
2016-01-13 17:43:59 +01:00
void
ChoicePage::doAlongsideApply()
{
Q_ASSERT( m_afterPartitionSplitterWidget->splitPartitionSize() >= 0 );
2020-08-22 01:19:58 +02:00
Q_ASSERT( m_afterPartitionSplitterWidget->newPartitionSize() >= 0 );
QMutexLocker locker( &m_coreMutex );
2020-08-22 01:19:58 +02:00
QString path = m_beforePartitionBarsView->selectionModel()
->currentIndex()
.data( PartitionModel::PartitionPathRole )
.toString();
2016-01-13 17:43:59 +01:00
DeviceModel* dm = m_core->deviceModel();
for ( int i = 0; i < dm->rowCount(); ++i )
{
Device* dev = dm->deviceForIndex( dm->index( i ) );
Partition* candidate = findPartitionByPath( { dev }, path );
2016-01-13 17:43:59 +01:00
if ( candidate )
{
qint64 firstSector = candidate->firstSector();
qint64 oldLastSector = candidate->lastSector();
2020-08-22 01:19:58 +02:00
qint64 newLastSector
= firstSector + m_afterPartitionSplitterWidget->splitPartitionSize() / dev->logicalSize();
2016-01-13 17:43:59 +01:00
m_core->resizePartition( dev, candidate, firstSector, newLastSector );
2020-08-22 01:19:58 +02:00
m_core->layoutApply( dev,
newLastSector + 2,
oldLastSector,
m_encryptWidget->passphrase(),
candidate->parent(),
candidate->roles() );
2016-01-13 17:43:59 +01:00
m_core->dumpQueue();
break;
}
}
}
2015-12-24 17:24:26 +01:00
void
2020-08-22 01:19:58 +02:00
ChoicePage::onPartitionToReplaceSelected( const QModelIndex& current, const QModelIndex& previous )
2015-12-24 17:24:26 +01:00
{
Q_UNUSED( previous )
if ( !current.isValid() )
2020-08-22 01:19:58 +02:00
{
return;
2020-08-22 01:19:58 +02:00
}
// Reset state on selection regardless of whether this will be used.
m_reuseHomeCheckBox->setChecked( false );
doReplaceSelectedPartition( current );
}
void
ChoicePage::doReplaceSelectedPartition( const QModelIndex& current )
{
if ( !current.isValid() )
2020-08-22 01:19:58 +02:00
{
return;
2020-08-22 01:19:58 +02:00
}
QString* homePartitionPath = new QString();
bool doReuseHomePartition = m_reuseHomeCheckBox->isChecked();
// NOTE: using by-ref captures because we need to write homePartitionPath and
// doReuseHomePartition *after* the device revert, for later use.
2020-08-22 01:19:58 +02:00
ScanningDialog::run(
QtConcurrent::run(
[ this, current ]( QString* homePartitionPath, bool doReuseHomePartition ) {
2020-08-22 01:19:58 +02:00
QMutexLocker locker( &m_coreMutex );
2020-08-22 01:19:58 +02:00
if ( m_core->isDirty() )
{
2020-08-22 01:19:58 +02:00
m_core->revertDevice( selectedDevice() );
}
2020-08-22 01:19:58 +02:00
// if the partition is unallocated(free space), we don't replace it but create new one
// with the same first and last sector
Partition* selectedPartition
= static_cast< Partition* >( current.data( PartitionModel::PartitionPtrRole ).value< void* >() );
if ( isPartitionFreeSpace( selectedPartition ) )
{
//NOTE: if the selected partition is free space, we don't deal with
// a separate /home partition at all because there's no existing
// rootfs to read it from.
PartitionRole newRoles = PartitionRole( PartitionRole::Primary );
PartitionNode* newParent = selectedDevice()->partitionTable();
if ( selectedPartition->parent() )
{
2020-08-22 01:19:58 +02:00
Partition* parent = dynamic_cast< Partition* >( selectedPartition->parent() );
if ( parent && parent->roles().has( PartitionRole::Extended ) )
{
newRoles = PartitionRole( PartitionRole::Logical );
newParent = findPartitionByPath( { selectedDevice() }, parent->partitionPath() );
}
}
2016-07-15 13:09:07 +02:00
2020-08-22 01:19:58 +02:00
m_core->layoutApply( selectedDevice(),
selectedPartition->firstSector(),
selectedPartition->lastSector(),
m_encryptWidget->passphrase(),
newParent,
newRoles );
2016-07-15 13:09:07 +02:00
}
else
{
2020-08-22 01:19:58 +02:00
// We can't use the PartitionPtrRole because we need to make changes to the
// main DeviceModel, not the immutable copy.
QString partPath = current.data( PartitionModel::PartitionPathRole ).toString();
selectedPartition = findPartitionByPath( { selectedDevice() }, partPath );
if ( selectedPartition )
{
// Find out is the selected partition has a rootfs. If yes, then make the
// m_reuseHomeCheckBox visible and set its text to something meaningful.
homePartitionPath->clear();
for ( const OsproberEntry& osproberEntry : m_core->osproberEntries() )
if ( osproberEntry.path == partPath )
{
*homePartitionPath = osproberEntry.homePath;
}
if ( homePartitionPath->isEmpty() )
{
doReuseHomePartition = false;
}
2020-08-22 01:19:58 +02:00
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
2016-02-19 16:51:24 +01:00
2020-08-22 01:19:58 +02:00
PartitionActions::doReplacePartition(
m_core,
selectedDevice(),
selectedPartition,
{ gs->value( "defaultFileSystemType" ).toString(), m_encryptWidget->passphrase() } );
Partition* homePartition = findPartitionByPath( { selectedDevice() }, *homePartitionPath );
if ( homePartition && doReuseHomePartition )
{
PartitionInfo::setMountPoint( homePartition, "/home" );
gs->insert( "reuseHome", true );
}
else
{
gs->insert( "reuseHome", false );
}
}
}
},
homePartitionPath,
doReuseHomePartition ),
[ = ] {
2020-08-22 01:19:58 +02:00
m_reuseHomeCheckBox->setVisible( !homePartitionPath->isEmpty() );
if ( !homePartitionPath->isEmpty() )
m_reuseHomeCheckBox->setText( tr( "Reuse %1 as home partition for %2." )
.arg( *homePartitionPath )
.arg( Calamares::Branding::instance()->shortProductName() ) );
delete homePartitionPath;
if ( m_isEfi )
setupEfiSystemPartitionSelector();
updateNextEnabled();
if ( !m_bootloaderComboBox.isNull() && m_bootloaderComboBox->currentIndex() < 0 )
m_bootloaderComboBox->setCurrentIndex( m_lastSelectedDeviceIndex );
},
this );
2015-12-24 17:24:26 +01:00
}
/**
* @brief ChoicePage::updateDeviceStatePreview clears and rebuilds the contents of the
* preview widget for the current on-disk state. This also triggers a rescan in the
* PCM to get a Device* copy that's unaffected by subsequent PCM changes.
* @param currentDevice a pointer to the selected Device.
*/
void
ChoicePage::updateDeviceStatePreview()
{
//FIXME: this needs to be made async because the rescan can block the UI thread for
// a while. --Teo 10/2015
Device* currentDevice = selectedDevice();
Q_ASSERT( currentDevice );
QMutexLocker locker( &m_previewsMutex );
cDebug() << "Updating partitioning state widgets.";
qDeleteAll( m_previewBeforeFrame->children() );
auto layout = m_previewBeforeFrame->layout();
if ( layout )
2020-08-22 01:19:58 +02:00
{
layout->deleteLater(); // Doesn't like nullptr
2020-08-22 01:19:58 +02:00
}
layout = new QVBoxLayout;
m_previewBeforeFrame->setLayout( layout );
2015-12-03 19:40:06 +01:00
CalamaresUtils::unmarginLayout( layout );
layout->setSpacing( 6 );
2020-08-22 01:19:58 +02:00
PartitionBarsView::NestedPartitionsMode mode
= Calamares::JobQueue::instance()->globalStorage()->value( "drawNestedPartitions" ).toBool()
? PartitionBarsView::DrawNestedPartitions
: PartitionBarsView::NoNestedPartitions;
m_beforePartitionBarsView = new PartitionBarsView( m_previewBeforeFrame );
m_beforePartitionBarsView->setNestedPartitionsMode( mode );
m_beforePartitionLabelsView = new PartitionLabelsView( m_previewBeforeFrame );
2016-02-11 16:00:36 +01:00
m_beforePartitionLabelsView->setExtendedPartitionHidden( mode == PartitionBarsView::NoNestedPartitions );
Device* deviceBefore = m_core->immutableDeviceCopy( currentDevice );
PartitionModel* model = new PartitionModel( m_beforePartitionBarsView );
model->init( deviceBefore, m_core->osproberEntries() );
m_beforePartitionBarsView->setModel( model );
m_beforePartitionLabelsView->setModel( model );
// Make the bars and labels view use the same selectionModel.
auto sm = m_beforePartitionLabelsView->selectionModel();
m_beforePartitionLabelsView->setSelectionModel( m_beforePartitionBarsView->selectionModel() );
if ( sm )
2020-08-22 01:19:58 +02:00
{
sm->deleteLater();
2020-08-22 01:19:58 +02:00
}
switch ( m_choice )
{
case InstallChoice::Replace:
case InstallChoice::Alongside:
m_beforePartitionBarsView->setSelectionMode( QAbstractItemView::SingleSelection );
m_beforePartitionLabelsView->setSelectionMode( QAbstractItemView::SingleSelection );
break;
default:
m_beforePartitionBarsView->setSelectionMode( QAbstractItemView::NoSelection );
m_beforePartitionLabelsView->setSelectionMode( QAbstractItemView::NoSelection );
}
layout->addWidget( m_beforePartitionBarsView );
layout->addWidget( m_beforePartitionLabelsView );
}
/**
* @brief ChoicePage::updateActionChoicePreview clears and rebuilds the contents of the
* preview widget for the current PCM-proposed state. No rescans here, this should
* be immediate.
* @param currentDevice a pointer to the selected Device.
* @param choice the chosen partitioning action.
*/
void
ChoicePage::updateActionChoicePreview( ChoicePage::InstallChoice choice )
{
Device* currentDevice = selectedDevice();
Q_ASSERT( currentDevice );
QMutexLocker locker( &m_previewsMutex );
cDebug() << "Updating partitioning preview widgets.";
qDeleteAll( m_previewAfterFrame->children() );
auto oldlayout = m_previewAfterFrame->layout();
if ( oldlayout )
2020-08-22 01:19:58 +02:00
{
oldlayout->deleteLater();
2020-08-22 01:19:58 +02:00
}
QVBoxLayout* layout = new QVBoxLayout;
m_previewAfterFrame->setLayout( layout );
2015-12-03 19:40:06 +01:00
CalamaresUtils::unmarginLayout( layout );
layout->setSpacing( 6 );
2020-08-22 01:19:58 +02:00
PartitionBarsView::NestedPartitionsMode mode
= Calamares::JobQueue::instance()->globalStorage()->value( "drawNestedPartitions" ).toBool()
? PartitionBarsView::DrawNestedPartitions
: PartitionBarsView::NoNestedPartitions;
2016-02-10 17:02:42 +01:00
m_reuseHomeCheckBox->hide();
2016-07-15 13:09:07 +02:00
Calamares::JobQueue::instance()->globalStorage()->insert( "reuseHome", false );
switch ( choice )
{
case InstallChoice::Alongside:
2020-08-22 01:19:58 +02:00
{
if ( m_enableEncryptionWidget )
{
2020-08-22 01:19:58 +02:00
m_encryptWidget->show();
}
m_previewBeforeLabel->setText( tr( "Current:" ) );
m_selectLabel->setText( tr( "<strong>Select a partition to shrink, "
"then drag the bottom bar to resize</strong>" ) );
m_selectLabel->show();
m_afterPartitionSplitterWidget = new PartitionSplitterWidget( m_previewAfterFrame );
m_afterPartitionSplitterWidget->init( selectedDevice(), mode == PartitionBarsView::DrawNestedPartitions );
layout->addWidget( m_afterPartitionSplitterWidget );
QLabel* sizeLabel = new QLabel( m_previewAfterFrame );
layout->addWidget( sizeLabel );
sizeLabel->setWordWrap( true );
connect( m_afterPartitionSplitterWidget,
&PartitionSplitterWidget::partitionResized,
this,
[ this, sizeLabel ]( const QString& path, qint64 size, qint64 sizeNext ) {
2020-08-22 01:19:58 +02:00
Q_UNUSED( path )
sizeLabel->setText(
tr( "%1 will be shrunk to %2MiB and a new "
"%3MiB partition will be created for %4." )
.arg( m_beforePartitionBarsView->selectionModel()->currentIndex().data().toString() )
.arg( CalamaresUtils::BytesToMiB( size ) )
.arg( CalamaresUtils::BytesToMiB( sizeNext ) )
.arg( Calamares::Branding::instance()->shortProductName() ) );
} );
m_previewAfterFrame->show();
m_previewAfterLabel->show();
SelectionFilter filter = [ this ]( const QModelIndex& index ) {
2020-08-22 01:19:58 +02:00
return PartUtils::canBeResized(
static_cast< Partition* >( index.data( PartitionModel::PartitionPtrRole ).value< void* >() ) );
};
m_beforePartitionBarsView->setSelectionFilter( filter );
m_beforePartitionLabelsView->setSelectionFilter( filter );
2020-08-22 01:19:58 +02:00
break;
}
case InstallChoice::Erase:
case InstallChoice::Replace:
{
if ( m_enableEncryptionWidget )
{
m_encryptWidget->show();
}
m_previewBeforeLabel->setText( tr( "Current:" ) );
m_afterPartitionBarsView = new PartitionBarsView( m_previewAfterFrame );
m_afterPartitionBarsView->setNestedPartitionsMode( mode );
m_afterPartitionLabelsView = new PartitionLabelsView( m_previewAfterFrame );
m_afterPartitionLabelsView->setExtendedPartitionHidden( mode == PartitionBarsView::NoNestedPartitions );
m_afterPartitionLabelsView->setCustomNewRootLabel(
Calamares::Branding::instance()->string( Calamares::Branding::BootloaderEntryName ) );
2020-08-22 01:19:58 +02:00
PartitionModel* model = m_core->partitionModelForDevice( selectedDevice() );
2020-08-22 01:19:58 +02:00
// The QObject parents tree is meaningful for memory management here,
// see qDeleteAll above.
m_afterPartitionBarsView->setModel( model );
m_afterPartitionLabelsView->setModel( model );
m_afterPartitionBarsView->setSelectionMode( QAbstractItemView::NoSelection );
m_afterPartitionLabelsView->setSelectionMode( QAbstractItemView::NoSelection );
2020-08-22 01:19:58 +02:00
layout->addWidget( m_afterPartitionBarsView );
layout->addWidget( m_afterPartitionLabelsView );
2016-01-15 18:20:40 +01:00
2020-08-22 01:19:58 +02:00
if ( !m_isEfi )
{
2020-08-22 01:19:58 +02:00
QWidget* eraseWidget = new QWidget;
QHBoxLayout* eraseLayout = new QHBoxLayout;
eraseWidget->setLayout( eraseLayout );
eraseLayout->setContentsMargins( 0, 0, 0, 0 );
QLabel* eraseBootloaderLabel = new QLabel( eraseWidget );
eraseLayout->addWidget( eraseBootloaderLabel );
eraseBootloaderLabel->setText( tr( "Boot loader location:" ) );
m_bootloaderComboBox = createBootloaderComboBox( eraseWidget );
connect( m_core->bootLoaderModel(), &QAbstractItemModel::modelReset, [ this ]() {
2020-08-22 01:19:58 +02:00
if ( !m_bootloaderComboBox.isNull() )
{
Calamares::restoreSelectedBootLoader( *m_bootloaderComboBox, m_core->bootLoaderInstallPath() );
}
} );
connect(
m_core,
&PartitionCoreModule::deviceReverted,
this,
[ this ]( Device* dev ) {
2020-08-22 01:19:58 +02:00
Q_UNUSED( dev )
if ( !m_bootloaderComboBox.isNull() )
{
2020-08-22 01:19:58 +02:00
if ( m_bootloaderComboBox->model() != m_core->bootLoaderModel() )
{
2020-08-22 01:19:58 +02:00
m_bootloaderComboBox->setModel( m_core->bootLoaderModel() );
}
2016-01-29 17:07:08 +01:00
2020-08-22 01:19:58 +02:00
m_bootloaderComboBox->setCurrentIndex( m_lastSelectedDeviceIndex );
}
},
Qt::QueuedConnection );
// ^ Must be Queued so it's sure to run when the widget is already visible.
2020-08-22 01:19:58 +02:00
eraseLayout->addWidget( m_bootloaderComboBox );
eraseBootloaderLabel->setBuddy( m_bootloaderComboBox );
eraseLayout->addStretch();
2020-08-22 01:19:58 +02:00
layout->addWidget( eraseWidget );
}
2015-12-22 12:41:47 +01:00
2020-08-22 01:19:58 +02:00
m_previewAfterFrame->show();
m_previewAfterLabel->show();
2015-12-22 12:41:47 +01:00
2020-08-22 01:19:58 +02:00
if ( m_choice == InstallChoice::Erase )
{
m_selectLabel->hide();
}
else
{
SelectionFilter filter = [ this ]( const QModelIndex& index ) {
2020-08-22 01:19:58 +02:00
return PartUtils::canBeReplaced(
static_cast< Partition* >( index.data( PartitionModel::PartitionPtrRole ).value< void* >() ) );
};
m_beforePartitionBarsView->setSelectionFilter( filter );
m_beforePartitionLabelsView->setSelectionFilter( filter );
m_selectLabel->show();
m_selectLabel->setText( tr( "<strong>Select a partition to install on</strong>" ) );
}
2020-08-22 01:19:58 +02:00
break;
}
case InstallChoice::NoChoice:
case InstallChoice::Manual:
2015-12-22 12:41:47 +01:00
m_selectLabel->hide();
m_previewAfterFrame->hide();
2016-01-25 16:58:54 +01:00
m_previewBeforeLabel->setText( tr( "Current:" ) );
2015-12-31 16:55:18 +01:00
m_previewAfterLabel->hide();
m_encryptWidget->hide();
break;
}
if ( m_isEfi && ( m_choice == InstallChoice::Alongside || m_choice == InstallChoice::Replace ) )
{
QHBoxLayout* efiLayout = new QHBoxLayout;
layout->addLayout( efiLayout );
m_efiLabel = new QLabel( m_previewAfterFrame );
efiLayout->addWidget( m_efiLabel );
m_efiComboBox = new QComboBox( m_previewAfterFrame );
efiLayout->addWidget( m_efiComboBox );
m_efiLabel->setBuddy( m_efiComboBox );
m_efiComboBox->hide();
efiLayout->addStretch();
}
// Also handle selection behavior on beforeFrame.
QAbstractItemView::SelectionMode previewSelectionMode;
switch ( m_choice )
{
case InstallChoice::Replace:
case InstallChoice::Alongside:
previewSelectionMode = QAbstractItemView::SingleSelection;
break;
default:
previewSelectionMode = QAbstractItemView::NoSelection;
}
2015-12-31 15:02:46 +01:00
m_beforePartitionBarsView->setSelectionMode( previewSelectionMode );
m_beforePartitionLabelsView->setSelectionMode( previewSelectionMode );
}
2016-02-19 16:57:49 +01:00
void
ChoicePage::setupEfiSystemPartitionSelector()
{
Q_ASSERT( m_isEfi );
// Only the already existing ones:
QList< Partition* > efiSystemPartitions = m_core->efiSystemPartitions();
2020-08-22 01:19:58 +02:00
if ( efiSystemPartitions.count() == 0 ) //should never happen
2016-02-19 16:57:49 +01:00
{
2020-08-22 01:19:58 +02:00
m_efiLabel->setText( tr( "An EFI system partition cannot be found anywhere "
"on this system. Please go back and use manual "
"partitioning to set up %1." )
.arg( Calamares::Branding::instance()->shortProductName() ) );
updateNextEnabled();
2016-02-19 16:57:49 +01:00
}
2020-08-22 01:19:58 +02:00
else if ( efiSystemPartitions.count() == 1 ) //probably most usual situation
2016-02-19 16:57:49 +01:00
{
2020-08-22 01:19:58 +02:00
m_efiLabel->setText( tr( "The EFI system partition at %1 will be used for "
"starting %2." )
.arg( efiSystemPartitions.first()->partitionPath() )
.arg( Calamares::Branding::instance()->shortProductName() ) );
2016-02-19 16:57:49 +01:00
}
else
{
m_efiComboBox->show();
m_efiLabel->setText( tr( "EFI system partition:" ) );
for ( int i = 0; i < efiSystemPartitions.count(); ++i )
{
Partition* efiPartition = efiSystemPartitions.at( i );
m_efiComboBox->addItem( efiPartition->partitionPath(), i );
// We pick an ESP on the currently selected device, if possible
2020-08-22 01:19:58 +02:00
if ( efiPartition->devicePath() == selectedDevice()->deviceNode() && efiPartition->number() == 1 )
{
2016-02-19 16:57:49 +01:00
m_efiComboBox->setCurrentIndex( i );
2020-08-22 01:19:58 +02:00
}
2016-02-19 16:57:49 +01:00
}
}
}
QComboBox*
ChoicePage::createBootloaderComboBox( QWidget* parent )
{
QComboBox* bcb = new QComboBox( parent );
bcb->setModel( m_core->bootLoaderModel() );
// When the chosen bootloader device changes, we update the choice in the PCM
connect( bcb, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, [ this ]( int newIndex ) {
2016-01-29 17:07:08 +01:00
QComboBox* bcb = qobject_cast< QComboBox* >( sender() );
if ( bcb )
2016-01-29 16:49:21 +01:00
{
2016-01-29 17:07:08 +01:00
QVariant var = bcb->itemData( newIndex, BootLoaderModel::BootLoaderPathRole );
if ( !var.isValid() )
2020-08-22 01:19:58 +02:00
{
2016-01-29 17:07:08 +01:00
return;
2020-08-22 01:19:58 +02:00
}
2016-01-29 17:07:08 +01:00
m_core->setBootLoaderInstallPath( var.toString() );
2016-01-29 16:49:21 +01:00
}
2016-01-29 17:07:08 +01:00
} );
return bcb;
}
static inline void
2020-08-22 01:19:58 +02:00
force_uncheck( QButtonGroup* grp, PrettyRadioButton* button )
{
button->hide();
grp->setExclusive( false );
button->setChecked( false );
grp->setExclusive( true );
}
static inline QDebug&
2020-08-22 01:19:58 +02:00
operator<<( QDebug& s, PartitionIterator& it )
{
s << ( ( *it ) ? ( *it )->deviceNode() : QString( "<null device>" ) );
return s;
}
/**
* @brief ChoicePage::setupActions happens every time a new Device* is selected in the
* device picker. Sets up the text and visibility of the partitioning actions based
* on the currently selected Device*, bootloader and os-prober output.
* @param currentDevice
*/
void
ChoicePage::setupActions()
{
Device* currentDevice = selectedDevice();
2020-08-22 01:19:58 +02:00
OsproberEntryList osproberEntriesForCurrentDevice = getOsproberEntriesForDevice( currentDevice );
2020-08-22 01:19:58 +02:00
cDebug() << "Setting up actions for" << currentDevice->deviceNode() << "with"
<< osproberEntriesForCurrentDevice.count() << "entries.";
2015-11-27 17:25:47 +01:00
if ( currentDevice->partitionTable() )
2020-08-22 01:19:58 +02:00
{
2015-11-27 17:25:47 +01:00
m_deviceInfoWidget->setPartitionTableType( currentDevice->partitionTable()->type() );
2020-08-22 01:19:58 +02:00
}
2015-11-27 17:25:47 +01:00
else
2020-08-22 01:19:58 +02:00
{
2015-11-27 17:25:47 +01:00
m_deviceInfoWidget->setPartitionTableType( PartitionTable::unknownTableType );
2020-08-22 01:19:58 +02:00
}
2015-11-27 17:25:47 +01:00
if ( m_allowManualPartitioning )
2020-08-22 01:19:58 +02:00
{
m_somethingElseButton->show();
2020-08-22 01:19:58 +02:00
}
else
2020-08-22 01:19:58 +02:00
{
force_uncheck( m_grp, m_somethingElseButton );
2020-08-22 01:19:58 +02:00
}
2016-02-12 16:48:29 +01:00
bool atLeastOneCanBeResized = false;
bool atLeastOneCanBeReplaced = false;
bool atLeastOneIsMounted = false; // Suppress 'erase' if so
bool isInactiveRAID = false;
#ifdef WITH_KPMCORE4API
2020-08-22 01:19:58 +02:00
if ( currentDevice->type() == Device::Type::SoftwareRAID_Device
&& static_cast< SoftwareRAID* >( currentDevice )->status() == SoftwareRAID::Status::Inactive )
{
cDebug() << Logger::SubEntry << "part of an inactive RAID device";
isInactiveRAID = true;
}
#endif
2016-02-12 16:48:29 +01:00
2020-08-22 01:19:58 +02:00
for ( auto it = PartitionIterator::begin( currentDevice ); it != PartitionIterator::end( currentDevice ); ++it )
2016-02-12 16:48:29 +01:00
{
if ( PartUtils::canBeResized( *it ) )
{
cDebug() << Logger::SubEntry << "contains resizable" << it;
2016-02-12 16:48:29 +01:00
atLeastOneCanBeResized = true;
}
if ( PartUtils::canBeReplaced( *it ) )
{
cDebug() << Logger::SubEntry << "contains replaceable" << it;
atLeastOneCanBeReplaced = true;
}
2020-08-22 01:19:58 +02:00
if ( ( *it )->isMounted() )
{
atLeastOneIsMounted = true;
}
}
if ( osproberEntriesForCurrentDevice.count() == 0 )
{
CALAMARES_RETRANSLATE(
cDebug() << "Setting texts for 0 osprober entries";
m_messageLabel->setText( tr( "This storage device does not seem to have an operating system on it. "
2015-12-03 14:39:23 +01:00
"What would you like to do?<br/>"
"You will be able to review and confirm your choices "
"before any change is made to the storage device." ) );
2015-12-15 17:21:45 +01:00
m_eraseButton->setText( tr( "<strong>Erase disk</strong><br/>"
2015-12-21 11:28:11 +01:00
"This will <font color=\"red\">delete</font> all data "
"currently present on the selected storage device." ) );
m_alongsideButton->setText( tr( "<strong>Install alongside</strong><br/>"
"The installer will shrink a partition to make room for %1." )
2020-08-22 01:19:58 +02:00
.arg( Calamares::Branding::instance()->shortVersionedName() ) );
m_replaceButton->setText( tr( "<strong>Replace a partition</strong><br/>"
"Replaces a partition with %1." )
2020-08-22 01:19:58 +02:00
.arg( Calamares::Branding::instance()->shortVersionedName() ) ); )
m_replaceButton->hide();
m_alongsideButton->hide();
m_grp->setExclusive( false );
m_replaceButton->setChecked( false );
m_alongsideButton->setChecked( false );
m_grp->setExclusive( true );
}
else if ( osproberEntriesForCurrentDevice.count() == 1 )
{
QString osName = osproberEntriesForCurrentDevice.first().prettyName;
if ( !osName.isEmpty() )
{
CALAMARES_RETRANSLATE(
cDebug() << "Setting texts for 1 non-empty osprober entry";
m_messageLabel->setText( tr( "This storage device has %1 on it. "
2015-12-04 16:23:59 +01:00
"What would you like to do?<br/>"
"You will be able to review and confirm your choices "
"before any change is made to the storage device." )
2020-08-22 01:19:58 +02:00
.arg( osName ) );
2015-12-15 17:28:21 +01:00
m_alongsideButton->setText( tr( "<strong>Install alongside</strong><br/>"
2015-12-21 11:24:06 +01:00
"The installer will shrink a partition to make room for %1." )
2020-08-22 01:19:58 +02:00
.arg( Calamares::Branding::instance()->shortVersionedName() ) );
2015-12-15 17:21:45 +01:00
m_eraseButton->setText( tr( "<strong>Erase disk</strong><br/>"
2015-12-21 11:28:11 +01:00
"This will <font color=\"red\">delete</font> all data "
"currently present on the selected storage device." ) );
2015-12-15 17:23:13 +01:00
m_replaceButton->setText( tr( "<strong>Replace a partition</strong><br/>"
2015-12-21 11:26:12 +01:00
"Replaces a partition with %1." )
2020-08-22 01:19:58 +02:00
.arg( Calamares::Branding::instance()->shortVersionedName() ) ); )
}
else
{
CALAMARES_RETRANSLATE(
cDebug() << "Setting texts for 1 empty osprober entry";
m_messageLabel->setText( tr( "This storage device already has an operating system on it. "
2015-12-04 16:23:59 +01:00
"What would you like to do?<br/>"
"You will be able to review and confirm your choices "
"before any change is made to the storage device." ) );
2015-12-15 17:28:21 +01:00
m_alongsideButton->setText( tr( "<strong>Install alongside</strong><br/>"
2015-12-21 11:24:06 +01:00
"The installer will shrink a partition to make room for %1." )
2020-08-22 01:19:58 +02:00
.arg( Calamares::Branding::instance()->shortVersionedName() ) );
2015-12-15 17:21:45 +01:00
m_eraseButton->setText( tr( "<strong>Erase disk</strong><br/>"
2015-12-21 11:28:11 +01:00
"This will <font color=\"red\">delete</font> all data "
"currently present on the selected storage device." ) );
2015-12-15 17:23:13 +01:00
m_replaceButton->setText( tr( "<strong>Replace a partition</strong><br/>"
2015-12-21 11:26:12 +01:00
"Replaces a partition with %1." )
2020-08-22 01:19:58 +02:00
.arg( Calamares::Branding::instance()->shortVersionedName() ) ); )
}
}
else
{
// osproberEntriesForCurrentDevice has at least 2 items.
CALAMARES_RETRANSLATE(
cDebug() << "Setting texts for >= 2 osprober entries";
m_messageLabel->setText( tr( "This storage device has multiple operating systems on it. "
2015-12-04 16:23:59 +01:00
"What would you like to do?<br/>"
"You will be able to review and confirm your choices "
"before any change is made to the storage device." ) );
2015-12-15 17:28:21 +01:00
m_alongsideButton->setText( tr( "<strong>Install alongside</strong><br/>"
2015-12-21 11:24:06 +01:00
"The installer will shrink a partition to make room for %1." )
2020-08-22 01:19:58 +02:00
.arg( Calamares::Branding::instance()->shortVersionedName() ) );
2015-12-15 17:21:45 +01:00
m_eraseButton->setText( tr( "<strong>Erase disk</strong><br/>"
2015-12-21 11:28:11 +01:00
"This will <font color=\"red\">delete</font> all data "
"currently present on the selected storage device." ) );
2015-12-15 17:23:13 +01:00
m_replaceButton->setText( tr( "<strong>Replace a partition</strong><br/>"
2015-12-21 11:26:12 +01:00
"Replaces a partition with %1." )
2020-08-22 01:19:58 +02:00
.arg( Calamares::Branding::instance()->shortVersionedName() ) ); )
}
#ifdef DEBUG_PARTITION_UNSAFE
#ifdef DEBUG_PARTITION_LAME
// If things can't be broken, allow all the buttons
atLeastOneCanBeReplaced = true;
atLeastOneCanBeResized = true;
atLeastOneIsMounted = false;
isInactiveRAID = false;
#endif
#endif
if ( atLeastOneCanBeReplaced )
2020-08-22 01:19:58 +02:00
{
m_replaceButton->show();
2020-08-22 01:19:58 +02:00
}
else
{
cDebug() << "Replace button suppressed because none can be replaced.";
force_uncheck( m_grp, m_replaceButton );
}
if ( atLeastOneCanBeResized )
2020-08-22 01:19:58 +02:00
{
m_alongsideButton->show();
2020-08-22 01:19:58 +02:00
}
else
{
cDebug() << "Alongside button suppressed because none can be resized.";
force_uncheck( m_grp, m_alongsideButton );
}
if ( !atLeastOneIsMounted && !isInactiveRAID )
2020-08-22 01:19:58 +02:00
{
m_eraseButton->show(); // None mounted
2020-08-22 01:19:58 +02:00
}
else
{
cDebug() << "Erase button suppressed"
2020-08-22 01:19:58 +02:00
<< "mount?" << atLeastOneIsMounted << "raid?" << isInactiveRAID;
force_uncheck( m_grp, m_eraseButton );
}
bool isEfi = PartUtils::isEfiSystem();
bool efiSystemPartitionFound = !m_core->efiSystemPartitions().isEmpty();
if ( isEfi && !efiSystemPartitionFound )
{
cWarning() << "System is EFI but there's no EFI system partition, "
2020-08-22 01:19:58 +02:00
"DISABLING alongside and replace features.";
m_alongsideButton->hide();
m_replaceButton->hide();
}
}
OsproberEntryList
ChoicePage::getOsproberEntriesForDevice( Device* device ) const
{
OsproberEntryList eList;
for ( const OsproberEntry& entry : m_core->osproberEntries() )
{
if ( entry.path.startsWith( device->deviceNode() ) )
2020-08-22 01:19:58 +02:00
{
eList.append( entry );
2020-08-22 01:19:58 +02:00
}
}
return eList;
}
bool
2015-04-15 12:20:01 +02:00
ChoicePage::isNextEnabled() const
{
return m_nextEnabled;
}
ChoicePage::InstallChoice
2015-04-15 12:20:01 +02:00
ChoicePage::currentChoice() const
{
return m_choice;
}
bool
ChoicePage::calculateNextEnabled() const
{
bool enabled = false;
auto sm_p = m_beforePartitionBarsView ? m_beforePartitionBarsView->selectionModel() : nullptr;
switch ( m_choice )
{
case InstallChoice::NoChoice:
cDebug() << "No partitioning choice";
return false;
case InstallChoice::Replace:
case InstallChoice::Alongside:
if ( !( sm_p && sm_p->currentIndex().isValid() ) )
{
cDebug() << "No partition selected";
return false;
}
enabled = true;
break;
case InstallChoice::Erase:
case InstallChoice::Manual:
enabled = true;
}
2020-08-22 01:19:58 +02:00
if ( !enabled )
{
cDebug() << "No valid choice made";
return false;
}
if ( m_isEfi && ( m_choice == InstallChoice::Alongside || m_choice == InstallChoice::Replace ) )
{
if ( m_core->efiSystemPartitions().count() == 0 )
{
cDebug() << "No EFI partition for alongside or replace";
return false;
}
}
if ( m_choice != InstallChoice::Manual && m_encryptWidget->isVisible() )
{
switch ( m_encryptWidget->state() )
{
2020-08-22 01:19:58 +02:00
case EncryptWidget::Encryption::Unconfirmed:
cDebug() << "No passphrase provided";
return false;
case EncryptWidget::Encryption::Disabled:
case EncryptWidget::Encryption::Confirmed:
// Checkbox not checked, **or** passphrases match
break;
}
}
return true;
}
void
ChoicePage::updateNextEnabled()
{
bool enabled = calculateNextEnabled();
if ( enabled != m_nextEnabled )
{
m_nextEnabled = enabled;
emit nextStatusChanged( enabled );
}
}
void
2020-08-22 01:19:58 +02:00
ChoicePage::updateSwapChoicesTr( QComboBox* box )
{
if ( !box )
2020-08-22 01:19:58 +02:00
{
return;
2020-08-22 01:19:58 +02:00
}
2020-08-22 01:19:58 +02:00
static_assert( SwapChoice::NoSwap == 0, "Enum values out-of-sync" );
for ( int index = 0; index < box->count(); ++index )
{
bool ok = false;
int value = 0;
switch ( value = box->itemData( index ).toInt( &ok ) )
{
2020-08-22 01:19:58 +02:00
// case 0:
case SwapChoice::NoSwap:
// toInt() returns 0 on failure, so check for ok
if ( ok ) // It was explicitly set to 0
{
box->setItemText( index, tr( "No Swap" ) );
}
else
{
cWarning() << "Box item" << index << box->itemText( index ) << "has non-integer role.";
}
break;
case SwapChoice::ReuseSwap:
box->setItemText( index, tr( "Reuse Swap" ) );
break;
case SwapChoice::SmallSwap:
box->setItemText( index, tr( "Swap (no Hibernate)" ) );
break;
case SwapChoice::FullSwap:
box->setItemText( index, tr( "Swap (with Hibernate)" ) );
break;
case SwapChoice::SwapFile:
box->setItemText( index, tr( "Swap to file" ) );
break;
default:
cWarning() << "Box item" << index << box->itemText( index ) << "has role" << value;
}
}
}
int
ChoicePage::lastSelectedDeviceIndex()
{
return m_lastSelectedDeviceIndex;
}
void
ChoicePage::setLastSelectedDeviceIndex( int index )
{
m_lastSelectedDeviceIndex = index;
m_drivesCombo->setCurrentIndex( m_lastSelectedDeviceIndex );
}