// // (c)2021 by Lucky Resistor. https://luckyresistor.me/ // // This program 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. // // This program 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 this program. If not, see . // #include "MainWindow.hpp" #include "ApplicationController.hpp" #include "BitmapPanel.hpp" #include "ConverterFramebufMonoVLSB.hpp" #include "ConverterFramebufMonoHLSB.hpp" #include "ConverterFramebufMonoHMSB.hpp" #include "FontConverterFramebufMono.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { initializeConverterList(); initializeUi(); initializeMenu(); loadSettings(); onFormatChanged(); onFontChanged(); onCharactersChanged(); } MainWindow::~MainWindow() { } void MainWindow::initializeConverterList() { _converterList.append(new ConverterFramebufMonoVLSB()); _converterList.append(new ConverterFramebufMonoHMSB()); _converterList.append(new ConverterFramebufMonoHLSB()); _converterList.append(new FontConverterFramebufMono()); } void MainWindow::initializeUi() { const int cFixedWidth = 400; setMinimumSize(800, 600); setWindowTitle(tr("MicroPython Bitmap Tool - V%1 - Lucky Resistor").arg(qApp->applicationVersion())); auto centralWidget = new QWidget(); centralWidget->setObjectName("CentralWidget"); setCentralWidget(centralWidget); auto mainLayout = new QHBoxLayout(centralWidget); mainLayout->setContentsMargins(0, 0, 0, 0); auto settingsPanel = new QFrame(); settingsPanel->setObjectName("SettingsPanel"); mainLayout->addWidget(settingsPanel); auto settingsLayout = new QVBoxLayout(settingsPanel); settingsLayout->setSpacing(4); auto logo = new QLabel(); logo->setFixedSize(cFixedWidth, 100); logo->setPixmap(QPixmap(":/images/AppLogo.png")); logo->setAlignment(Qt::AlignCenter); settingsLayout->addWidget(logo); auto versionLabel = new QLabel(tr("Version %1").arg(qApp->applicationVersion())); versionLabel->setAlignment(Qt::AlignCenter); settingsLayout->addWidget(versionLabel); settingsLayout->addSpacing(16); settingsLayout->addWidget(new QLabel(tr("Generated Format:"))); _formatSelector = new QComboBox(); for (auto converter : _converterList) { _formatSelector->addItem(converter->displayName()); } _formatSelector->setCurrentIndex(0); settingsLayout->addWidget(_formatSelector); settingsLayout->addWidget(new QLabel(tr("Parameters:"))); auto parameterScroll = new QScrollArea(); parameterScroll->setObjectName("ParameterScrollArea"); _parameterFrame = new QFrame(); _parameterFrame->setObjectName("ParameterFrame"); _parameterFrame->setFixedWidth(cFixedWidth); _parameterLayout = new QFormLayout(_parameterFrame); _parameterLayout->setContentsMargins(8, 8, 8, 8); parameterScroll->setWidget(_parameterFrame); parameterScroll->setWidgetResizable(true); parameterScroll->setFixedWidth(cFixedWidth); parameterScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); //parameterScroll->setBackgroundRole(QPalette::Window); settingsLayout->addWidget(parameterScroll); _bitmapConverterFrame = new QFrame(); auto bitmapConverterLayout = new QVBoxLayout(_bitmapConverterFrame); bitmapConverterLayout->setContentsMargins(0, 0, 0, 0); bitmapConverterLayout->setSpacing(4); settingsLayout->addWidget(_bitmapConverterFrame); bitmapConverterLayout->addWidget(new QLabel(tr("Loaded Bitmap Info:"))); _bitmapInfo = new QLabel(tr("No Bitmap Loaded")); _bitmapInfo->setObjectName("BitmapInfo"); _bitmapInfo->setMinimumHeight(100); _bitmapInfo->setAlignment(Qt::AlignTop|Qt::AlignLeft); bitmapConverterLayout->addWidget(_bitmapInfo); auto loadButton = new QPushButton(); loadButton->setText(tr("Load Bitmap")); bitmapConverterLayout->addWidget(loadButton); connect(loadButton, &QPushButton::clicked, this, &MainWindow::onLoadBitmap); _fontConverterFrame = new QFrame(); auto fontConverterLayout = new QVBoxLayout(_fontConverterFrame); fontConverterLayout->setContentsMargins(0, 0, 0, 0); fontConverterLayout->setSpacing(4); settingsLayout->addWidget(_fontConverterFrame); fontConverterLayout->addWidget(new QLabel(tr("Selected Font Info:"))); _fontInfo = new QLabel(tr("No Font Seleccted")); _fontInfo->setObjectName("FontInfo"); _fontInfo->setMinimumHeight(100); _fontInfo->setAlignment(Qt::AlignTop|Qt::AlignLeft); fontConverterLayout->addWidget(_fontInfo); fontConverterLayout->addWidget(new QLabel(tr("Selected Font:"))); _fontSelector = new QFontComboBox(); fontConverterLayout->addWidget(_fontSelector); auto fontDetailsLayout = new QHBoxLayout(); fontDetailsLayout->setContentsMargins(0, 0, 0, 0); fontDetailsLayout->setSpacing(4); fontConverterLayout->addLayout(fontDetailsLayout); _fontWeightSelector = new QComboBox(); _fontWeightSelector->addItem(tr("Thin"), static_cast(QFont::Thin)); _fontWeightSelector->addItem(tr("Extra Light"), static_cast(QFont::ExtraLight)); _fontWeightSelector->addItem(tr("Light"), static_cast(QFont::Light)); _fontWeightSelector->addItem(tr("Normal"), static_cast(QFont::Normal)); _fontWeightSelector->addItem(tr("Medium"), static_cast(QFont::Medium)); _fontWeightSelector->addItem(tr("Demi Bold"), static_cast(QFont::DemiBold)); _fontWeightSelector->addItem(tr("Bold"), static_cast(QFont::Bold)); _fontWeightSelector->addItem(tr("Extra Bold"), static_cast(QFont::ExtraBold)); _fontWeightSelector->addItem(tr("Black"), static_cast(QFont::Black)); _fontWeightSelector->setCurrentIndex(3); _fontSizeSelector = new QSpinBox(); _fontSizeSelector->setRange(4, 200); _fontSizeSelector->setValue(12); _fontHinting = new QComboBox(); _fontHinting->addItem(tr("Default"), static_cast(QFont::PreferDefaultHinting)); _fontHinting->addItem(tr("None"), static_cast(QFont::PreferNoHinting)); _fontHinting->addItem(tr("Vertical"), static_cast(QFont::PreferVerticalHinting)); _fontHinting->addItem(tr("Full"), static_cast(QFont::PreferFullHinting)); _fontHinting->setCurrentIndex(0); fontDetailsLayout->addWidget(new QLabel(tr("Size:"))); fontDetailsLayout->addWidget(_fontSizeSelector); fontDetailsLayout->addWidget(new QLabel(tr("Weight:"))); fontDetailsLayout->addWidget(_fontWeightSelector); fontDetailsLayout->addWidget(new QLabel(tr("Hinting:"))); fontDetailsLayout->addWidget(_fontHinting); fontConverterLayout->addWidget(new QLabel(tr("Characters to convert:"))); _fontCharacters = new QPlainTextEdit(); _fontCharacters->setFixedHeight(100); _fontCharacters->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); _fontCharacters->setWordWrapMode(QTextOption::WrapAnywhere); _fontCharacters->setPlainText("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,:;!?()+-=\"'<>"); _fontCharacters->setReadOnly(true); fontConverterLayout->addWidget(_fontCharacters); connect(_fontCharacters, &QPlainTextEdit::textChanged, this, &MainWindow::onCharactersChanged); auto previewSplittter = new QSplitter(Qt::Vertical); previewSplittter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); previewSplittter->setChildrenCollapsible(false); mainLayout->addWidget(previewSplittter); _bitmapPanel = new BitmapPanel(); previewSplittter->addWidget(_bitmapPanel); auto codePanel = new QWidget(); codePanel->setMinimumHeight(200); auto codeLayout = new QVBoxLayout(codePanel); codeLayout->setContentsMargins(16, 4, 16, 16); codeLayout->setSpacing(4); codeLayout->addWidget(new QLabel(tr("Generated Code:"))); _codePreview = new QPlainTextEdit(); _codePreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); _codePreview->setReadOnly(true); _codePreview->setWordWrapMode(QTextOption::NoWrap); codeLayout->addWidget(_codePreview); const auto monospaceFont = gApp()->monospaceFont(); _codePreview->setFont(monospaceFont); _fontCharacters->setFont(monospaceFont); auto codeActions = new QFrame(); auto codeActionsLayout = new QHBoxLayout(codeActions); codeActionsLayout->setContentsMargins(0, 0, 0, 0); codeActionsLayout->setSpacing(4); codeActionsLayout->addStretch(); auto copyCodeButton = new QPushButton(); copyCodeButton->setText(tr("Copy Code")); codeActionsLayout->addWidget(copyCodeButton); connect(copyCodeButton, &QPushButton::clicked, [=]{ qApp->clipboard()->setText(_codePreview->toPlainText()); }); codeLayout->addWidget(codeActions); previewSplittter->addWidget(codePanel); previewSplittter->setStretchFactor(0, 4); previewSplittter->setStretchFactor(1, 1); connect(_formatSelector, &QComboBox::currentIndexChanged, this, &MainWindow::onFormatChanged); connect(_fontSelector, &QFontComboBox::currentFontChanged, this, &MainWindow::onFontChanged); connect(_fontSizeSelector, &QSpinBox::valueChanged, this, &MainWindow::onFontChanged); connect(_fontWeightSelector, &QComboBox::currentIndexChanged, this, &MainWindow::onFontChanged); connect(_fontHinting, &QComboBox::currentIndexChanged, this, &MainWindow::onFontChanged); connect(_bitmapPanel, &BitmapPanel::selectedCharacterChanged, this, &MainWindow::onSelectedCharacterChanged); } void MainWindow::initializeMenu() { auto menuFile = menuBar()->addMenu(tr("File")); auto actionLoadBitmap = menuFile->addAction(tr("Load Bitmap...")); actionLoadBitmap->setShortcut(QKeySequence("Ctrl+O")); connect(actionLoadBitmap, &QAction::triggered, this, &MainWindow::onLoadBitmap); auto actionQuit = menuFile->addAction(tr("Quit")); connect(actionQuit, &QAction::triggered, [=]{ qApp->quit(); }); auto menuEdit = menuBar()->addMenu(tr("Edit")); menuEdit->addAction(tr("Cut"), [=]{ QMetaObject::invokeMethod(focusWidget(), "cut"); }, QKeySequence("Ctrl+X")); menuEdit->addAction(tr("Copy"), [=]{ QMetaObject::invokeMethod(focusWidget(), "copy"); }, QKeySequence("Ctrl+C")); menuEdit->addAction(tr("Paste"), [=]{ QMetaObject::invokeMethod(focusWidget(), "paste"); }, QKeySequence("Ctrl+V")); auto menuHelp = menuBar()->addMenu(tr("Help")); auto actionAbout = menuHelp->addAction(tr("About...")); menuHelp->addAction(tr("Lucky Resistor..."), []{ QDesktopServices::openUrl(QUrl("https://luckyresistor.me/")); }); menuHelp->addAction(tr("Project Page..."), []{ QDesktopServices::openUrl(QUrl("https://luckyresistor.me/applications/micropython-bitmap-tool/")); }); connect(actionAbout, &QAction::triggered, [=]{ QMessageBox::about(this, tr("Micropython Bitmap Tool by Lucky Resistor"), tr("

