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