Files
BloomPatched/src/Insight/UserInterfaces/InsightWindow/InsightWindow.cpp

476 lines
18 KiB
C++
Raw Normal View History

2021-04-04 21:04:12 +01:00
#include <QtUiTools>
#include <QtSvg/QtSvg>
2021-06-21 00:14:31 +01:00
#include <utility>
2021-04-04 21:04:12 +01:00
#include "InsightWindow.hpp"
#include "AboutWindow.hpp"
#include "Widgets/TargetWidgets/DIP/DualInlinePackageWidget.hpp"
#include "Widgets/TargetWidgets/QFP/QuadFlatPackageWidget.hpp"
2021-04-04 21:04:12 +01:00
#include "src/Logger/Logger.hpp"
#include "src/Exceptions/Exception.hpp"
#include "src/Targets/TargetDescriptor.hpp"
#include "src/Helpers/Paths.hpp"
2021-04-04 21:04:12 +01:00
using namespace Bloom;
using namespace Bloom::Exceptions;
using namespace Bloom::Widgets;
using Bloom::Targets::TargetDescriptor;
using Bloom::Targets::TargetState;
using Bloom::Targets::TargetPinState;
using Bloom::Targets::TargetVariant;
using Bloom::Targets::TargetPackage;
using Bloom::Targets::TargetPinDescriptor;
2021-04-04 21:04:12 +01:00
void InsightWindow::init(
QApplication& application,
2021-05-30 16:53:24 +01:00
TargetDescriptor targetDescriptor
) {
2021-06-21 00:14:31 +01:00
this->targetDescriptor = std::move(targetDescriptor);
2021-04-04 21:04:12 +01:00
auto mainWindowUiFile = QFile(
QString::fromStdString(Paths::compiledResourcesPath()
+ "/src/Insight/UserInterfaces/InsightWindow/UiFiles/InsightWindow.ui"
)
);
auto mainWindowStylesheet = QFile(
QString::fromStdString(Paths::compiledResourcesPath()
+ "/src/Insight/UserInterfaces/InsightWindow/Stylesheets/InsightWindow.qss"
)
);
2021-04-04 21:04:12 +01:00
if (!mainWindowUiFile.open(QFile::ReadOnly)) {
throw Exception("Failed to open InsightWindow UI file");
}
if (!mainWindowStylesheet.open(QFile::ReadOnly)) {
throw Exception("Failed to open InsightWindow stylesheet file");
}
auto uiLoader = QUiLoader(this);
this->mainWindowWidget = uiLoader.load(&mainWindowUiFile);
this->mainWindowWidget->setStyleSheet(mainWindowStylesheet.readAll());
2021-06-21 00:14:31 +01:00
QApplication::setWindowIcon(QIcon(
QString::fromStdString(Paths::compiledResourcesPath()
+ "/src/Insight/UserInterfaces/InsightWindow/Images/BloomIcon.svg"
)
));
2021-04-04 21:04:12 +01:00
this->ioContainerWidget = this->mainWindowWidget->findChild<QWidget*>("io-container");
2021-05-30 16:53:24 +01:00
this->ioUnavailableWidget = this->mainWindowWidget->findChild<QLabel*>("io-inspection-unavailable");
2021-04-04 21:04:12 +01:00
this->mainMenuBar = this->mainWindowWidget->findChild<QMenuBar*>("menu-bar");
auto fileMenu = this->mainMenuBar->findChild<QMenu*>("file-menu");
auto helpMenu = this->mainMenuBar->findChild<QMenu*>("help-menu");
auto quitAction = fileMenu->findChild<QAction*>("close-insight");
auto openReportIssuesUrlAction = helpMenu->findChild<QAction*>("open-report-issues-url");
auto openGettingStartedUrlAction = helpMenu->findChild<QAction*>("open-getting-started-url");
2021-04-04 21:04:12 +01:00
auto openAboutWindowAction = helpMenu->findChild<QAction*>("open-about-dialogue");
connect(quitAction, &QAction::triggered, this, &InsightWindow::close);
connect(openReportIssuesUrlAction, &QAction::triggered, this, &InsightWindow::openReportIssuesUrl);
connect(openGettingStartedUrlAction, &QAction::triggered, this, &InsightWindow::openGettingStartedUrl);
2021-04-04 21:04:12 +01:00
connect(openAboutWindowAction, &QAction::triggered, this, &InsightWindow::openAboutWindow);
this->header = this->mainWindowWidget->findChild<QWidget*>("header");
this->refreshIoInspectionButton = this->header->findChild<QToolButton*>("refresh-io-inspection-btn");
connect(this->refreshIoInspectionButton, &QToolButton::clicked, this, [this] {
if (this->targetState == TargetState::STOPPED && this->selectedVariant != nullptr) {
this->toggleUi(true);
emit this->refreshTargetPinStates(this->selectedVariant->id);
}
});
this->footer = this->mainWindowWidget->findChild<QWidget*>("footer");
this->targetStatusLabel = this->footer->findChild<QLabel*>("target-state");
this->programCounterValueLabel = this->footer->findChild<QLabel*>("target-program-counter-value");
2021-05-30 16:53:24 +01:00
this->activate();
/*
* Do not delete svgWidget. It seems like it's absolutely pointless, but it's really not. I know this is gross but
* I don't seem to have any other option.
*
* You see, we use Qt's SVG libraries for some icons and other graphics in the Insight window, but because these are
* all loaded via the .ui and .qss file (at runtime, by Qt), and the fact that we don't use QSvgWidget anywhere in
* our C++ code, the linker doesn't link the SVG library to the Bloom binary! This then leads to the failure of
* rendering SVG images at runtime.
*
* I've scowered the internet looking for a solution to this, but I've found nothing! There was one post on the
* Qt forum, where someone had the very same issue: https://forum.qt.io/topic/61875/cmake-is-not-linking but no
* helpful responses.
*
* The easy solution is finding an excuse to use the QSvgWidget class in our code, but we have no real requirement
* of that.
*
* So for now, I'm just going to create an instance to QSvgWidget() here. Doing so will force the linker to link
* the libQt5Svg library. This will need to remain here until I find a better solution, or an actual need for the
* QSVGWidget class in our code.
*/
auto svgWidget = QSvgWidget();
}
void InsightWindow::activate() {
auto targetNameLabel = this->footer->findChild<QLabel*>("target-name");
auto targetIdLabel = this->footer->findChild<QLabel*>("target-id");
2021-04-04 21:04:12 +01:00
targetNameLabel->setText(QString::fromStdString(this->targetDescriptor.name));
targetIdLabel->setText("0x" + QString::fromStdString(this->targetDescriptor.id).remove("0x").toUpper());
this->variantMenu = this->footer->findChild<QMenu*>("target-variant-menu");
2021-05-30 16:53:24 +01:00
this->ioUnavailableWidget->hide();
std::optional<QString> previouslySelectedVariantName;
if (this->selectedVariant != nullptr) {
previouslySelectedVariantName = QString::fromStdString(this->selectedVariant->name).toLower();
this->selectedVariant = nullptr;
}
this->supportedVariantsByName.clear();
/*
* We don't want to present the user with duplicate target variants.
*
* In the context of the Insight window, a variant that doesn't differ in package type or pinout
* configuration is considered a duplicate.
*/
auto processedVariants = std::vector<TargetVariant>();
auto isDuplicateVariant = [&processedVariants] (const TargetVariant& variantA) {
return std::ranges::any_of(
processedVariants.begin(),
processedVariants.end(),
[&variantA, &processedVariants] (const TargetVariant& variantB) {
if (variantA.package != variantB.package) {
return false;
}
if (variantA.pinDescriptorsByNumber.size() != variantB.pinDescriptorsByNumber.size()) {
return false;
}
if (variantA.pinDescriptorsByNumber != variantB.pinDescriptorsByNumber) {
return false;
}
return true;
}
);
};
2021-04-04 21:04:12 +01:00
for (const auto& targetVariant: this->targetDescriptor.variants) {
if (isDuplicateVariant(targetVariant)) {
continue;
}
2021-04-04 21:04:12 +01:00
auto variantAction = new QAction(this->variantMenu);
variantAction->setText(
QString::fromStdString(targetVariant.name + " (" + targetVariant.packageName + ")")
);
2021-06-21 00:14:31 +01:00
if (InsightWindow::isVariantSupported(targetVariant)) {
2021-05-30 16:53:24 +01:00
auto supportedVariantPtr = &(this->supportedVariantsByName.insert(
std::pair(QString::fromStdString(targetVariant.name).toLower(), targetVariant)
).first->second);
2021-04-04 21:04:12 +01:00
connect(
variantAction,
&QAction::triggered,
this,
2021-05-30 16:53:24 +01:00
[this, supportedVariantPtr] {
this->selectVariant(supportedVariantPtr);
2021-04-04 21:04:12 +01:00
}
);
} else {
variantAction->setEnabled(false);
variantAction->setText(variantAction->text() + " (unsupported)");
2021-05-30 16:53:24 +01:00
}
2021-04-04 21:04:12 +01:00
this->variantMenu->addAction(variantAction);
processedVariants.push_back(targetVariant);
2021-04-04 21:04:12 +01:00
}
2021-05-30 16:53:24 +01:00
this->variantMenu->setEnabled(true);
Logger::debug("Number of target variants supported by Insight: " + std::to_string(supportedVariantsByName.size()));
2021-04-04 21:04:12 +01:00
if (!this->supportedVariantsByName.empty()) {
2021-05-30 16:53:24 +01:00
if (previouslySelectedVariantName.has_value()
&& this->supportedVariantsByName.contains(previouslySelectedVariantName.value())
) {
this->selectVariant(&(this->supportedVariantsByName.at(previouslySelectedVariantName.value())));
} else if (this->targetConfig.variantName.has_value()) {
auto selectedVariantName = QString::fromStdString(this->targetConfig.variantName.value());
if (this->supportedVariantsByName.contains(selectedVariantName)) {
// The user has specified a valid variant name in their config file, so use that as the default
this->selectVariant(&(this->supportedVariantsByName.at(selectedVariantName)));
2021-04-04 21:04:12 +01:00
} else {
2021-05-30 16:53:24 +01:00
Logger::error("Invalid target variant name \"" + this->targetConfig.variantName.value()
+ "\" - no such variant with the given name was found.");
}
}
if (this->selectedVariant == nullptr) {
/*
* Given that we haven't been able to select a variant at this point, we will just fallback to the first
* one that is available.
*/
this->selectVariant(&(this->supportedVariantsByName.begin()->second));
}
2021-04-04 21:04:12 +01:00
} else {
if (this->targetDescriptor.variants.empty()) {
this->variantMenu->parentWidget()->hide();
}
2021-05-30 16:53:24 +01:00
this->ioUnavailableWidget->setText(
"GPIO inspection is not available for this target. "
"Please report this to Bloom developers by clicking Help -> Report An Issue"
);
2021-04-04 21:04:12 +01:00
this->ioUnavailableWidget->show();
}
2021-05-30 16:53:24 +01:00
this->toggleUi(this->targetState != TargetState::STOPPED);
this->activated = true;
}
void InsightWindow::deactivate() {
if (this->targetPackageWidget != nullptr) {
this->targetPackageWidget->hide();
this->targetPackageWidget->deleteLater();
this->targetPackageWidget = nullptr;
}
this->ioUnavailableWidget->setText(
2021-05-31 00:03:57 +01:00
"Insight deactivated - Bloom has been disconnected from the target.\n\n"
"Bloom will attempt to reconnect upon the start of a new debug session."
2021-05-30 16:53:24 +01:00
);
this->ioUnavailableWidget->show();
this->targetStatusLabel->setText("Unknown");
this->programCounterValueLabel->setText("-");
this->variantMenu->clear();
this->variantMenu->setEnabled(false);
this->toggleUi(true);
this->activated = false;
}
bool InsightWindow::isVariantSupported(const TargetVariant& variant) {
/*
* Because the size of the pin body widget is fixed, for all of our target package widgets, we run out of screen
* estate for target variants with more than 100 pins.
*
* This will be addressed at some point, but for now, we just won't support variants with more than 100 pins.
*/
if (variant.pinDescriptorsByNumber.size() > 100) {
return false;
}
if (variant.package == TargetPackage::DIP
|| variant.package == TargetPackage::SOIC
|| variant.package == TargetPackage::SSOP
) {
// All DIP, SOIC and SSOP variants must have a pin count that is a multiple of two
2021-05-30 16:53:24 +01:00
if (variant.pinDescriptorsByNumber.size() % 2 == 0) {
return true;
}
}
if (variant.package == TargetPackage::QFP || variant.package == TargetPackage::QFN) {
// All QFP and QFN variants must have a pin count that is a multiple of four
2021-05-30 16:53:24 +01:00
if (variant.pinDescriptorsByNumber.size() % 4 == 0) {
return true;
}
}
return false;
2021-04-04 21:04:12 +01:00
}
void InsightWindow::selectVariant(const TargetVariant* variant) {
if (!this->isVariantSupported(*variant)) {
Logger::error("Attempted to select unsupported target variant.");
return;
}
if (this->selectedVariant != nullptr && this->selectedVariant->id == variant->id) {
return;
}
if (this->targetPackageWidget != nullptr) {
this->targetPackageWidget->hide();
this->targetPackageWidget->deleteLater();
2021-05-30 16:53:24 +01:00
this->targetPackageWidget = nullptr;
2021-04-04 21:04:12 +01:00
}
this->selectedVariant = variant;
this->variantMenu->setTitle(QString::fromStdString(variant->name + " (" + variant->packageName + ")"));
if (variant->package == TargetPackage::DIP
|| variant->package == TargetPackage::SOIC
|| variant->package == TargetPackage::SSOP
) {
2021-04-04 21:04:12 +01:00
this->targetPackageWidget = new InsightTargetWidgets::Dip::DualInlinePackageWidget(
*variant,
this,
this->ioContainerWidget
);
} else if (variant->package == TargetPackage::QFP || variant->package == TargetPackage::QFN) {
2021-04-04 21:04:12 +01:00
this->targetPackageWidget = new InsightTargetWidgets::Qfp::QuadFlatPackageWidget(
*variant,
this,
this->ioContainerWidget
);
}
if (this->targetPackageWidget != nullptr) {
if (this->targetState == TargetState::STOPPED) {
this->toggleUi(true);
emit this->refreshTargetPinStates(variant->id);
}
this->targetPackageWidget->show();
}
}
void InsightWindow::show() {
this->mainWindowWidget->activateWindow();
this->mainWindowWidget->show();
}
void InsightWindow::close() {
if (this->mainWindowWidget != nullptr) {
this->mainWindowWidget->close();
}
}
2021-05-30 16:53:24 +01:00
void InsightWindow::toggleUi(bool disable) {
this->uiDisabled = disable;
if (this->refreshIoInspectionButton != nullptr) {
this->refreshIoInspectionButton->setDisabled(disable);
this->refreshIoInspectionButton->repaint();
2021-04-04 21:04:12 +01:00
}
2021-05-30 16:53:24 +01:00
if (this->ioContainerWidget != nullptr) {
this->ioContainerWidget->setDisabled(disable);
this->ioContainerWidget->repaint();
2021-04-04 21:04:12 +01:00
}
2021-05-30 16:53:24 +01:00
}
2021-04-04 21:04:12 +01:00
2021-05-30 16:53:24 +01:00
void InsightWindow::onTargetControllerSuspended() {
if (this->activated) {
this->deactivate();
}
}
void InsightWindow::onTargetControllerResumed(const TargetDescriptor& targetDescriptor) {
if (!this->activated) {
this->targetDescriptor = targetDescriptor;
this->activate();
}
2021-04-04 21:04:12 +01:00
}
void InsightWindow::openReportIssuesUrl() {
auto url = QUrl("https://bloom.oscillate.io/report-issue");
/*
* The https://bloom.oscillate.io/report-issue URL just redirects to the Bloom GitHub issue page.
*
* We can use query parameters in the URL to pre-fill the body of the issue. We use this to include some
* target information.
*/
auto urlQuery = QUrlQuery();
auto issueBody = QString("Issue reported via Bloom Insight.\nTarget name: "
+ QString::fromStdString(this->targetDescriptor.name) + "\n"
+ "Target ID: " + QString::fromStdString(this->targetDescriptor.id) + "\n"
);
if (this->selectedVariant != nullptr) {
issueBody += "Target variant: " + QString::fromStdString(this->selectedVariant->name) + "\n";
}
issueBody += "\nPlease describe your issue below. Include as much detail as possible.";
urlQuery.addQueryItem("body", issueBody);
url.setQuery(urlQuery);
QDesktopServices::openUrl(url);
2021-04-04 21:04:12 +01:00
}
void InsightWindow::openGettingStartedUrl() {
QDesktopServices::openUrl(QUrl("https://bloom.oscillate.io/docs/getting-started"));
}
2021-04-04 21:04:12 +01:00
void InsightWindow::openAboutWindow() {
if (this->aboutWindowWidget == nullptr) {
this->aboutWindowWidget = new AboutWindow(this->mainWindowWidget);
}
this->aboutWindowWidget->show();
}
void InsightWindow::onTargetStateUpdate(TargetState newState) {
this->targetState = newState;
if (newState == TargetState::RUNNING) {
this->targetStatusLabel->setText("Running");
this->programCounterValueLabel->setText("-");
this->toggleUi(true);
} else if (newState == TargetState::STOPPED) {
this->targetStatusLabel->setText("Stopped");
if (this->selectedVariant != nullptr) {
emit this->refreshTargetPinStates(this->selectedVariant->id);
}
} else {
this->targetStatusLabel->setText("Unknown");
}
}
void InsightWindow::onTargetProgramCounterUpdate(quint32 programCounter) {
this->programCounterValueLabel->setText(
"0x" + QString::number(programCounter, 16).toUpper() + " (" + QString::number(programCounter) + ")"
);
}
void InsightWindow::onTargetIoPortsUpdate() {
if (this->targetState == TargetState::STOPPED && this->selectedVariant != nullptr) {
emit this->refreshTargetPinStates(this->selectedVariant->id);
}
}
void InsightWindow::onTargetPinStatesUpdate(int variantId, Bloom::Targets::TargetPinStateMappingType pinStatesByNumber) {
if (this->targetPackageWidget != nullptr
&& this->selectedVariant != nullptr
&& this->selectedVariant->id == variantId
) {
this->targetPackageWidget->updatePinStates(pinStatesByNumber);
if (this->targetState == TargetState::STOPPED && this->uiDisabled) {
2021-04-04 21:04:12 +01:00
this->toggleUi(false);
} else {
this->targetPackageWidget->repaint();
}
}
}
void InsightWindow::togglePinIoState(InsightTargetWidgets::TargetPinWidget* pinWidget) {
2021-04-04 21:04:12 +01:00
auto pinState = pinWidget->getPinState();
// Currently, we only allow users to toggle the IO state of output pins
if (pinState.has_value()
&& pinState.value().ioDirection == TargetPinState::IoDirection::OUTPUT
&& this->selectedVariant != nullptr
) {
auto& pinDescriptor = pinWidget->getPinDescriptor();
pinState.value().ioState = (pinState.value().ioState == TargetPinState::IoState::HIGH) ?
TargetPinState::IoState::LOW : TargetPinState::IoState::HIGH;
emit this->setTargetPinState(this->selectedVariant->id, pinDescriptor, pinState.value());
}
2021-05-30 16:53:24 +01:00
}