mirror of https://github.com/mgba-emu/mgba.git
GB SIO: Game Boy Printer
This commit is contained in:
parent
86901d93b6
commit
dc976eaf51
1
CHANGES
1
CHANGES
|
@ -3,6 +3,7 @@ Features:
|
|||
- ELF support
|
||||
- Game Boy Camera support
|
||||
- Qt: Set default Game Boy colors
|
||||
- Game Boy Printer support
|
||||
Bugfixes:
|
||||
- GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749)
|
||||
- Python: Fix importing .gb or .gba before .core
|
||||
|
|
|
@ -57,7 +57,7 @@ file(GLOB GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_CURRENT_S
|
|||
file(GLOB GBA_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/renderers/*.c)
|
||||
file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c)
|
||||
file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/*.c)
|
||||
file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/lockstep.c)
|
||||
file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/*.c)
|
||||
file(GLOB GB_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/renderers/*.c)
|
||||
file(GLOB GB_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/extra/*.c)
|
||||
file(GLOB THIRD_PARTY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/inih/*.c)
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GB_PRINTER_H
|
||||
#define GB_PRINTER_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/gb/interface.h>
|
||||
|
||||
enum GBPrinterPacketByte {
|
||||
GB_PRINTER_BYTE_MAGIC_0,
|
||||
GB_PRINTER_BYTE_MAGIC_1,
|
||||
GB_PRINTER_BYTE_COMMAND,
|
||||
GB_PRINTER_BYTE_COMPRESSION,
|
||||
GB_PRINTER_BYTE_LENGTH_0,
|
||||
GB_PRINTER_BYTE_LENGTH_1,
|
||||
GB_PRINTER_BYTE_DATA,
|
||||
GB_PRINTER_BYTE_CHECKSUM_0,
|
||||
GB_PRINTER_BYTE_CHECKSUM_1,
|
||||
GB_PRINTER_BYTE_KEEPALIVE,
|
||||
GB_PRINTER_BYTE_STATUS,
|
||||
|
||||
GB_PRINTER_BYTE_COMPRESSED_DATUM,
|
||||
GB_PRINTER_BYTE_UNCOMPRESSED_DATA
|
||||
};
|
||||
|
||||
enum GBPrinterStatus {
|
||||
GB_PRINTER_STATUS_CHECKSUM_ERROR = 0x01,
|
||||
GB_PRINTER_STATUS_PRINTING = 0x02,
|
||||
GB_PRINTER_STATUS_PRINT_REQ = 0x04,
|
||||
GB_PRINTER_STATUS_READY = 0x08,
|
||||
GB_PRINTER_STATUS_LOW_BATTERY = 0x10,
|
||||
GB_PRINTER_STATUS_TIMEOUT = 0x20,
|
||||
GB_PRINTER_STATUS_PAPER_JAM = 0x40,
|
||||
GB_PRINTER_STATUS_TEMPERATURE_ISSUE = 0x80
|
||||
};
|
||||
|
||||
enum GBPrinterCommand {
|
||||
GB_PRINTER_COMMAND_INIT = 0x1,
|
||||
GB_PRINTER_COMMAND_PRINT = 0x2,
|
||||
GB_PRINTER_COMMAND_DATA = 0x4,
|
||||
GB_PRINTER_COMMAND_STATUS = 0xF,
|
||||
};
|
||||
|
||||
struct GBPrinter {
|
||||
struct GBSIODriver d;
|
||||
|
||||
void (*print)(struct GBPrinter*, int height, const uint8_t* data);
|
||||
|
||||
uint8_t* buffer;
|
||||
uint16_t checksum;
|
||||
enum GBPrinterCommand command;
|
||||
uint16_t remainingBytes;
|
||||
uint8_t remainingCmpBytes;
|
||||
unsigned currentIndex;
|
||||
bool compression;
|
||||
|
||||
uint8_t byte;
|
||||
enum GBPrinterPacketByte next;
|
||||
uint8_t status;
|
||||
int printWait;
|
||||
};
|
||||
|
||||
void GBPrinterCreate(struct GBPrinter* printer);
|
||||
void GBPrinterDonePrinting(struct GBPrinter* printer);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -0,0 +1,233 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/internal/gb/sio/printer.h>
|
||||
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/io.h>
|
||||
|
||||
|
||||
static bool GBPrinterInit(struct GBSIODriver* driver);
|
||||
static void GBPrinterDeinit(struct GBSIODriver* driver);
|
||||
static void GBPrinterWriteSB(struct GBSIODriver* driver, uint8_t value);
|
||||
static uint8_t GBPrinterWriteSC(struct GBSIODriver* driver, uint8_t value);
|
||||
|
||||
void GBPrinterCreate(struct GBPrinter* printer) {
|
||||
printer->d.init = GBPrinterInit;
|
||||
printer->d.deinit = GBPrinterDeinit;
|
||||
printer->d.writeSB = GBPrinterWriteSB;
|
||||
printer->d.writeSC = GBPrinterWriteSC;
|
||||
printer->print = NULL;
|
||||
}
|
||||
|
||||
bool GBPrinterInit(struct GBSIODriver* driver) {
|
||||
struct GBPrinter* printer = (struct GBPrinter*) driver;
|
||||
|
||||
printer->checksum = 0;
|
||||
printer->command = 0;
|
||||
printer->remainingBytes = 0;
|
||||
printer->currentIndex = 0;
|
||||
printer->compression = false;
|
||||
printer->byte = 0;
|
||||
printer->next = GB_PRINTER_BYTE_MAGIC_0;
|
||||
printer->status = 0;
|
||||
printer->printWait = -1;
|
||||
|
||||
printer->buffer = malloc(GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS / 2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBPrinterDeinit(struct GBSIODriver* driver) {
|
||||
struct GBPrinter* printer = (struct GBPrinter*) driver;
|
||||
free(printer->buffer);
|
||||
}
|
||||
|
||||
static void GBPrinterWriteSB(struct GBSIODriver* driver, uint8_t value) {
|
||||
struct GBPrinter* printer = (struct GBPrinter*) driver;
|
||||
printer->byte = value;
|
||||
}
|
||||
|
||||
static void _processByte(struct GBPrinter* printer) {
|
||||
switch (printer->command) {
|
||||
case GB_PRINTER_COMMAND_DATA:
|
||||
if (printer->currentIndex < GB_VIDEO_VERTICAL_PIXELS * GB_VIDEO_HORIZONTAL_PIXELS / 2) {
|
||||
printer->buffer[printer->currentIndex] = printer->byte;
|
||||
++printer->currentIndex;
|
||||
}
|
||||
break;
|
||||
case GB_PRINTER_COMMAND_PRINT:
|
||||
// TODO
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t GBPrinterWriteSC(struct GBSIODriver* driver, uint8_t value) {
|
||||
struct GBPrinter* printer = (struct GBPrinter*) driver;
|
||||
if ((value & 0x81) == 0x81) {
|
||||
switch (printer->next) {
|
||||
driver->p->pendingSB = 0;
|
||||
case GB_PRINTER_BYTE_MAGIC_0:
|
||||
if (printer->byte == 0x88) {
|
||||
printer->next = GB_PRINTER_BYTE_MAGIC_1;
|
||||
} else {
|
||||
printer->next = GB_PRINTER_BYTE_MAGIC_0;
|
||||
}
|
||||
break;
|
||||
case GB_PRINTER_BYTE_MAGIC_1:
|
||||
if (printer->byte == 0x33) {
|
||||
printer->next = GB_PRINTER_BYTE_COMMAND;
|
||||
} else {
|
||||
printer->next = GB_PRINTER_BYTE_MAGIC_0;
|
||||
}
|
||||
break;
|
||||
case GB_PRINTER_BYTE_COMMAND:
|
||||
printer->checksum = printer->byte;
|
||||
printer->command = printer->byte;
|
||||
printer->next = GB_PRINTER_BYTE_COMPRESSION;
|
||||
break;
|
||||
case GB_PRINTER_BYTE_COMPRESSION:
|
||||
printer->checksum += printer->byte;
|
||||
printer->compression = printer->byte;
|
||||
printer->next = GB_PRINTER_BYTE_LENGTH_0;
|
||||
break;
|
||||
case GB_PRINTER_BYTE_LENGTH_0:
|
||||
printer->checksum += printer->byte;
|
||||
printer->remainingBytes = printer->byte;
|
||||
printer->next = GB_PRINTER_BYTE_LENGTH_1;
|
||||
break;
|
||||
case GB_PRINTER_BYTE_LENGTH_1:
|
||||
printer->checksum += printer->byte;
|
||||
printer->remainingBytes |= printer->byte << 8;
|
||||
if (printer->remainingBytes) {
|
||||
printer->next = GB_PRINTER_BYTE_DATA;
|
||||
} else {
|
||||
printer->next = GB_PRINTER_BYTE_CHECKSUM_0;
|
||||
}
|
||||
switch (printer->command) {
|
||||
case GB_PRINTER_COMMAND_INIT:
|
||||
printer->currentIndex = 0;
|
||||
printer->status &= ~(GB_PRINTER_STATUS_PRINT_REQ | GB_PRINTER_STATUS_READY);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GB_PRINTER_BYTE_DATA:
|
||||
printer->checksum += printer->byte;
|
||||
if (!printer->compression) {
|
||||
_processByte(printer);
|
||||
} else {
|
||||
printer->next = printer->byte & 0x80 ? GB_PRINTER_BYTE_COMPRESSED_DATUM : GB_PRINTER_BYTE_UNCOMPRESSED_DATA;
|
||||
printer->remainingCmpBytes = (printer->byte & 0x7F) + 1;
|
||||
if (printer->byte & 0x80) {
|
||||
++printer->remainingCmpBytes;
|
||||
}
|
||||
}
|
||||
--printer->remainingBytes;
|
||||
if (!printer->remainingBytes) {
|
||||
printer->next = GB_PRINTER_BYTE_CHECKSUM_0;
|
||||
}
|
||||
break;
|
||||
case GB_PRINTER_BYTE_UNCOMPRESSED_DATA:
|
||||
printer->checksum += printer->byte;
|
||||
_processByte(printer);
|
||||
--printer->remainingCmpBytes;
|
||||
if (!printer->remainingCmpBytes) {
|
||||
printer->next = GB_PRINTER_BYTE_DATA;
|
||||
}
|
||||
--printer->remainingBytes;
|
||||
if (!printer->remainingBytes) {
|
||||
printer->next = GB_PRINTER_BYTE_CHECKSUM_0;
|
||||
}
|
||||
break;
|
||||
case GB_PRINTER_BYTE_COMPRESSED_DATUM:
|
||||
printer->checksum += printer->byte;
|
||||
while (printer->remainingCmpBytes) {
|
||||
_processByte(printer);
|
||||
--printer->remainingCmpBytes;
|
||||
}
|
||||
--printer->remainingBytes;
|
||||
if (!printer->remainingBytes) {
|
||||
printer->next = GB_PRINTER_BYTE_CHECKSUM_0;
|
||||
} else {
|
||||
printer->next = GB_PRINTER_BYTE_DATA;
|
||||
}
|
||||
break;
|
||||
case GB_PRINTER_BYTE_CHECKSUM_0:
|
||||
printer->checksum ^= printer->byte;
|
||||
printer->next = GB_PRINTER_BYTE_CHECKSUM_1;
|
||||
break;
|
||||
case GB_PRINTER_BYTE_CHECKSUM_1:
|
||||
printer->checksum ^= printer->byte << 8;
|
||||
printer->next = GB_PRINTER_BYTE_KEEPALIVE;
|
||||
break;
|
||||
case GB_PRINTER_BYTE_KEEPALIVE:
|
||||
driver->p->pendingSB = 0x81;
|
||||
printer->next = GB_PRINTER_BYTE_STATUS;
|
||||
break;
|
||||
case GB_PRINTER_BYTE_STATUS:
|
||||
switch (printer->command) {
|
||||
case GB_PRINTER_COMMAND_DATA:
|
||||
if (printer->currentIndex >= 0x280 && !(printer->status & GB_PRINTER_STATUS_CHECKSUM_ERROR)) {
|
||||
printer->status |= GB_PRINTER_STATUS_READY;
|
||||
}
|
||||
break;
|
||||
case GB_PRINTER_COMMAND_PRINT:
|
||||
if (printer->currentIndex >= GB_VIDEO_HORIZONTAL_PIXELS * 2) {
|
||||
printer->printWait = 0;
|
||||
}
|
||||
break;
|
||||
case GB_PRINTER_COMMAND_STATUS:
|
||||
if (!printer->printWait) {
|
||||
printer->status &= ~GB_PRINTER_STATUS_READY;
|
||||
printer->status |= GB_PRINTER_STATUS_PRINTING | GB_PRINTER_STATUS_PRINT_REQ;
|
||||
if (printer->print) {
|
||||
size_t y;
|
||||
for (y = 0; y < printer->currentIndex / (2 * GB_VIDEO_HORIZONTAL_PIXELS); ++y) {
|
||||
uint8_t lineBuffer[GB_VIDEO_HORIZONTAL_PIXELS * 2];
|
||||
uint8_t* buffer = &printer->buffer[sizeof(lineBuffer) * y];
|
||||
size_t i;
|
||||
for (i = 0; i < sizeof(lineBuffer); i += 2) {
|
||||
uint8_t ilo = buffer[i + 0x0];
|
||||
uint8_t ihi = buffer[i + 0x1];
|
||||
uint8_t olo = 0;
|
||||
uint8_t ohi = 0;
|
||||
olo |= ((ihi & 0x80) >> 0) | ((ilo & 0x80) >> 1);
|
||||
olo |= ((ihi & 0x40) >> 1) | ((ilo & 0x40) >> 2);
|
||||
olo |= ((ihi & 0x20) >> 2) | ((ilo & 0x20) >> 3);
|
||||
olo |= ((ihi & 0x10) >> 3) | ((ilo & 0x10) >> 4);
|
||||
ohi |= ((ihi & 0x08) << 4) | ((ilo & 0x08) << 3);
|
||||
ohi |= ((ihi & 0x04) << 3) | ((ilo & 0x04) << 2);
|
||||
ohi |= ((ihi & 0x02) << 2) | ((ilo & 0x02) << 1);
|
||||
ohi |= ((ihi & 0x01) << 1) | ((ilo & 0x01) << 0);
|
||||
lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) & ~1)] = olo;
|
||||
lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) | 1)] = ohi;
|
||||
}
|
||||
memcpy(buffer, lineBuffer, sizeof(lineBuffer));
|
||||
}
|
||||
printer->print(printer, printer->currentIndex * 4 / GB_VIDEO_HORIZONTAL_PIXELS, printer->buffer);
|
||||
}
|
||||
}
|
||||
if (printer->printWait >= 0) {
|
||||
--printer->printWait;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
driver->p->pendingSB = printer->status;
|
||||
printer->next = GB_PRINTER_BYTE_MAGIC_0;
|
||||
break;
|
||||
}
|
||||
printer->byte = 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void GBPrinterDonePrinting(struct GBPrinter* printer) {
|
||||
printer->status &= ~GB_PRINTER_STATUS_PRINTING;
|
||||
}
|
|
@ -93,6 +93,7 @@ set(SOURCE_FILES
|
|||
ObjView.cpp
|
||||
OverrideView.cpp
|
||||
PaletteView.cpp
|
||||
PrinterView.cpp
|
||||
ROMInfo.cpp
|
||||
SavestateButton.cpp
|
||||
SensorView.cpp
|
||||
|
@ -123,6 +124,7 @@ set(UI_FILES
|
|||
ObjView.ui
|
||||
OverrideView.ui
|
||||
PaletteView.ui
|
||||
PrinterView.ui
|
||||
ROMInfo.ui
|
||||
SensorView.ui
|
||||
SettingsView.ui
|
||||
|
|
|
@ -590,6 +590,59 @@ void CoreController::exportSharkport(const QString& path) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void CoreController::attachPrinter() {
|
||||
#ifdef M_CORE_GB
|
||||
if (platform() != PLATFORM_GB) {
|
||||
return;
|
||||
}
|
||||
GB* gb = static_cast<GB*>(m_threadContext.core->board);
|
||||
clearMultiplayerController();
|
||||
GBPrinterCreate(&m_printer.d);
|
||||
m_printer.parent = this;
|
||||
m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) {
|
||||
QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer);
|
||||
QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8);
|
||||
QVector<QRgb> colors;
|
||||
colors.append(qRgb(0xF8, 0xF8, 0xF8));
|
||||
colors.append(qRgb(0xA8, 0xA8, 0xA8));
|
||||
colors.append(qRgb(0x50, 0x50, 0x50));
|
||||
colors.append(qRgb(0x00, 0x00, 0x00));
|
||||
image.setColorTable(colors);
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
|
||||
uint8_t byte = data[(x + y * GB_VIDEO_HORIZONTAL_PIXELS) / 4];
|
||||
image.setPixel(x + 0, y, (byte & 0xC0) >> 6);
|
||||
image.setPixel(x + 1, y, (byte & 0x30) >> 4);
|
||||
image.setPixel(x + 2, y, (byte & 0x0C) >> 2);
|
||||
image.setPixel(x + 3, y, (byte & 0x03) >> 0);
|
||||
}
|
||||
}
|
||||
QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image));
|
||||
};
|
||||
GBSIOSetDriver(&gb->sio, &m_printer.d.d);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::detachPrinter() {
|
||||
#ifdef M_CORE_GB
|
||||
if (platform() != PLATFORM_GB) {
|
||||
return;
|
||||
}
|
||||
GB* gb = static_cast<GB*>(m_threadContext.core->board);
|
||||
GBPrinterDonePrinting(&m_printer.d);
|
||||
GBSIOSetDriver(&gb->sio, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::endPrint() {
|
||||
#ifdef M_CORE_GB
|
||||
if (platform() != PLATFORM_GB) {
|
||||
return;
|
||||
}
|
||||
GBPrinterDonePrinting(&m_printer.d);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::setAVStream(mAVStream* stream) {
|
||||
Interrupter interrupter(this);
|
||||
m_threadContext.core->setAVStream(m_threadContext.core, stream);
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
#include <mgba/core/thread.h>
|
||||
#include <mgba/core/tile-cache.h>
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/sio/printer.h>
|
||||
#endif
|
||||
|
||||
struct mCore;
|
||||
|
||||
namespace QGBA {
|
||||
|
@ -123,6 +127,10 @@ public slots:
|
|||
void importSharkport(const QString& path);
|
||||
void exportSharkport(const QString& path);
|
||||
|
||||
void attachPrinter();
|
||||
void detachPrinter();
|
||||
void endPrint();
|
||||
|
||||
void setAVStream(mAVStream*);
|
||||
void clearAVStream();
|
||||
|
||||
|
@ -149,6 +157,8 @@ signals:
|
|||
void statusPosted(const QString& message);
|
||||
void logPosted(int level, int category, const QString& log);
|
||||
|
||||
void imagePrinted(const QImage&);
|
||||
|
||||
private:
|
||||
void updateKeys();
|
||||
int updateAutofire();
|
||||
|
@ -195,6 +205,13 @@ private:
|
|||
|
||||
mVideoLogContext* m_vl = nullptr;
|
||||
VFile* m_vlVf = nullptr;
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
struct QGBPrinter {
|
||||
GBPrinter d;
|
||||
CoreController* parent;
|
||||
} m_printer;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -233,6 +233,9 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
|||
}
|
||||
|
||||
void MultiplayerController::detachGame(CoreController* controller) {
|
||||
if (m_players.empty()) {
|
||||
return;
|
||||
}
|
||||
mCoreThread* thread = controller->thread();
|
||||
if (!thread) {
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "PrinterView.h"
|
||||
|
||||
#include "CoreController.h"
|
||||
#include "GBAApp.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
PrinterView::PrinterView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_controller(controller)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
connect(controller.get(), &CoreController::imagePrinted, this, &PrinterView::printImage);
|
||||
connect(&m_timer, &QTimer::timeout, this, &PrinterView::printLine);
|
||||
connect(m_ui.hurry, &QAbstractButton::clicked, this, &PrinterView::printAll);
|
||||
connect(m_ui.tear, &QAbstractButton::clicked, this, &PrinterView::clear);
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &PrinterView::save);
|
||||
m_timer.setInterval(80);
|
||||
clear();
|
||||
}
|
||||
|
||||
PrinterView::~PrinterView() {
|
||||
m_controller->detachPrinter();
|
||||
}
|
||||
|
||||
void PrinterView::save() {
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Save Printout"), tr("Portable Network Graphics (*.png)"));
|
||||
if (filename.isNull()) {
|
||||
return;
|
||||
}
|
||||
m_image.save(filename);
|
||||
}
|
||||
|
||||
void PrinterView::clear() {
|
||||
m_ui.image->setFixedHeight(0);
|
||||
m_ui.image->clear();
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
|
||||
}
|
||||
|
||||
void PrinterView::printImage(const QImage& image) {
|
||||
QPixmap pixmap(image.width(), image.height() + m_image.height());
|
||||
QPainter painter(&pixmap);
|
||||
painter.drawPixmap(0, 0, m_image);
|
||||
painter.drawImage(0, m_image.height(), image);
|
||||
m_image = pixmap;
|
||||
m_ui.image->setPixmap(m_image);
|
||||
m_timer.start();
|
||||
m_ui.hurry->setEnabled(true);
|
||||
}
|
||||
|
||||
void PrinterView::printLine() {
|
||||
m_ui.image->setFixedHeight(m_ui.image->height() + 1);
|
||||
m_ui.scrollArea->ensureVisible(0, m_ui.image->height(), 0, 0);
|
||||
if (m_ui.image->height() >= m_image.height()) {
|
||||
printAll();
|
||||
}
|
||||
}
|
||||
|
||||
void PrinterView::printAll() {
|
||||
m_timer.stop();
|
||||
m_ui.image->setFixedHeight(m_image.height());
|
||||
m_controller->endPrint();
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(true);
|
||||
m_ui.hurry->setEnabled(false);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_PRINTER_VIEW
|
||||
#define QGBA_PRINTER_VIEW
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPixmap>
|
||||
#include <QTimer>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ui_PrinterView.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class CoreController;
|
||||
|
||||
class PrinterView : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PrinterView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||
~PrinterView();
|
||||
|
||||
signals:
|
||||
void donePrinting();
|
||||
|
||||
public slots:
|
||||
void save();
|
||||
void clear();
|
||||
|
||||
private slots:
|
||||
void printImage(const QImage&);
|
||||
void printLine();
|
||||
void printAll();
|
||||
|
||||
private:
|
||||
Ui::PrinterView m_ui;
|
||||
QPixmap m_image;
|
||||
QTimer m_timer;
|
||||
|
||||
std::shared_ptr<CoreController> m_controller;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,137 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PrinterView</class>
|
||||
<widget class="QWidget" name="PrinterView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>241</width>
|
||||
<height>311</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Game Boy Printer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="image">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignHCenter|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="hurry">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hurry up!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="tear">
|
||||
<property name="text">
|
||||
<string>Tear off</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>PrinterView</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>112</x>
|
||||
<y>226</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>112</x>
|
||||
<y>123</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
|
@ -41,6 +41,7 @@
|
|||
#include "OverrideView.h"
|
||||
#include "ObjView.h"
|
||||
#include "PaletteView.h"
|
||||
#include "PrinterView.h"
|
||||
#include "ROMInfo.h"
|
||||
#include "SensorView.h"
|
||||
#include "SettingsView.h"
|
||||
|
@ -1358,9 +1359,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
fpsTargetOption->addValue(tr("240"), 240, target);
|
||||
m_config->updateOption("fpsTarget");
|
||||
|
||||
#if defined(USE_PNG) || defined(USE_FFMPEG) || defined(USE_MAGICK)
|
||||
avMenu->addSeparator();
|
||||
#endif
|
||||
|
||||
#ifdef USE_PNG
|
||||
QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu);
|
||||
|
@ -1397,6 +1396,18 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
addControlledAction(avMenu, stopVL, "stopVL");
|
||||
m_gameActions.append(stopVL);
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
QAction* gbPrint = new QAction(tr("Game Boy Printer..."), avMenu);
|
||||
connect(gbPrint, &QAction::triggered, [this]() {
|
||||
PrinterView* view = new PrinterView(m_controller);
|
||||
openView(view);
|
||||
m_controller->attachPrinter();
|
||||
|
||||
});
|
||||
addControlledAction(avMenu, gbPrint, "gbPrint");
|
||||
m_gameActions.append(gbPrint);
|
||||
#endif
|
||||
|
||||
avMenu->addSeparator();
|
||||
m_videoLayers = avMenu->addMenu(tr("Video layers"));
|
||||
m_shortcutController->addMenu(m_videoLayers, avMenu);
|
||||
|
@ -1807,6 +1818,7 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
}
|
||||
|
||||
m_controller->start();
|
||||
m_controller->loadConfig(m_config);
|
||||
}
|
||||
|
||||
WindowBackground::WindowBackground(QWidget* parent)
|
||||
|
|
Loading…
Reference in New Issue