mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-16 04:13:50 +00:00
e0a677545e
Internal: Visible in: https://igarastudio.zendesk.com/agent/tickets/5772 With files from: https://igarastudio.zendesk.com/agent/tickets/5773
311 lines
7.4 KiB
C++
311 lines
7.4 KiB
C++
// Aseprite
|
|
// Copyright (C) 2018-2024 Igara Studio S.A.
|
|
// Copyright (C) 2001-2018 David Capello
|
|
//
|
|
// This program is distributed under the terms of
|
|
// the End-User License Agreement for Aseprite.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "app/console.h"
|
|
|
|
#include "app/app.h"
|
|
#include "app/context.h"
|
|
#include "app/i18n/strings.h"
|
|
#include "app/modules/gui.h"
|
|
#include "app/ui/main_window.h"
|
|
#include "app/ui/status_bar.h"
|
|
#include "base/memory.h"
|
|
#include "base/string.h"
|
|
#include "fmt/format.h"
|
|
#include "ui/system.h"
|
|
#include "ui/ui.h"
|
|
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <memory>
|
|
|
|
#define TRACE_CON(...) // TRACEARGS(__VA_ARGS__)
|
|
|
|
namespace app {
|
|
|
|
using namespace ui;
|
|
|
|
Console::ConsoleWindow* Console::m_console = nullptr;
|
|
|
|
class Console::ConsoleWindow final : public Window {
|
|
public:
|
|
ConsoleWindow() : Window(Window::WithTitleBar, Strings::debugger_console()),
|
|
m_textbox("", WORDWRAP),
|
|
m_button(Strings::debugger_cancel()) {
|
|
TRACE_CON("CON: ConsoleWindow this=", this);
|
|
|
|
m_button.Click.connect([this]{ closeWindow(&m_button); });
|
|
|
|
// When the main window is closed, we should close the console (in
|
|
// other case the main message loop will continue running for the
|
|
// console too).
|
|
m_mainWindowClosedConn =
|
|
App::instance()->mainWindow()->Close.connect(
|
|
[this]{ closeWindow(nullptr); });
|
|
|
|
// When the window is closed, we clear the text
|
|
Close.connect(
|
|
[this]{
|
|
m_mainWindowClosedConn.disconnect();
|
|
m_textbox.setText(std::string());
|
|
|
|
Console::m_console->deferDelete();
|
|
Console::m_console = nullptr;
|
|
TRACE_CON("CON: Close signal");
|
|
});
|
|
|
|
m_view.attachToView(&m_textbox);
|
|
|
|
ui::Grid* grid = new ui::Grid(1, false);
|
|
grid->addChildInCell(&m_view, 1, 1, HORIZONTAL | VERTICAL);
|
|
grid->addChildInCell(&m_button, 1, 1, CENTER);
|
|
addChild(grid);
|
|
|
|
m_textbox.setFocusMagnet(true);
|
|
m_button.setFocusMagnet(true);
|
|
m_view.setExpansive(true);
|
|
|
|
initTheme();
|
|
}
|
|
|
|
~ConsoleWindow() {
|
|
TRACE_CON("CON: ~ConsoleWindow this=", this);
|
|
}
|
|
|
|
void addMessage(const std::string& msg) {
|
|
if (!m_hasText) {
|
|
m_hasText = true;
|
|
centerConsole();
|
|
}
|
|
|
|
gfx::Size maxSize = m_view.getScrollableSize();
|
|
gfx::Size visible = m_view.visibleSize();
|
|
gfx::Point pt = m_view.viewScroll();
|
|
const bool autoScroll = (pt.y >= maxSize.h - visible.h);
|
|
|
|
m_textbox.setText(m_textbox.text() + msg);
|
|
|
|
if (autoScroll) {
|
|
maxSize = m_view.getScrollableSize();
|
|
visible = m_view.visibleSize();
|
|
pt.y = maxSize.h - visible.h;
|
|
m_view.setViewScroll(pt);
|
|
}
|
|
}
|
|
|
|
bool hasConsoleText() const {
|
|
return m_hasText;
|
|
}
|
|
|
|
void centerConsole() {
|
|
initTheme();
|
|
|
|
Display* display = ui::Manager::getDefault()->display();
|
|
const gfx::Rect displayRc = display->bounds();
|
|
gfx::Rect rc;
|
|
rc.w = displayRc.w*9/10;
|
|
rc.h = displayRc.h*6/10;
|
|
rc.x = displayRc.x + displayRc.w/2 - rc.w/2;
|
|
rc.y = displayRc.y + displayRc.h/2 - rc.h/2;
|
|
|
|
ui::fit_bounds(display, this, rc);
|
|
}
|
|
|
|
private:
|
|
// As Esc key activates the close button only on foreground windows,
|
|
// we have to override this method to allow pressing the window
|
|
// close button using Esc key even in this window (which runs in the
|
|
// background).
|
|
bool shouldProcessEscKeyToCloseWindow() const override {
|
|
return true;
|
|
}
|
|
|
|
bool onProcessMessage(ui::Message* msg) override {
|
|
switch (msg->type()) {
|
|
|
|
case ui::kKeyDownMessage: {
|
|
auto scancode = static_cast<KeyMessage*>(msg)->scancode();
|
|
|
|
#if defined __APPLE__
|
|
if (msg->onlyCmdPressed())
|
|
#else
|
|
if (msg->onlyCtrlPressed())
|
|
#endif
|
|
{
|
|
if (scancode == kKeyC)
|
|
set_clipboard_text(m_textbox.text());
|
|
}
|
|
|
|
// Esc to close the window.
|
|
if (auto closeButton = this->closeButton()) {
|
|
bool p = msg->propagateToParent();
|
|
msg->setPropagateToParent(false);
|
|
|
|
if (closeButton->sendMessage(msg))
|
|
return true;
|
|
|
|
msg->setPropagateToParent(p);
|
|
}
|
|
|
|
// Send Enter key to the Close button, Tab to change focus
|
|
if ((scancode == kKeyEnter) ||
|
|
(scancode == kKeyEnterPad))
|
|
return m_button.sendMessage(msg);
|
|
|
|
if (scancode == kKeyTab) {
|
|
if (auto mgr = manager())
|
|
return mgr->processFocusMovementMessage(msg);
|
|
}
|
|
|
|
// All keys are used if we have this window focused (so they
|
|
// don't trigger commands)
|
|
return true;
|
|
}
|
|
|
|
case ui::kKeyUpMessage:
|
|
if (auto closeButton = this->closeButton()) {
|
|
bool p = msg->propagateToParent();
|
|
msg->setPropagateToParent(false);
|
|
|
|
if (closeButton->sendMessage(msg))
|
|
return true;
|
|
|
|
msg->setPropagateToParent(p);
|
|
}
|
|
break;
|
|
}
|
|
return Window::onProcessMessage(msg);
|
|
}
|
|
|
|
void onInitTheme(InitThemeEvent& ev) override {
|
|
Window::onInitTheme(ev);
|
|
|
|
m_button.setMinSize(gfx::Size(60*ui::guiscale(), 0));
|
|
}
|
|
|
|
obs::scoped_connection m_mainWindowClosedConn;
|
|
View m_view;
|
|
TextBox m_textbox;
|
|
Button m_button;
|
|
bool m_hasText = false;
|
|
};
|
|
|
|
Console::Console(Context* ctx)
|
|
: m_withUI(false)
|
|
{
|
|
TRACE_CON("CON: Console this=", this, "ctx=", ctx, "is_ui_thread=", ui::is_ui_thread(), "{");
|
|
|
|
if (!ui::is_ui_thread())
|
|
return;
|
|
|
|
if (ctx)
|
|
m_withUI = ctx->isUIAvailable();
|
|
else
|
|
m_withUI = Console::isUIAvailable();
|
|
|
|
if (!m_withUI)
|
|
return;
|
|
|
|
TRACE_CON("CON: -> withUI=", m_withUI);
|
|
|
|
if (!m_console)
|
|
m_console = new ConsoleWindow;
|
|
}
|
|
|
|
Console::~Console()
|
|
{
|
|
TRACE_CON("CON: } ~Console this=", this, "withUI=", m_withUI);
|
|
|
|
if (!m_withUI)
|
|
return;
|
|
|
|
if (m_console &&
|
|
m_console->hasConsoleText() &&
|
|
!m_console->isVisible()) {
|
|
m_console->manager()->attractFocus(m_console);
|
|
m_console->openWindow();
|
|
TRACE_CON("CON: openWindow");
|
|
}
|
|
}
|
|
|
|
void Console::printf(const char* format, ...)
|
|
{
|
|
std::va_list ap;
|
|
va_start(ap, format);
|
|
std::string msg = base::string_vprintf(format, ap);
|
|
va_end(ap);
|
|
|
|
if (!m_withUI) {
|
|
fputs(msg.c_str(), stdout);
|
|
fflush(stdout);
|
|
return;
|
|
}
|
|
|
|
// Create the console window if it was closed/deleted by the user
|
|
if (!m_console) {
|
|
m_console = new ConsoleWindow;
|
|
}
|
|
|
|
// Open the window if it's hidden
|
|
if (!m_console->isVisible()) {
|
|
m_console->openWindow();
|
|
ui::Manager::getDefault()->invalidate();
|
|
}
|
|
|
|
// Update the textbox
|
|
m_console->addMessage(msg);
|
|
}
|
|
|
|
// static
|
|
void Console::showException(const std::exception& e)
|
|
{
|
|
std::string text;
|
|
if (typeid(e) == typeid(std::bad_alloc))
|
|
text = "There is not enough memory to complete the action.";
|
|
else
|
|
text = fmt::format("A problem has occurred.\n\nDetails:\n{}\n", e.what());
|
|
|
|
if (!ui::is_ui_thread()) {
|
|
LOG(ERROR, text.c_str());
|
|
|
|
// Show the error in the UI thread (if the UI is available)
|
|
if (Console::isUIAvailable()) {
|
|
ui::execute_from_ui_thread(
|
|
[text]{
|
|
Console console;
|
|
console.printf(text.c_str());
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
Console console;
|
|
console.printf(text.c_str());
|
|
}
|
|
|
|
// static
|
|
void Console::notifyNewDisplayConfiguration()
|
|
{
|
|
if (m_console)
|
|
m_console->centerConsole();
|
|
}
|
|
|
|
// static
|
|
bool Console::isUIAvailable()
|
|
{
|
|
auto app = App::instance();
|
|
auto man = Manager::getDefault();
|
|
return (app && app->isGui() &&
|
|
man && man->display() && man->display()->nativeWindow());
|
|
}
|
|
|
|
} // namespace app
|