aseprite/src/app/console.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

316 lines
7.7 KiB
C++
Raw Normal View History

2015-02-12 23:16:25 +08:00
// Aseprite
// Copyright (C) 2018-2025 Igara Studio S.A.
2018-07-04 23:35:15 +08:00
// Copyright (C) 2001-2018 David Capello
2015-02-12 23:16:25 +08:00
//
2016-08-27 04:02:58 +08:00
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
2007-09-19 07:57:02 +08:00
#ifdef HAVE_CONFIG_H
2007-09-19 07:57:02 +08:00
#include "config.h"
#endif
2007-09-19 07:57:02 +08:00
#include "app/console.h"
2007-09-19 07:57:02 +08:00
#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"
2018-07-04 23:35:15 +08:00
#include "ui/system.h"
#include "ui/ui.h"
#include <cstdarg>
#include <cstdio>
#include <memory>
#define TRACE_CON(...) // TRACEARGS(__VA_ARGS__)
namespace app {
2007-09-19 07:57:02 +08:00
using namespace ui;
Console::ConsoleWindow* Console::m_console = nullptr;
class Console::ConsoleWindow final : public Window {
2018-12-15 05:08:12 +08:00
public:
ConsoleWindow()
: Window(Window::WithTitleBar, Strings::debugger_console())
2018-12-15 05:08:12 +08:00
, m_textbox("", WORDWRAP)
, m_button(Strings::debugger_cancel())
{
TRACE_CON("CON: ConsoleWindow this=", this);
m_button.Click.connect([this] { closeWindow(&m_button); });
2018-12-15 05:08:12 +08:00
// 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). The main window can be nullptr if the console is
// used to show an error when loading the default theme or font at
// the initialization.
if (auto* mainWin = App::instance()->mainWindow()) {
m_mainWindowClosedConn = mainWin->Close.connect([this] { closeWindow(nullptr); });
}
2018-12-15 05:08:12 +08:00
// When the window is closed, we clear the text
Close.connect([this] {
m_mainWindowClosedConn.disconnect();
m_textbox.setText(std::string());
2024-12-17 01:52:19 +08:00
Console::m_console->deferDelete();
Console::m_console = nullptr;
TRACE_CON("CON: Close signal");
});
2018-12-15 05:08:12 +08:00
m_view.attachToView(&m_textbox);
ui::Grid* grid = new ui::Grid(1, false);
2018-12-15 05:08:12 +08:00
grid->addChildInCell(&m_view, 1, 1, HORIZONTAL | VERTICAL);
grid->addChildInCell(&m_button, 1, 1, CENTER);
addChild(grid);
m_textbox.setFocusMagnet(true);
2018-12-15 05:08:12 +08:00
m_button.setFocusMagnet(true);
m_view.setExpansive(true);
initTheme();
2018-12-15 05:08:12 +08:00
}
~ConsoleWindow() { TRACE_CON("CON: ~ConsoleWindow this=", this); }
void addMessage(std::string msg)
2018-12-15 05:08:12 +08:00
{
if (!m_hasText) {
m_hasText = true;
centerConsole();
2018-12-15 05:08:12 +08:00
}
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);
// Escape characters we can't show properly
for (size_t i = 0; i < msg.size(); i++) {
switch (msg[i]) {
case '\a':
case '\b':
case '\r':
case '\t':
case '\v': msg[i] = ' ';
}
}
2018-12-15 05:08:12 +08:00
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);
}
2018-12-15 05:08:12 +08:00
}
bool hasConsoleText() const { return m_hasText; }
2018-12-15 05:08:12 +08:00
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);
}
2018-12-15 05:08:12 +08:00
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;
2018-12-15 05:08:12 +08:00
View m_view;
TextBox m_textbox;
Button m_button;
bool m_hasText = false;
};
Console::Console(Context* ctx) : m_withUI(false)
2007-09-19 07:57:02 +08:00
{
TRACE_CON("CON: Console this=", this, "ctx=", ctx, "is_ui_thread=", ui::is_ui_thread(), "{");
2018-07-04 23:35:15 +08:00
if (!ui::is_ui_thread())
return;
if (ctx)
m_withUI = ctx->isUIAvailable();
else
m_withUI = Console::isUIAvailable();
if (!m_withUI)
return;
2007-09-19 07:57:02 +08:00
TRACE_CON("CON: -> withUI=", m_withUI);
2007-09-19 07:57:02 +08:00
if (!m_console)
m_console = new ConsoleWindow;
2007-09-19 07:57:02 +08:00
}
Console::~Console()
2007-09-19 07:57:02 +08:00
{
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");
}
2007-09-19 07:57:02 +08:00
}
void Console::printf(const char* format, ...)
2007-09-19 07:57:02 +08:00
{
std::va_list ap;
va_start(ap, format);
std::string msg = base::string_vprintf(format, ap);
va_end(ap);
2007-09-19 07:57:02 +08:00
if (!m_withUI) {
fputs(msg.c_str(), stdout);
fflush(stdout);
return;
}
2007-09-19 07:57:02 +08:00
// 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
2018-12-15 05:08:12 +08:00
if (!m_console->isVisible()) {
m_console->openWindow();
ui::Manager::getDefault()->invalidate();
}
// Update the textbox
2018-12-15 05:08:12 +08:00
m_console->addMessage(msg);
2007-09-19 07:57:02 +08:00
}
// 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