Micropython Bitmap Tool

" "

Copyright (c)2021 by Lucky Resistor

" "

Version %1

" "

License

" "

This program 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.

" "

This program 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 this program. If not, see https://www.gnu.org/licenses/.

").arg(qApp->applicationVersion())); }); auto actionAboutQt = menuHelp->addAction(tr("Information About Qt...")); connect(actionAboutQt, &QAction::triggered, [=]{ QMessageBox::aboutQt(this); }); } void MainWindow::loadSettings() { QSettings settings; restoreGeometry(settings.value("mainWindow.geometry").toByteArray()); restoreState(settings.value("mainWindow.state").toByteArray()); _formatSelector->setCurrentIndex(settings.value("format.index").toInt(0)); _currentFont = QFont(settings.value("font.family", "Arial").toString()); _currentFont.setPixelSize(settings.value("font.size", 12).toInt()); _currentFont.setWeight(static_cast(settings.value("font.weight", 500).toInt())); _fontSelector->setCurrentFont(_currentFont); _fontSizeSelector->setValue(_currentFont.pixelSize()); const int index = _fontWeightSelector->findData(static_cast(_currentFont.weight())); if (index >= 0) { _fontWeightSelector->setCurrentIndex(index); } else { _fontWeightSelector->setCurrentIndex(3); // normal } _fontHinting->setCurrentIndex(settings.value("font.hinting", 0).toInt()); } void MainWindow::saveSettings() { QSettings settings; settings.setValue("mainWindow.geometry", saveGeometry()); settings.setValue("mainWindow.state", saveState()); settings.setValue("format.index", _formatSelector->currentIndex()); settings.setValue("font.family", _currentFont.family()); settings.setValue("font.size", _currentFont.pixelSize()); settings.setValue("font.weight", static_cast(_currentFont.weight())); settings.setValue("font.hinting", _fontHinting->currentIndex()); } Converter *MainWindow::selectedConverter() const { auto converter = _converterList.value(_formatSelector->currentIndex()); if (converter == nullptr) { converter = _converterList.first(); } return converter; } void MainWindow::updateInfoBox() { QString text; QTextStream ts(&text); if (selectedConverter()->mode() == Converter::Mode::Bitmap) { ts << "

