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

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

sync with github

File size: 16.5 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014, Aurélien Gâteau <agateau@kde.org>
4 *   Copyright 2015-2016, Teo Mrnjavac <teo@kde.org>
5 *
6 *   Calamares is free software: you can redistribute it and/or modify
7 *   it under the terms of the GNU General Public License as published by
8 *   the Free Software Foundation, either version 3 of the License, or
9 *   (at your option) any later version.
10 *
11 *   Calamares is distributed in the hope that it will be useful,
12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 *   GNU General Public License for more details.
15 *
16 *   You should have received a copy of the GNU General Public License
17 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "PartitionPage.h"
21
22// Local
23#include "core/BootLoaderModel.h"
24#include "core/DeviceModel.h"
25#include "core/PartitionCoreModule.h"
26#include "core/PartitionInfo.h"
27#include "core/PartitionModel.h"
28#include "core/PartUtils.h"
29#include "core/KPMHelpers.h"
30#include "gui/CreatePartitionDialog.h"
31#include "gui/EditExistingPartitionDialog.h"
32#include "gui/ScanningDialog.h"
33
34#include "ui_PartitionPage.h"
35#include "ui_CreatePartitionTableDialog.h"
36
37#include "utils/Logger.h"
38#include "utils/Retranslator.h"
39#include "Branding.h"
40#include "JobQueue.h"
41#include "GlobalStorage.h"
42
43// KPMcore
44#include <kpmcore/core/device.h>
45#include <kpmcore/core/partition.h>
46
47// Qt
48#include <QDebug>
49#include <QHeaderView>
50#include <QItemSelectionModel>
51#include <QMessageBox>
52#include <QPointer>
53#include <QDir>
54#include <QFutureWatcher>
55#include <QtConcurrent/QtConcurrent>
56
57PartitionPage::PartitionPage( PartitionCoreModule* core, QWidget* parent )
58    : QWidget( parent )
59    , m_ui( new Ui_PartitionPage )
60    , m_core( core )
61    , m_lastSelectedBootLoaderIndex(-1)
62    , m_isEfi( false )
63{
64    m_isEfi = PartUtils::isEfiSystem();
65
66    m_ui->setupUi( this );
67    m_ui->partitionLabelsView->setVisible(
68            Calamares::JobQueue::instance()->globalStorage()->
69                    value( "alwaysShowPartitionLabels" ).toBool() );
70    m_ui->deviceComboBox->setModel( m_core->deviceModel() );
71    m_ui->bootLoaderComboBox->setModel( m_core->bootLoaderModel() );
72    PartitionBarsView::NestedPartitionsMode mode = Calamares::JobQueue::instance()->globalStorage()->
73                                                   value( "drawNestedPartitions" ).toBool() ?
74                                                       PartitionBarsView::DrawNestedPartitions :
75                                                       PartitionBarsView::NoNestedPartitions;
76    m_ui->partitionBarsView->setNestedPartitionsMode( mode );
77    updateButtons();
78    updateBootLoaderInstallPath();
79
80    updateFromCurrentDevice();
81
82    connect( m_ui->deviceComboBox, &QComboBox::currentTextChanged,
83             [ this ]( const QString& /* text */ )
84    {
85        updateFromCurrentDevice();
86    } );
87    connect( m_ui->bootLoaderComboBox, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::activated),
88                [ this ]( const QString& /* text */ )
89    {
90        m_lastSelectedBootLoaderIndex = m_ui->bootLoaderComboBox->currentIndex();
91    } );
92
93    connect( m_ui->bootLoaderComboBox, &QComboBox::currentTextChanged,
94             [ this ]( const QString& /* text */ )
95    {
96        updateBootLoaderInstallPath();
97    } );
98
99    connect( m_core, &PartitionCoreModule::isDirtyChanged, m_ui->revertButton, &QWidget::setEnabled );
100
101    connect( m_ui->partitionTreeView, &QAbstractItemView::doubleClicked, this, &PartitionPage::onPartitionViewActivated );
102    connect( m_ui->revertButton, &QAbstractButton::clicked, this, &PartitionPage::onRevertClicked );
103    connect( m_ui->newPartitionTableButton, &QAbstractButton::clicked, this, &PartitionPage::onNewPartitionTableClicked );
104    connect( m_ui->createButton, &QAbstractButton::clicked, this, &PartitionPage::onCreateClicked );
105    connect( m_ui->editButton, &QAbstractButton::clicked, this, &PartitionPage::onEditClicked );
106    connect( m_ui->deleteButton, &QAbstractButton::clicked, this, &PartitionPage::onDeleteClicked );
107
108    if ( m_isEfi ) {
109        m_ui->bootLoaderComboBox->hide();
110        m_ui->label_3->hide();
111    }
112
113    CALAMARES_RETRANSLATE( m_ui->retranslateUi( this ); )
114}
115
116PartitionPage::~PartitionPage()
117{
118}
119
120void
121PartitionPage::updateButtons()
122{
123    bool create = false, createTable = false, edit = false, del = false;
124
125    QModelIndex index = m_ui->partitionTreeView->currentIndex();
126    if ( index.isValid() )
127    {
128        const PartitionModel* model = static_cast< const PartitionModel* >( index.model() );
129        Q_ASSERT( model );
130        Partition* partition = model->partitionForIndex( index );
131        Q_ASSERT( partition );
132        bool isFree = KPMHelpers::isPartitionFreeSpace( partition );
133        bool isExtended = partition->roles().has( PartitionRole::Extended );
134
135        create = isFree;
136        // Keep it simple for now: do not support editing extended partitions as
137        // it does not work with our current edit implementation which is
138        // actually remove + add. This would not work with extended partitions
139        // because they need to be created *before* creating logical partitions
140        // inside them, so an edit must be applied without altering the job
141        // order.
142        edit = !isFree && !isExtended;
143        del = !isFree;
144    }
145
146    if ( m_ui->deviceComboBox->currentIndex() >= 0 )
147    {
148        QModelIndex deviceIndex = m_core->deviceModel()->index( m_ui->deviceComboBox->currentIndex(), 0 );
149        if ( m_core->deviceModel()->deviceForIndex( deviceIndex )->type() != Device::Type::LVM_Device )
150            createTable = true;
151    }
152
153    m_ui->createButton->setEnabled( create );
154    m_ui->editButton->setEnabled( edit );
155    m_ui->deleteButton->setEnabled( del );
156    m_ui->newPartitionTableButton->setEnabled( createTable );
157}
158
159void
160PartitionPage::onNewPartitionTableClicked()
161{
162    QModelIndex index = m_core->deviceModel()->index( m_ui->deviceComboBox->currentIndex(), 0 );
163    Q_ASSERT( index.isValid() );
164    Device* device = m_core->deviceModel()->deviceForIndex( index );
165
166    QPointer<QDialog> dlg = new QDialog( this );
167    Ui_CreatePartitionTableDialog ui;
168    ui.setupUi( dlg.data() );
169    QString areYouSure = tr( "Are you sure you want to create a new partition table on %1?" ).arg( device->name() );
170    ui.areYouSureLabel->setText( areYouSure );
171    if ( dlg->exec() == QDialog::Accepted )
172    {
173        PartitionTable::TableType type = ui.mbrRadioButton->isChecked() ? PartitionTable::msdos : PartitionTable::gpt;
174        m_core->createPartitionTable( device, type );
175    }
176    delete dlg;
177    // PartionModelReset isn't emmited after createPartitionTable, so we have to manually update
178    // the bootLoader index after the reset.
179    updateBootLoaderIndex();
180}
181
182bool
183PartitionPage::checkCanCreate( Device* device )
184{
185    auto table = device->partitionTable();
186
187    if ( table->type() == PartitionTable::msdos ||table->type() == PartitionTable::msdos_sectorbased )
188    {
189        cDebug() << "Checking MSDOS partition" << table->numPrimaries() << "primaries, max" << table->maxPrimaries();
190
191        if ( ( table->numPrimaries() >= table->maxPrimaries() ) && !table->hasExtended() )
192        {
193            QMessageBox::warning( this, tr( "Can not create new partition" ),
194                tr( "The partition table on %1 already has %2 primary partitions, and no more can be added. "
195                    "Please remove one primary partition and add an extended partition, instead." ).arg( device->name() ).arg( table->numPrimaries() )
196            );
197            return false;
198        }
199        return true;
200    }
201    else
202        return true;  // GPT is fine
203}
204
205void
206PartitionPage::onCreateClicked()
207{
208    QModelIndex index = m_ui->partitionTreeView->currentIndex();
209    Q_ASSERT( index.isValid() );
210
211    const PartitionModel* model = static_cast< const PartitionModel* >( index.model() );
212    Partition* partition = model->partitionForIndex( index );
213    Q_ASSERT( partition );
214
215    if ( !checkCanCreate( model->device() ) )
216        return;
217
218    QPointer< CreatePartitionDialog > dlg = new CreatePartitionDialog( model->device(),
219                                                                       partition->parent(),
220                                                                       nullptr,
221                                                                       getCurrentUsedMountpoints(),
222                                                                       this );
223    dlg->initFromFreeSpace( partition );
224    if ( dlg->exec() == QDialog::Accepted )
225    {
226        Partition* newPart = dlg->createPartition();
227        m_core->createPartition( model->device(), newPart, dlg->newFlags() );
228    }
229    delete dlg;
230}
231
232void
233PartitionPage::onEditClicked()
234{
235    QModelIndex index = m_ui->partitionTreeView->currentIndex();
236    Q_ASSERT( index.isValid() );
237
238    const PartitionModel* model = static_cast< const PartitionModel* >( index.model() );
239    Partition* partition = model->partitionForIndex( index );
240    Q_ASSERT( partition );
241
242    if ( KPMHelpers::isPartitionNew( partition ) )
243        updatePartitionToCreate( model->device(), partition );
244    else
245        editExistingPartition( model->device(), partition );
246
247}
248
249void
250PartitionPage::onDeleteClicked()
251{
252    QModelIndex index = m_ui->partitionTreeView->currentIndex();
253    Q_ASSERT( index.isValid() );
254
255    const PartitionModel* model = static_cast< const PartitionModel* >( index.model() );
256    Partition* partition = model->partitionForIndex( index );
257    Q_ASSERT( partition );
258
259    m_core->deletePartition( model->device(), partition );
260}
261
262
263void
264PartitionPage::onRevertClicked()
265{
266    ScanningDialog::run(
267        QtConcurrent::run( [ this ]
268        {
269            QMutexLocker locker( &m_revertMutex );
270
271            int oldIndex = m_ui->deviceComboBox->currentIndex();
272            m_core->revertAllDevices();
273            m_ui->deviceComboBox->setCurrentIndex( oldIndex );
274            updateFromCurrentDevice();
275        } ),
276        [ this ]{
277            m_lastSelectedBootLoaderIndex = -1;
278            if( m_ui->bootLoaderComboBox->currentIndex() < 0 ) {
279                m_ui->bootLoaderComboBox->setCurrentIndex( 0 );
280            }
281        },
282        this );
283}
284
285void
286PartitionPage::onPartitionViewActivated()
287{
288    QModelIndex index = m_ui->partitionTreeView->currentIndex();
289    if ( !index.isValid() )
290        return;
291
292    const PartitionModel* model = static_cast< const PartitionModel* >( index.model() );
293    Q_ASSERT( model );
294    Partition* partition = model->partitionForIndex( index );
295    Q_ASSERT( partition );
296
297    // Use the buttons to trigger the actions so that they do nothing if they
298    // are disabled. Alternatively, the code could use QAction to centralize,
299    // but I don't expect there will be other occurences of triggering the same
300    // action from multiple UI elements in this page, so it does not feel worth
301    // the price.
302    if ( KPMHelpers::isPartitionFreeSpace( partition ) )
303        m_ui->createButton->click();
304    else
305        m_ui->editButton->click();
306}
307
308void
309PartitionPage::updatePartitionToCreate( Device* device, Partition* partition )
310{
311    QStringList mountPoints = getCurrentUsedMountpoints();
312    mountPoints.removeOne( PartitionInfo::mountPoint( partition ) );
313
314    QPointer< CreatePartitionDialog > dlg = new CreatePartitionDialog( device,
315                                                                       partition->parent(),
316                                                                       partition,
317                                                                       mountPoints,
318                                                                       this );
319    dlg->initFromPartitionToCreate( partition );
320    if ( dlg->exec() == QDialog::Accepted )
321    {
322        Partition* newPartition = dlg->createPartition();
323        m_core->deletePartition( device, partition );
324        m_core->createPartition( device, newPartition, dlg->newFlags() );
325    }
326    delete dlg;
327}
328
329void
330PartitionPage::editExistingPartition( Device* device, Partition* partition )
331{
332    QStringList mountPoints = getCurrentUsedMountpoints();
333    mountPoints.removeOne( PartitionInfo::mountPoint( partition ) );
334
335    QPointer<EditExistingPartitionDialog> dlg = new EditExistingPartitionDialog( device, partition, mountPoints, this );
336    if ( dlg->exec() == QDialog::Accepted )
337        dlg->applyChanges( m_core );
338    delete dlg;
339}
340
341void
342PartitionPage::updateBootLoaderInstallPath()
343{
344    if ( m_isEfi || !m_ui->bootLoaderComboBox->isVisible() )
345        return;
346
347    QVariant var = m_ui->bootLoaderComboBox->currentData( BootLoaderModel::BootLoaderPathRole );
348    if ( !var.isValid() )
349        return;
350    qDebug() << "PartitionPage::updateBootLoaderInstallPath" << var.toString();
351    m_core->setBootLoaderInstallPath( var.toString() );
352}
353
354void
355PartitionPage::updateFromCurrentDevice()
356{
357    QModelIndex index = m_core->deviceModel()->index( m_ui->deviceComboBox->currentIndex(), 0 );
358    if ( !index.isValid() )
359        return;
360
361    Device* device = m_core->deviceModel()->deviceForIndex( index );
362
363    QAbstractItemModel* oldModel = m_ui->partitionTreeView->model();
364    if ( oldModel )
365        disconnect( oldModel, 0, this, 0 );
366
367    PartitionModel* model = m_core->partitionModelForDevice( device );
368    m_ui->partitionBarsView->setModel( model );
369    m_ui->partitionLabelsView->setModel( model );
370    m_ui->partitionTreeView->setModel( model );
371    m_ui->partitionTreeView->expandAll();
372
373    // Make all views use the same selection model.
374    if ( m_ui->partitionBarsView->selectionModel() !=
375         m_ui->partitionTreeView->selectionModel() ||
376         m_ui->partitionBarsView->selectionModel() !=
377         m_ui->partitionLabelsView->selectionModel() )
378    {
379        // Tree view
380        QItemSelectionModel* selectionModel = m_ui->partitionTreeView->selectionModel();
381        m_ui->partitionTreeView->setSelectionModel( m_ui->partitionBarsView->selectionModel() );
382        selectionModel->deleteLater();
383
384        // Labels view
385        selectionModel = m_ui->partitionLabelsView->selectionModel();
386        m_ui->partitionLabelsView->setSelectionModel( m_ui->partitionBarsView->selectionModel() );
387        selectionModel->deleteLater();
388    }
389
390    // This is necessary because even with the same selection model it might happen that
391    // a !=0 column is selected in the tree view, which for some reason doesn't trigger a
392    // timely repaint in the bars view.
393    connect( m_ui->partitionBarsView->selectionModel(), &QItemSelectionModel::currentChanged,
394             this, [=]
395    {
396        QModelIndex selectedIndex = m_ui->partitionBarsView->selectionModel()->currentIndex();
397        selectedIndex = selectedIndex.sibling( selectedIndex.row(), 0 );
398        m_ui->partitionBarsView->setCurrentIndex( selectedIndex );
399        m_ui->partitionLabelsView->setCurrentIndex( selectedIndex );
400    }, Qt::UniqueConnection );
401
402    // Must be done here because we need to have a model set to define
403    // individual column resize mode
404    QHeaderView* header = m_ui->partitionTreeView->header();
405    header->setSectionResizeMode( QHeaderView::ResizeToContents );
406    header->setSectionResizeMode( 0, QHeaderView::Stretch );
407
408    updateButtons();
409    // Establish connection here because selection model is destroyed when
410    // model changes
411    connect( m_ui->partitionTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
412             [ this ]( const QModelIndex&, const QModelIndex& )
413    {
414        updateButtons();
415    } );
416    connect( model, &QAbstractItemModel::modelReset, this, &PartitionPage::onPartitionModelReset );
417}
418
419void
420PartitionPage::onPartitionModelReset()
421{
422    m_ui->partitionTreeView->expandAll();
423    updateButtons();
424    updateBootLoaderIndex();
425}
426
427void
428PartitionPage::updateBootLoaderIndex()
429{
430    // set bootloader back to user selected index
431    if ( m_lastSelectedBootLoaderIndex >= 0 && m_ui->bootLoaderComboBox->count() ) {
432        m_ui->bootLoaderComboBox->setCurrentIndex( m_lastSelectedBootLoaderIndex );
433    }
434}
435
436QStringList
437PartitionPage::getCurrentUsedMountpoints()
438{
439    QModelIndex index = m_core->deviceModel()->index(
440                            m_ui->deviceComboBox->currentIndex(), 0 );
441    if ( !index.isValid() )
442        return QStringList();
443
444    Device* device = m_core->deviceModel()->deviceForIndex( index );
445    QStringList mountPoints;
446
447    for ( Partition* partition : device->partitionTable()->children() )
448    {
449        const QString& mountPoint = PartitionInfo::mountPoint( partition );
450        if ( !mountPoint.isEmpty() )
451            mountPoints << mountPoint;
452    }
453
454    return mountPoints;
455}
Note: See TracBrowser for help on using the repository browser.