From a98a4bb61461bcc760de8d8d6a97cd828e37a688 Mon Sep 17 00:00:00 2001
From: Teo Mrnjavac <teo@kde.org>
Date: Wed, 3 Sep 2014 18:09:37 +0200
Subject: [PATCH] Add ChoicePage to partition module + OS detection with
 os-prober.

---
 src/modules/partition/CMakeLists.txt          |   3 +
 src/modules/partition/gui/ChoicePage.cpp      | 246 ++++++++++++++++++
 src/modules/partition/gui/ChoicePage.h        |  65 +++++
 .../partition/gui/PartitionViewStep.cpp       |  95 ++++++-
 src/modules/partition/gui/PartitionViewStep.h |   7 +-
 5 files changed, 409 insertions(+), 7 deletions(-)
 create mode 100644 src/modules/partition/gui/ChoicePage.cpp
 create mode 100644 src/modules/partition/gui/ChoicePage.h

diff --git a/src/modules/partition/CMakeLists.txt b/src/modules/partition/CMakeLists.txt
index e980fb5780..83f360992e 100644
--- a/src/modules/partition/CMakeLists.txt
+++ b/src/modules/partition/CMakeLists.txt
@@ -17,6 +17,7 @@ add_definitions( -DCALAMARES )
 add_subdirectory( tests )
 
 include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui )
+
 calamares_add_plugin( partition
     TYPE viewmodule
     EXPORT_MACRO PLUGINDLLEXPORT_PRO
@@ -29,12 +30,14 @@ calamares_add_plugin( partition
         core/PartitionIterator.cpp
         core/PartitionModel.cpp
         core/PMUtils.cpp
+        gui/ChoicePage.cpp
         gui/CreatePartitionDialog.cpp
         gui/EditExistingPartitionDialog.cpp
         gui/PartitionPage.cpp
         gui/PartitionPreview.cpp
         gui/PartitionSizeController.cpp
         gui/PartitionViewStep.cpp
+        gui/PrettyRadioButton.cpp
         jobs/CheckFileSystemJob.cpp
         jobs/CreatePartitionJob.cpp
         jobs/CreatePartitionTableJob.cpp
diff --git a/src/modules/partition/gui/ChoicePage.cpp b/src/modules/partition/gui/ChoicePage.cpp
new file mode 100644
index 0000000000..eca3b17cce
--- /dev/null
+++ b/src/modules/partition/gui/ChoicePage.cpp
@@ -0,0 +1,246 @@
+/* === This file is part of Calamares - <http://github.com/calamares> ===
+ *
+ *   Copyright 2014, Teo Mrnjavac <teo@kde.org>
+ *
+ *   Calamares is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Calamares is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ChoicePage.h"
+
+#include "PrettyRadioButton.h"
+
+#include "utils/CalamaresUtilsGui.h"
+
+#include <QBoxLayout>
+#include <QButtonGroup>
+#include <QLabel>
+
+ChoicePage::ChoicePage( QWidget* parent )
+    : QWidget( parent )
+    , m_choice( NoChoice )
+    , m_nextEnabled( false )
+{
+    QBoxLayout* mainLayout = new QVBoxLayout;
+    setLayout( mainLayout );
+
+    m_messageLabel = new QLabel;
+    m_messageLabel->setWordWrap( true );
+
+    m_itemsLayout = new QVBoxLayout;
+    CalamaresUtils::unmarginLayout( m_itemsLayout );
+
+    mainLayout->addSpacing( CalamaresUtils::defaultFontHeight() );
+    mainLayout->addWidget( m_messageLabel );
+    mainLayout->addLayout( m_itemsLayout );
+    mainLayout->addStretch();
+}
+
+
+ChoicePage::~ChoicePage()
+{}
+
+
+void
+ChoicePage::init( PartitionCoreModule* core, const QStringList& osproberLines )
+{
+    m_core = core;
+
+    // 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?
+
+    QSize iconSize( CalamaresUtils::defaultIconSize().width() * 3,
+                    CalamaresUtils::defaultIconSize().height() * 3 );
+    QButtonGroup* grp = new QButtonGroup( this );
+
+    m_cleanOsproberLines.clear();
+    foreach ( const QString& line, osproberLines )
+    {
+        if ( !line.simplified().isEmpty() )
+            m_cleanOsproberLines.append( line );
+    }
+
+    PrettyRadioButton* alongsideButton = new PrettyRadioButton;
+    alongsideButton->setIconSize( iconSize );
+    alongsideButton->setIcon( CalamaresUtils::defaultPixmap( CalamaresUtils::Information,
+                                                             CalamaresUtils::Original,
+                                                             iconSize ) );
+    grp->addButton( alongsideButton->buttonWidget() );
+
+    PrettyRadioButton* eraseButton = new PrettyRadioButton;
+    eraseButton->setIconSize( iconSize );
+    eraseButton->setIcon( CalamaresUtils::defaultPixmap( CalamaresUtils::Magic,
+                                                         CalamaresUtils::Original,
+                                                         iconSize ) );
+    grp->addButton( eraseButton->buttonWidget() );
+
+    m_itemsLayout->addWidget( alongsideButton );
+    m_itemsLayout->addWidget( eraseButton );
+
+    if ( m_cleanOsproberLines.count() == 0 )
+    {
+        m_messageLabel->setText( tr( "This computer currently does not seem to have an operating system on it. "
+                                     "What would you like to do?" ) );
+
+        eraseButton->setText( tr( "<b>Erase disk and install %1</b><br/>"
+                                  "<font color=\"red\">Warning: </font>This will delete all of your programs, "
+                                  "documents, photos, music, and any other files." )
+                                .arg( "$RELEASE" ) );
+
+        alongsideButton->hide();
+    }
+    else if ( m_cleanOsproberLines.count() == 1 )
+    {
+        QStringList osLine = m_cleanOsproberLines.first().split( ':' );
+        QString osName;
+        if ( !osLine.value( 1 ).simplified().isEmpty() )
+            osName = osLine.value( 1 ).simplified();
+        else if ( !osLine.value( 2 ).simplified().isEmpty() )
+            osName = osLine.value( 2 ).simplified();
+
+        if ( !osName.isEmpty() )
+        {
+            m_messageLabel->setText( tr( "This computer currently has %1 on it. "
+                                         "What would you like to do?" )
+                                        .arg( osName ) );
+
+            alongsideButton->setText( tr( "<b>Install %2 alongside %1</b><br/>"
+                                          "Documents, music and other personal files will be kept. "
+                                          "You can choose which operating system you want each time the "
+                                          "computer starts up." )
+                                        .arg( osName )
+                                        .arg( "$RELEASE" ) );
+
+            eraseButton->setText( tr( "<b>Replace %1 with %2</b><br/>"
+                                      "<font color=\"red\">Warning: </font>This will erase the whole disk and "
+                                      "delete all of your %1 programs, "
+                                      "documents, photos, music, and any other files." )
+                                    .arg( osName )
+                                    .arg( "$RELEASE" ) );
+        }
+        else
+        {
+            m_messageLabel->setText( tr( "This computer already has an operating system on it. "
+                                         "What would you like to do?" ) );
+
+            alongsideButton->setText( tr( "<b>Install %1 alongside your current operating system</b><br/>"
+                                          "Documents, music and other personal files will be kept. "
+                                          "You can choose which operating system you want each time the "
+                                          "computer starts up." )
+                                        .arg( "$RELEASE" ) );
+
+            eraseButton->setText( tr( "<b>Erase disk and install %1</b><br/>"
+                                      "<font color=\"red\">Warning: </font>This will delete all of your Windows 7 programs, "
+                                      "documents, photos, music, and any other files." )
+                                    .arg( "$RELEASE" ) );
+        }
+    }
+    else
+    {
+        m_messageLabel->setText( tr( "This computer currently has multiple operating systems on it. "
+                                     "What would you like to do?" ) );
+
+        alongsideButton->setText( tr( "<b>Install %1 alongside your current operating systems</b><br/>"
+                                      "Documents, music and other personal files will be kept. "
+                                      "You can choose which operating system you want each time the "
+                                      "computer starts up." )
+                                    .arg( "$RELEASE" ) );
+
+        eraseButton->setText( tr( "<b>Erase disk and install %1</b><br/>"
+                                  "<font color=\"red\">Warning: </font>This will delete all of your Windows 7 programs, "
+                                  "documents, photos, music, and any other files." )
+                                .arg( "$RELEASE" ) );
+    }
+
+    m_itemsLayout->addStretch();
+
+    QFrame* hLine = new QFrame;
+    hLine->setFrameStyle( QFrame::HLine );
+    m_itemsLayout->addWidget( hLine );
+
+    m_itemsLayout->addStretch();
+
+    PrettyRadioButton* somethingElseButton = new PrettyRadioButton;
+    somethingElseButton->setText( tr( "<b>Something else</b><br/>"
+                                      "You can create or resize partitions yourself, or choose "
+                                      "multiple partitions for %1." )
+                                    .arg( "$RELEASE" ) );
+    somethingElseButton->setIconSize( iconSize );
+    somethingElseButton->setIcon( CalamaresUtils::defaultPixmap( CalamaresUtils::Partitions,
+                                                                 CalamaresUtils::Original,
+                                                                 iconSize ) );
+    m_itemsLayout->addWidget( somethingElseButton );
+    grp->addButton( somethingElseButton->buttonWidget() );
+
+
+    connect( alongsideButton->buttonWidget(), &QRadioButton::toggled,
+             this, [ this ]( bool checked )
+    {
+        if ( checked )
+            m_choice = Alongside;
+        setNextEnabled( true );
+    } );
+
+    connect( eraseButton->buttonWidget(), &QRadioButton::toggled,
+             this, [ this ]( bool checked )
+    {
+        if ( checked )
+            m_choice = Erase;
+        setNextEnabled( true );
+    } );
+
+    connect( somethingElseButton->buttonWidget(), &QRadioButton::toggled,
+             this, [ this ]( bool checked )
+    {
+        if ( checked )
+            m_choice = Manual;
+        setNextEnabled( true );
+    } );
+}
+
+
+bool
+ChoicePage::isNextEnabled()
+{
+    return m_nextEnabled;
+}
+
+
+ChoicePage::Choice
+ChoicePage::currentChoice()
+{
+    return m_choice;
+}
+
+
+void
+ChoicePage::setNextEnabled( bool enabled )
+{
+    if ( enabled == m_nextEnabled )
+        return;
+
+    m_nextEnabled = enabled;
+    emit nextStatusChanged( enabled );
+}
diff --git a/src/modules/partition/gui/ChoicePage.h b/src/modules/partition/gui/ChoicePage.h
new file mode 100644
index 0000000000..600f0d101e
--- /dev/null
+++ b/src/modules/partition/gui/ChoicePage.h
@@ -0,0 +1,65 @@
+/* === This file is part of Calamares - <http://github.com/calamares> ===
+ *
+ *   Copyright 2014, Teo Mrnjavac <teo@kde.org>
+ *
+ *   Calamares is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Calamares is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHOICEPAGE_H
+#define CHOICEPAGE_H
+
+#include <QWidget>
+
+class QBoxLayout;
+class QLabel;
+
+class PartitionCoreModule;
+
+class ChoicePage : public QWidget
+{
+    Q_OBJECT
+public:
+    enum Choice
+    {
+        NoChoice,
+        Alongside,
+        Erase,
+        Manual
+    };
+
+    explicit ChoicePage( QWidget* parent = nullptr );
+    virtual ~ChoicePage();
+
+    void init( PartitionCoreModule* core, const QStringList& osproberLines );
+
+    bool isNextEnabled();
+
+    Choice currentChoice();
+
+signals:
+    void nextStatusChanged( bool enabled );
+
+private:
+    void setNextEnabled( bool enabled );
+
+    bool m_nextEnabled;
+    PartitionCoreModule* m_core;
+    QBoxLayout* m_itemsLayout;
+    QLabel* m_messageLabel;
+
+    QStringList m_cleanOsproberLines;
+    Choice m_choice;
+};
+
+#endif // CHOICEPAGE_H
diff --git a/src/modules/partition/gui/PartitionViewStep.cpp b/src/modules/partition/gui/PartitionViewStep.cpp
index 3728735162..55cb4adbb4 100644
--- a/src/modules/partition/gui/PartitionViewStep.cpp
+++ b/src/modules/partition/gui/PartitionViewStep.cpp
@@ -21,20 +21,86 @@
 #include <core/DeviceModel.h>
 #include <core/PartitionCoreModule.h>
 #include <core/PartitionModel.h>
+#include <gui/ChoicePage.h>
 #include <gui/PartitionPage.h>
 #include <gui/PartitionPreview.h>
 
+#include "CalamaresVersion.h"
+#include "utils/CalamaresUtilsGui.h"
+#include "utils/Logger.h"
+#include "widgets/WaitingWidget.h"
+
 // Qt
+#include <QApplication>
 #include <QFormLayout>
 #include <QLabel>
+#include <QProcess>
+#include <QStackedWidget>
+#include <QTimer>
 
 PartitionViewStep::PartitionViewStep( QObject* parent )
     : Calamares::ViewStep( parent )
+    , m_widget( new QStackedWidget() )
     , m_core( new PartitionCoreModule( this ) )
-    , m_page( new PartitionPage( m_core ) )
+    , m_choicePage( new ChoicePage() )
+    , m_manualPartitionPage( new PartitionPage( m_core ) )
+{
+    m_widget->setContentsMargins( 0, 0, 0, 0 );
+
+    QWidget* waitingWidget = new WaitingWidget( tr( "Gathering system information..." ) );
+    m_widget->addWidget( waitingWidget );
+
+    QTimer* timer = new QTimer;
+    timer->setSingleShot( true );
+    connect( timer, &QTimer::timeout,
+             [=]()
+    {
+        QString osproberOutput;
+        QProcess osprober;
+        osprober.setProgram( "os-prober" );
+        osprober.setProcessChannelMode( QProcess::SeparateChannels );
+        osprober.start();
+        if ( !osprober.waitForStarted() )
+        {
+            cDebug() << "ERROR: os-prober cannot start.";
+        }
+        else if ( !osprober.waitForFinished( 60000 ) )
+        {
+            cDebug() << "ERROR: os-prober timed out.";
+        }
+        else
+        {
+            osproberOutput.append(
+                QString::fromLocal8Bit(
+                    osprober.readAllStandardOutput() ).trimmed() );
+        }
+
+        QStringList osproberLines = osproberOutput.split( '\n' );
+
+        m_choicePage->init( m_core, osproberLines );
+
+        m_widget->addWidget( m_choicePage );
+        m_widget->addWidget( m_manualPartitionPage );
+        m_widget->removeWidget( waitingWidget );
+        waitingWidget->deleteLater();
+
+        timer->deleteLater();
+    } );
+    timer->start( 0 );
+
+    connect( m_core, &PartitionCoreModule::hasRootMountPointChanged,
+             this, &PartitionViewStep::nextStatusChanged );
+    connect( m_choicePage, &ChoicePage::nextStatusChanged,
+             this, &PartitionViewStep::nextStatusChanged );
+}
+
+
+PartitionViewStep::~PartitionViewStep()
 {
-    connect( m_core, SIGNAL( hasRootMountPointChanged( bool ) ),
-             SIGNAL( nextStatusChanged( bool ) ) );
+    if ( m_choicePage && m_choicePage->parent() == nullptr )
+        m_choicePage->deleteLater();
+    if ( m_manualPartitionPage && m_manualPartitionPage->parent() == nullptr )
+        m_manualPartitionPage->deleteLater();
 }
 
 
@@ -48,7 +114,7 @@ PartitionViewStep::prettyName() const
 QWidget*
 PartitionViewStep::widget()
 {
-    return m_page;
+    return m_widget;
 }
 
 
@@ -79,21 +145,34 @@ PartitionViewStep::createSummaryWidget() const
     return widget;
 }
 
+
 void
 PartitionViewStep::next()
 {
-    emit done();
+    if ( m_choicePage == m_widget->currentWidget() )
+    {
+        m_widget->setCurrentWidget( m_manualPartitionPage );
+        cDebug() << "Choice applied: " << m_choicePage->currentChoice();
+    }
+    else
+        emit done();
 }
 
 
 void
 PartitionViewStep::back()
-{}
+{
+    if ( m_widget->currentWidget() != m_choicePage )
+        m_widget->setCurrentWidget( m_choicePage );
+}
 
 
 bool
 PartitionViewStep::isNextEnabled() const
 {
+    if ( m_choicePage && m_choicePage == m_widget->currentWidget() )
+        return m_choicePage->isNextEnabled();
+
     return m_core->hasRootMountPoint();
 }
 
@@ -101,6 +180,8 @@ PartitionViewStep::isNextEnabled() const
 bool
 PartitionViewStep::isAtBeginning() const
 {
+    if ( m_widget->currentWidget() == m_manualPartitionPage )
+        return false;
     return true;
 }
 
@@ -108,6 +189,8 @@ PartitionViewStep::isAtBeginning() const
 bool
 PartitionViewStep::isAtEnd() const
 {
+    if ( m_choicePage == m_widget->currentWidget() )
+        return false;
     return true;
 }
 
diff --git a/src/modules/partition/gui/PartitionViewStep.h b/src/modules/partition/gui/PartitionViewStep.h
index 9e1eae8085..768b98eab4 100644
--- a/src/modules/partition/gui/PartitionViewStep.h
+++ b/src/modules/partition/gui/PartitionViewStep.h
@@ -24,8 +24,10 @@
 #include "viewpages/ViewStep.h"
 #include "PluginDllMacro.h"
 
+class ChoicePage;
 class PartitionPage;
 class PartitionCoreModule;
+class QStackedWidget;
 
 /**
  * The starting point of the module. Instantiates PartitionCoreModule and
@@ -39,6 +41,7 @@ class PLUGINDLLEXPORT PartitionViewStep : public Calamares::ViewStep
 
 public:
     explicit PartitionViewStep( QObject* parent = 0 );
+    virtual ~PartitionViewStep();
 
     QString prettyName() const override;
     QWidget* createSummaryWidget() const override;
@@ -57,7 +60,9 @@ public:
 
 private:
     PartitionCoreModule* m_core;
-    PartitionPage* m_page;
+    QStackedWidget*   m_widget;
+    ChoicePage*       m_choicePage;
+    PartitionPage*    m_manualPartitionPage;
 };
 
 #endif // PARTITIONVIEWSTEP_H
-- 
GitLab