"; ts << "Bitmap Size: " << _currentImage.width() << "x" << _currentImage.height() << "
"; ts << "Bit Depth: " << _currentImage.depth() << "
"; ts << "Color Format: "; switch (_currentImage.pixelFormat().colorModel()) { case QPixelFormat::RGB: ts << "RGB"; break; case QPixelFormat::BGR: ts << "BGR"; break; case QPixelFormat::Indexed: ts << "Indexed"; break; case QPixelFormat::Grayscale: ts << "Grayscale"; break; case QPixelFormat::CMYK: ts << "CMYK"; break; case QPixelFormat::HSL: ts << "HSL"; break; case QPixelFormat::HSV: ts << "HSV"; break; case QPixelFormat::YUV: ts << "YUV"; break; case QPixelFormat::Alpha: ts << "Alpha"; break; } ts << "
"; const auto gs = selectedConverter()->generatedSize(_currentImage.size(), createParameterMap()); ts << "Generated Size: " << gs.width() << "x" << gs.height() << "

"; _bitmapInfo->setText(text); } else { ts << "

"; ts << "Family: " << _currentFont.family() << "
"; ts << "Weight: "; switch (_currentFont.weight()) { case QFont::Thin: ts << tr("Thin"); break; case QFont::ExtraLight: ts << tr("Extra Light"); break; case QFont::Light: ts << tr("Light"); break; case QFont::Normal: ts << tr("Normal"); break; case QFont::Medium: ts << tr("Medium"); break; case QFont::DemiBold: ts << tr("Demi Bold"); break; case QFont::Bold: ts << tr("Bold"); break; case QFont::ExtraBold: ts << tr("Extra Bold"); break; case QFont::Black: ts << tr("Black"); break; } ts << "
"; ts << "Pixel Size: " << _currentFont.pixelSize() << "
"; ts << "

