1 /* === This file is part of Calamares - <https://calamares.io> ===
2  *
3  *   SPDX-FileCopyrightText: 2015-2016 Teo Mrnjavac <teo@kde.org>
4  *   SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
5  *   SPDX-License-Identifier: GPL-3.0-or-later
6  *
7  *   Calamares is Free Software: see the License-Identifier above.
8  *
9  */
10 
11 #include "DebugWindow.h"
12 #include "ui_DebugWindow.h"
13 
14 #include "Branding.h"
15 #include "GlobalStorage.h"
16 #include "Job.h"
17 #include "JobQueue.h"
18 #include "Settings.h"
19 #include "VariantModel.h"
20 #include "modulesystem/Module.h"
21 #include "modulesystem/ModuleManager.h"
22 #include "utils/Logger.h"
23 #include "utils/Paste.h"
24 #include "utils/Retranslator.h"
25 
26 #ifdef WITH_PYTHONQT
27 #include "ViewManager.h"
28 #include "viewpages/PythonQtViewStep.h"
29 
30 #include <gui/PythonQtScriptingConsole.h>
31 #endif
32 
33 #include <QSplitter>
34 #include <QStringListModel>
35 #include <QTreeView>
36 #include <QWidget>
37 
38 /**
39  * @brief crash makes Calamares crash immediately.
40  */
41 static void
crash()42 crash()
43 {
44     volatile int* a = nullptr;
45     *a = 1;
46 }
47 
48 /// @brief Print out the widget tree (names) in indented form.
49 static void
dumpWidgetTree(QDebug & deb,const QWidget * widget,int depth)50 dumpWidgetTree( QDebug& deb, const QWidget* widget, int depth )
51 {
52     if ( !widget )
53     {
54         return;
55     }
56 
57     deb << Logger::Continuation;
58     for ( int i = 0; i < depth; ++i )
59     {
60         deb << ' ';
61     }
62     deb << widget->metaObject()->className() << widget->objectName();
63 
64     for ( const auto* w : widget->findChildren< QWidget* >( QString(), Qt::FindDirectChildrenOnly ) )
65     {
66         dumpWidgetTree( deb, w, depth + 1 );
67     }
68 }
69 
70 namespace Calamares
71 {
72 
DebugWindow()73 DebugWindow::DebugWindow()
74     : QWidget( nullptr )
75     , m_ui( new Ui::DebugWindow )
76     , m_globals( JobQueue::instance()->globalStorage()->data() )
77     , m_globals_model( std::make_unique< VariantModel >( &m_globals ) )
78     , m_module_model( std::make_unique< VariantModel >( &m_module ) )
79 {
80     GlobalStorage* gs = JobQueue::instance()->globalStorage();
81 
82     m_ui->setupUi( this );
83 
84     m_ui->globalStorageView->setModel( m_globals_model.get() );
85     m_ui->globalStorageView->expandAll();
86 
87     // Do above when the GS changes, too
88     connect( gs, &GlobalStorage::changed, this, [=] {
89         m_globals = JobQueue::instance()->globalStorage()->data();
90         m_globals_model->reload();
91         m_ui->globalStorageView->expandAll();
92     } );
93 
94     // JobQueue page
95     m_ui->jobQueueText->setReadOnly( true );
96     connect( JobQueue::instance(), &JobQueue::queueChanged, this, [this]( const QStringList& jobs ) {
97         m_ui->jobQueueText->setText( jobs.join( '\n' ) );
98     } );
99 
100     // Modules page
101     QStringList modulesKeys;
102     for ( const auto& m : ModuleManager::instance()->loadedInstanceKeys() )
103     {
104         modulesKeys << m.toString();
105     }
106 
107     QStringListModel* modulesModel = new QStringListModel( modulesKeys );
108     m_ui->modulesListView->setModel( modulesModel );
109     m_ui->modulesListView->setSelectionMode( QAbstractItemView::SingleSelection );
110 
111     m_ui->moduleConfigView->setModel( m_module_model.get() );
112 
113 #ifdef WITH_PYTHONQT
114     QPushButton* pythonConsoleButton = new QPushButton;
115     pythonConsoleButton->setText( "Attach Python console" );
116     m_ui->modulesVerticalLayout->insertWidget( 1, pythonConsoleButton );
117     pythonConsoleButton->hide();
118 
119     QObject::connect( pythonConsoleButton, &QPushButton::clicked, this, [this, moduleConfigModel] {
120         QString moduleName = m_ui->modulesListView->currentIndex().data().toString();
121         Module* module = ModuleManager::instance()->moduleInstance( moduleName );
122         if ( module->interface() != Module::Interface::PythonQt || module->type() != Module::Type::View )
123             return;
124 
125         for ( ViewStep* step : ViewManager::instance()->viewSteps() )
126         {
127             if ( step->moduleInstanceKey() == module->instanceKey() )
128             {
129                 PythonQtViewStep* pqvs = qobject_cast< PythonQtViewStep* >( step );
130                 if ( pqvs )
131                 {
132                     QWidget* consoleWindow = new QWidget;
133 
134                     QWidget* console = pqvs->createScriptingConsole();
135                     console->setParent( consoleWindow );
136 
137                     QVBoxLayout* layout = new QVBoxLayout;
138                     consoleWindow->setLayout( layout );
139                     layout->addWidget( console );
140 
141                     QHBoxLayout* bottomLayout = new QHBoxLayout;
142                     layout->addLayout( bottomLayout );
143 
144                     QLabel* bottomLabel = new QLabel( consoleWindow );
145                     bottomLayout->addWidget( bottomLabel );
146                     QString line = QString( "Module: <font color=\"#008000\"><code>%1</code></font><br/>"
147                                             "Python class: <font color=\"#008000\"><code>%2</code></font>" )
148                                        .arg( module->instanceKey() )
149                                        .arg( console->property( "classname" ).toString() );
150                     bottomLabel->setText( line );
151 
152                     QPushButton* closeButton = new QPushButton( consoleWindow );
153                     closeButton->setText( "&Close" );
154                     QObject::connect( closeButton, &QPushButton::clicked, [consoleWindow] { consoleWindow->close(); } );
155                     bottomLayout->addWidget( closeButton );
156                     bottomLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
157 
158                     consoleWindow->setParent( this );
159                     consoleWindow->setWindowFlags( Qt::Window );
160                     consoleWindow->setWindowTitle( "Calamares Python console" );
161                     consoleWindow->setAttribute( Qt::WA_DeleteOnClose, true );
162                     consoleWindow->showNormal();
163                     break;
164                 }
165             }
166         }
167     } );
168 
169 #endif
170 
171     connect( m_ui->modulesListView->selectionModel(),
172              &QItemSelectionModel::selectionChanged,
173              this,
174              [this
175 #ifdef WITH_PYTHONQT
176               ,
177               pythonConsoleButton
178 #endif
179     ] {
180                  QString moduleName = m_ui->modulesListView->currentIndex().data().toString();
181                  Module* module
182                      = ModuleManager::instance()->moduleInstance( ModuleSystem::InstanceKey::fromString( moduleName ) );
183                  if ( module )
184                  {
185                      m_module = module->configurationMap();
186                      m_module_model->reload();
187                      m_ui->moduleConfigView->expandAll();
188                      m_ui->moduleTypeLabel->setText( module->typeString() );
189                      m_ui->moduleInterfaceLabel->setText( module->interfaceString() );
190 #ifdef WITH_PYTHONQT
191                      pythonConsoleButton->setVisible( module->interface() == Module::Interface::PythonQt
192                                                       && module->type() == Module::Type::View );
193 #endif
194                  }
195              } );
196 
197     // Tools page
198     connect( m_ui->crashButton, &QPushButton::clicked, this, [] { ::crash(); } );
199     connect( m_ui->reloadStylesheetButton, &QPushButton::clicked, []() {
200         for ( auto* w : qApp->topLevelWidgets() )
201         {
202             // Needs to match what's set in CalamaresWindow
203             if ( w->objectName() == QStringLiteral( "mainApp" ) )
204             {
205                 w->setStyleSheet( Calamares::Branding::instance()->stylesheet() );
206             }
207         }
208     } );
209     connect( m_ui->widgetTreeButton, &QPushButton::clicked, []() {
210         for ( auto* w : qApp->topLevelWidgets() )
211         {
212             Logger::CDebug deb;
213             dumpWidgetTree( deb, w, 0 );
214         }
215     } );
216 
217     // Send Log button only if it would be useful
218     m_ui->sendLogButton->setVisible( CalamaresUtils::Paste::isEnabled() );
219     connect( m_ui->sendLogButton, &QPushButton::clicked, [this]() { CalamaresUtils::Paste::doLogUploadUI( this ); } );
220 
221     CALAMARES_RETRANSLATE( m_ui->retranslateUi( this ); setWindowTitle( tr( "Debug information" ) ); );
222 }
223 
224 
225 void
closeEvent(QCloseEvent * e)226 DebugWindow::closeEvent( QCloseEvent* e )
227 {
228     Q_UNUSED( e )
229     emit closed();
230 }
231 
232 
DebugWindowManager(QObject * parent)233 DebugWindowManager::DebugWindowManager( QObject* parent )
234     : QObject( parent )
235 {
236 }
237 
238 
239 bool
enabled() const240 DebugWindowManager::enabled() const
241 {
242     const auto* s = Settings::instance();
243     return ( Logger::logLevel() >= Logger::LOGVERBOSE ) || ( s ? s->debugMode() : false );
244 }
245 
246 
247 void
show(bool visible)248 DebugWindowManager::show( bool visible )
249 {
250     if ( !enabled() )
251     {
252         visible = false;
253     }
254     if ( m_visible == visible )
255     {
256         return;
257     }
258 
259     if ( visible )
260     {
261         m_debugWindow = new Calamares::DebugWindow();
262         m_debugWindow->show();
263         connect( m_debugWindow.data(), &Calamares::DebugWindow::closed, this, [=]() {
264             m_debugWindow->deleteLater();
265             m_visible = false;
266             emit visibleChanged( false );
267         } );
268         m_visible = true;
269         emit visibleChanged( true );
270     }
271     else
272     {
273         if ( m_debugWindow )
274         {
275             m_debugWindow->deleteLater();
276         }
277         m_visible = false;
278         emit visibleChanged( false );
279     }
280 }
281 
282 void
toggle()283 DebugWindowManager::toggle()
284 {
285     show( !m_visible );
286 }
287 
288 
289 }  // namespace Calamares
290