A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "regtab.h"
22
23#include <QSizePolicy>
24#include <QStringBuilder>
25#include <QFileDialog>
26#include <QDebug>
27#include <QStyle>
28#include <QMessageBox>
29#include "backend.h"
30#include "analyser.h"
31#include "regdisplaypanel.h"
32
33namespace
34{
35
36enum
37{
38 NodeInstRole = Qt::UserRole,
39};
40
41}
42
43/**
44 * EmptyRegTabPanel
45 */
46EmptyRegTabPanel::EmptyRegTabPanel(QWidget *parent)
47 :QWidget(parent)
48{
49 QVBoxLayout *l = new QVBoxLayout;
50 l->addStretch();
51 setLayout(l);
52}
53
54void EmptyRegTabPanel::AllowWrite(bool en)
55{
56 Q_UNUSED(en);
57}
58
59QWidget *EmptyRegTabPanel::GetWidget()
60{
61 return this;
62}
63
64
65/**
66 * RegTab
67 */
68
69RegTab::RegTab(Backend *backend, QWidget *parent)
70 :QSplitter(parent), m_backend(backend)
71{
72 QWidget *left = new QWidget;
73 this->addWidget(left);
74 this->setStretchFactor(0, 1);
75 QVBoxLayout *left_layout = new QVBoxLayout;
76 left->setLayout(left_layout);
77
78 QGroupBox *top_group = new QGroupBox("");
79 QHBoxLayout *top_group_layout = new QHBoxLayout;
80 m_soc_selector = new QComboBox;
81 top_group_layout->addWidget(new QLabel("SOC:"));
82 top_group_layout->addWidget(m_soc_selector, 1);
83 top_group->setLayout(top_group_layout);
84
85 m_reg_tree = new QTreeWidget();
86 m_reg_tree->setColumnCount(1);
87 m_reg_tree->setHeaderLabel(QString("Name"));
88
89 m_analysers_list = new QListWidget;
90
91 m_type_selector = new QTabWidget;
92 m_type_selector->addTab(m_reg_tree, "Registers");
93 m_type_selector->addTab(m_analysers_list, "Analyzers");
94 m_type_selector->setTabPosition(QTabWidget::West);
95
96 left_layout->addWidget(top_group);
97 left_layout->addWidget(m_type_selector);
98
99 m_right_panel = new QVBoxLayout;
100 QGroupBox *data_sel_group = new QGroupBox("");
101 QHBoxLayout *data_sel_layout = new QHBoxLayout;
102 m_backend_selector = new BackendSelector(m_backend, this);
103 m_backend_selector->SetNothingMessage("<i>Select a data source to analyse its content.</i>");
104 m_readonly_check = new QCheckBox("Read-only");
105 m_readonly_check->setCheckState(Qt::Checked);
106 m_data_soc_label = new QLabel;
107 m_dump = new QPushButton("Dump", this);
108 m_dump->setIcon(QIcon::fromTheme("system-run"));
109 m_data_sel_reload = new QPushButton(this);
110 m_data_sel_reload->setIcon(QIcon::fromTheme("view-refresh"));
111 m_data_sel_reload->setToolTip("Reload data");
112 data_sel_layout->addWidget(new QLabel("Data:"));
113 data_sel_layout->addWidget(m_backend_selector);
114 data_sel_layout->addWidget(m_readonly_check);
115 data_sel_layout->addWidget(m_data_soc_label);
116 data_sel_layout->addWidget(m_dump);
117 data_sel_layout->addWidget(m_data_sel_reload);
118 data_sel_group->setLayout(data_sel_layout);
119 m_data_soc_label->setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
120 m_msg = new MessageWidget(this);
121
122 m_right_panel->addWidget(data_sel_group, 0);
123 m_right_panel->addWidget(m_msg, 0);
124 m_right_content = 0;
125 SetPanel(new EmptyRegTabPanel);
126 QWidget *w = new QWidget;
127 w->setLayout(m_right_panel);
128 this->addWidget(w);
129 this->setStretchFactor(1, 2);
130
131 m_io_backend = m_backend_selector->GetBackend();
132
133 connect(m_soc_selector, SIGNAL(currentIndexChanged(int)),
134 this, SLOT(OnSocChanged(int)));
135 connect(m_backend, SIGNAL(OnSocAdded(const SocFileRef&)), this,
136 SLOT(OnSocAdded(const SocFileRef&)));
137 connect(m_reg_tree, SIGNAL(itemClicked(QTreeWidgetItem*, int)),
138 this, SLOT(OnRegItemClicked(QTreeWidgetItem*, int)));
139 connect(m_data_soc_label, SIGNAL(linkActivated(const QString&)), this,
140 SLOT(OnDataSocActivated(const QString&)));
141 connect(m_analysers_list, SIGNAL(itemClicked(QListWidgetItem *)),
142 this, SLOT(OnAnalyserClicked(QListWidgetItem *)));
143 connect(m_backend_selector, SIGNAL(OnSelect(IoBackend *)),
144 this, SLOT(OnBackendSelect(IoBackend *)));
145 connect(m_readonly_check, SIGNAL(clicked(bool)), this, SLOT(OnReadOnlyClicked(bool)));
146 connect(m_dump, SIGNAL(clicked(bool)), this, SLOT(OnDumpRegs(bool)));
147 connect(m_data_sel_reload, SIGNAL(clicked(bool)), this, SLOT(OnBackendReload(bool)));
148 connect(m_type_selector, SIGNAL(currentChanged(int)), this, SLOT(OnTypeChanged(int)));
149
150 m_msg_select_id = SetMessage(MessageWidget::Information,
151 "You can browse the registers using the left panel. "
152 "Select a data source at the top to analyse the values. "
153 "You can also use the analyzers available in the left panel, if any.");
154 m_msg_error_id = 0;
155
156 QList< SocFileRef > socs = m_backend->GetSocFileList();
157 for(int i = 0; i < socs.size(); i++)
158 OnSocAdded(socs[i]);
159 SetDataSocName("");
160 UpdateTabName();
161}
162
163QWidget *RegTab::GetWidget()
164{
165 return this;
166}
167
168RegTab::~RegTab()
169{
170 /* backend will be deleted by backend selector */
171}
172
173bool RegTab::Quit()
174{
175 return true;
176}
177
178void RegTab::SetDataSocName(const QString& socname)
179{
180 if(socname.size() != 0)
181 {
182 m_data_soc_label->setText("<a href=\"" + socname + "\">" + socname + "</a>");
183 m_data_soc_label->setTextFormat(Qt::RichText);
184 m_data_soc_label->show();
185 }
186 else
187 {
188 m_data_soc_label->setText("");
189 m_data_soc_label->hide();
190 }
191}
192
193void RegTab::OnDataSocActivated(const QString& str)
194{
195 int index = m_soc_selector->findText(str);
196 if(index != -1)
197 m_soc_selector->setCurrentIndex(index);
198 else if(str.size() > 0)
199 {
200 m_msg_error_id = SetMessage(MessageWidget::Error,
201 "Description file for this SoC is not available.");
202 SetPanel(new EmptyRegTabPanel);
203 }
204}
205
206void RegTab::UpdateTabName()
207{
208 /* do it the ugly way: try to cast to the different possible backends */
209 FileIoBackend *file = dynamic_cast< FileIoBackend* >(m_io_backend);
210#ifdef HAVE_HWSTUB
211 HWStubIoBackend *hwstub = dynamic_cast< HWStubIoBackend* >(m_io_backend);
212#endif
213 if(file)
214 {
215 QFileInfo info(file->GetFileName());
216 SetTabName(info.fileName());
217 }
218#ifdef HAVE_HWSTUB
219 else if(hwstub)
220 {
221 HWStubDevice *dev = hwstub->GetDevice();
222 SetTabName(dev->GetFriendlyName());
223 }
224#endif
225 else
226 {
227 SetTabName("Register Tab");
228 }
229}
230
231void RegTab::OnBackendSelect(IoBackend *backend)
232{
233 /* Hide "Please select two SoC" and errors message */
234 HideMessage(m_msg_select_id);
235 HideMessage(m_msg_error_id);
236 m_io_backend = backend;
237 /* Check if the backend is valid, otherwise it might confuse the user */
238 if(m_io_backend->IsValid())
239 {
240 SetReadOnlyIndicator();
241 SetDataSocName(m_io_backend->GetSocName());
242 OnDataSocActivated(m_io_backend->GetSocName());
243 OnDataChanged();
244 UpdateTabName();
245 }
246 /* But don't display it for the dummy backend either */
247 else if(dynamic_cast< DummyIoBackend * >(m_io_backend) == 0)
248 {
249 m_msg_error_id = SetMessage(MessageWidget::Error,
250 "Data source is not available.");
251 }
252}
253
254void RegTab::SetReadOnlyIndicator()
255{
256 if(m_io_backend->IsReadOnly())
257 m_readonly_check->setCheckState(Qt::Checked);
258}
259
260void RegTab::OnDataChanged()
261{
262 OnRegItemClicked(m_reg_tree->currentItem(), 0);
263}
264
265void RegTab::OnRegItemClicked(QTreeWidgetItem *current, int col)
266{
267 Q_UNUSED(col);
268 if(current == 0)
269 return;
270 DisplayNode(NodeInst(current));
271}
272
273void RegTab::OnAnalyserClicked(QListWidgetItem *current)
274{
275 if(current == 0)
276 return;
277 AnalyserFactory *ana = AnalyserFactory::GetAnalyserByName(current->text());
278 SetPanel(ana->Create(m_cur_soc, m_io_backend));
279}
280
281void RegTab::DisplayNode(const soc_desc::node_inst_t& ref)
282{
283 HideMessage(m_msg_select_id);
284 if(ref.node().is_root())
285 SetPanel(new SocDisplayPanel(this, m_io_backend, ref.soc()));
286 else if(ref.node().reg().valid())
287 SetPanel(new RegDisplayPanel(this, m_io_backend, ref));
288 else
289 SetPanel(new NodeDisplayPanel(this, m_io_backend, ref));
290}
291
292int RegTab::SetMessage(MessageWidget::MessageType type, const QString& msg)
293{
294 return m_msg->SetMessage(type, msg);
295}
296
297void RegTab::HideMessage(int id)
298{
299 m_msg->HideMessage(id);
300}
301
302void RegTab::SetPanel(RegTabPanel *panel)
303{
304 delete m_right_content;
305 m_right_content = panel;
306 m_right_content->AllowWrite(m_readonly_check->checkState() == Qt::Unchecked);
307 m_right_panel->addWidget(m_right_content->GetWidget(), 1);
308}
309
310void RegTab::OnSocAdded(const SocFileRef& ref)
311{
312 soc_desc::soc_ref_t soc = ref.GetSocFile()->GetSocRef();
313 QVariant v;
314 v.setValue(soc);
315 m_soc_selector->addItem(QString::fromStdString(soc.get()->name), v);
316}
317
318QTreeWidgetItem *RegTab::MakeNode(const soc_desc::node_inst_t& inst, const QString& s)
319{
320 QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(s));
321 item->setData(0, NodeInstRole, QVariant::fromValue(inst));
322 return item;
323}
324
325soc_desc::node_inst_t RegTab::NodeInst(QTreeWidgetItem *item)
326{
327 return item->data(0, NodeInstRole).value< soc_desc::node_inst_t >();
328}
329
330void RegTab::FillSubTree(QTreeWidgetItem *item)
331{
332 std::vector< soc_desc::node_inst_t > list = NodeInst(item).children();
333 for(size_t i = 0; i < list.size(); i++)
334 {
335 QString name = QString::fromStdString(list[i].name());
336 if(list[i].is_indexed())
337 name = QString("%1[%2]").arg(name).arg(list[i].index());
338 QTreeWidgetItem *child = MakeNode(list[i], name);
339 FillSubTree(child);
340 item->addChild(child);
341 }
342}
343
344void RegTab::FillRegTree()
345{
346 QTreeWidgetItem *soc_item = MakeNode(m_cur_soc.root_inst(),
347 QString::fromStdString(m_cur_soc.get()->name));
348 FillSubTree(soc_item);
349 m_reg_tree->addTopLevelItem(soc_item);
350 m_reg_tree->expandItem(soc_item);
351}
352
353void RegTab::FillAnalyserList()
354{
355 m_analysers_list->clear();
356 m_analysers_list->addItems(AnalyserFactory::GetAnalysersForSoc(
357 QString::fromStdString(m_cur_soc.get()->name)));
358}
359
360void RegTab::OnSocChanged(int index)
361{
362 if(index == -1)
363 return;
364 m_reg_tree->clear();
365 m_cur_soc = m_soc_selector->itemData(index).value< soc_desc::soc_ref_t >();
366 FillRegTree();
367 FillAnalyserList();
368}
369
370void RegTab::OnReadOnlyClicked(bool checked)
371{
372 if(m_io_backend->IsReadOnly())
373 return SetReadOnlyIndicator();
374 m_right_content->AllowWrite(!checked);
375}
376
377void RegTab::OnDumpRegs(bool c)
378{
379 Q_UNUSED(c);
380 QFileDialog *fd = new QFileDialog(this);
381 fd->setAcceptMode(QFileDialog::AcceptSave);
382 QStringList filters;
383 filters << "Textual files (*.txt)";
384 filters << "All files (*)";
385 fd->setNameFilters(filters);
386 fd->setDirectory(Settings::Get()->value("regtab/loaddatadir", QDir::currentPath()).toString());
387 if(!fd->exec())
388 return;
389 QStringList filenames = fd->selectedFiles();
390 Settings::Get()->setValue("regtab/loaddatadir", fd->directory().absolutePath());
391 BackendHelper bh(m_io_backend, m_cur_soc);
392 if(!bh.DumpAllRegisters(filenames[0]))
393 {
394 QMessageBox::warning(this, "The register dump was not saved",
395 "There was an error when dumping the registers");
396 }
397}
398
399void RegTab::OnBackendReload(bool c)
400{
401 Q_UNUSED(c);
402 m_io_backend->Reload();
403 OnDataChanged();
404}
405
406void RegTab::OnTypeChanged(int index)
407{
408 if(index == -1)
409 return;
410 if(index == 0) /* registers */
411 OnRegItemClicked(m_reg_tree->currentItem(), 0);
412 else if(index == 1) /* analysers */
413 OnAnalyserClicked(m_analysers_list->currentItem());
414}