"; _fontInfo->setText(text); } } void MainWindow::updateCode() { QString code; if (selectedConverter()->mode() == Converter::Mode::Bitmap) { auto bitmapConverter = static_cast(selectedConverter()); if (!_currentImage.isNull()) { code = bitmapConverter->generateCode(_currentImage, QVariantMap()); } } else { auto fontConverter = static_cast(selectedConverter()); code = fontConverter->generateCode(_currentFont, createParameterMap()); } _codePreview->setPlainText(code); } void MainWindow::updateParameters() { auto converter = selectedConverter(); if (converter == nullptr) { return; } // Replace the current parameter definition. _displayedParameters = converter->createParameterDefinition(); // Remove the previous UI while (_parameterLayout->count() > 0) { auto item = _parameterLayout->takeAt(0); delete item; } for (auto w : _parameterFrame->findChildren()) { w->deleteLater(); } // Build the new parameter UI. if (_displayedParameters->parameterList().isEmpty()) { auto label = new QLabel(); label->setText(tr("No Parameters")); _parameterLayout->addWidget(label); } else { for (const auto ¶meter : _displayedParameters->parameterList()) { auto pw = parameterFactory.createWidget(parameter); pw->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); pw->setObjectName(parameter->identifier()); _parameterLayout->addRow(parameter->label() + ":", pw); connect(pw, &ParameterWidget::valueChanged, this, &MainWindow::onParameterChanged); } } auto stretchItem = new QWidget(); stretchItem->setFixedWidth(10); stretchItem->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); _parameterLayout->addRow(stretchItem); } QVariantMap MainWindow::createParameterMap() const { QVariantMap result; result["characters"] = _fontCharacters->toPlainText(); result["font"] = _currentFont; result["char"] = _bitmapPanel->selectedCharacter(); for (auto pw : _parameterFrame->findChildren()) { result[pw->objectName()] = pw->value(); } return result; } void MainWindow::onLoadBitmap() { QSettings settings; auto lastDir = settings.value("bitmap.lastDir").toString(); auto fileName = QFileDialog::getOpenFileName( this, tr("Open Bitmap"), lastDir, tr("Images (*.png *.jpg *.jpeg *.gif *.pbm *.pgm *.ppm *.xbm *.xpm *.bpm)")); if (fileName.isEmpty()) { return; } QFileInfo fileInfo(fileName); settings.setValue("bitmap.lastDir", fileInfo.absoluteDir().path()); QImage newImage; if (!newImage.load(fileName)) { QMessageBox::warning(this, tr("Loading Image Failed"), tr("

Failed to load the image

" "

Could not load the specified image file. " "Make sure the image is in a supported format.

")); return; } if (newImage.width() > 0x100 || newImage.height() > 0x100) { QMessageBox::warning(this, tr("Image is Too Large"), tr("

Image is Too Large

" "

One dimension of the loaded image is larger than " "256 pixel. This software is designed for small bitmaps.

")); return; } _currentImage = newImage; _bitmapPanel->setImage(newImage); _bitmapPanel->setConverter(selectedConverter()); updateInfoBox(); updateCode(); } void MainWindow::onFormatChanged() { const auto converter = selectedConverter(); _bitmapPanel->setConverter(converter); if (converter->mode() == Converter::Mode::Bitmap) { _bitmapConverterFrame->setVisible(true); _fontConverterFrame->setVisible(false); } else { _bitmapConverterFrame->setVisible(false); _fontConverterFrame->setVisible(true); } updateParameters(); updateInfoBox(); updateCode(); _bitmapPanel->setParameter(createParameterMap()); onSelectedCharacterChanged(_bitmapPanel->selectedCharacter()); } void MainWindow::onFontChanged() { _currentFont = _fontSelector->currentFont(); _currentFont.setWeight(static_cast(_fontWeightSelector->currentData().toInt())); _currentFont.setPixelSize(_fontSizeSelector->value()); const auto hintingPreferences = static_cast(_fontHinting->currentData().toInt()); _currentFont.setHintingPreference(hintingPreferences); updateInfoBox(); updateCode(); _bitmapPanel->setParameter(createParameterMap()); onSelectedCharacterChanged(_bitmapPanel->selectedCharacter()); } void MainWindow::onCharactersChanged() { auto characters = _fontCharacters->toPlainText(); _bitmapPanel->setCharacters(characters); _bitmapPanel->setConverter(selectedConverter()); _bitmapPanel->setParameter(createParameterMap()); onSelectedCharacterChanged(_bitmapPanel->selectedCharacter()); } void MainWindow::onSelectedCharacterChanged(QChar c) { auto converter = selectedConverter(); if (converter == nullptr) { return; } if (converter->mode() == Converter::Mode::Font) { auto fontConverter = static_cast(converter); auto newImage = fontConverter->generateImage(_currentFont, c, createParameterMap()); _currentImage = newImage; _bitmapPanel->setImage(newImage); _bitmapPanel->setConverter(selectedConverter()); _bitmapPanel->setParameter(createParameterMap()); } } void MainWindow::onParameterChanged() { _bitmapPanel->setParameter(createParameterMap()); onSelectedCharacterChanged(_bitmapPanel->selectedCharacter()); } void MainWindow::closeEvent(QCloseEvent *event) { saveSettings(); QMainWindow::closeEvent(event); }