mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
* Added console panel for evaluating scripts and showing error messages.
* Rewrite of error queue code: errors are now pulled, instead of being turned into messageboxes automatically. git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@1629 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||
//| Copyright: (C) 2001 - 2010 Twan van Laarhoven and Sean Hunt |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <gui/set/console_panel.hpp>
|
||||
#include <gui/control/text_ctrl.hpp>
|
||||
#include <gui/util.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
#include <data/stylesheet.hpp>
|
||||
#include <wx/splitter.h>
|
||||
#include <wx/dcbuffer.h>
|
||||
|
||||
DECLARE_POINTER_TYPE(ConsoleMessage);
|
||||
DECLARE_TYPEOF_COLLECTION(ScriptParseError);
|
||||
DECLARE_TYPEOF_COLLECTION(ConsoleMessageP);
|
||||
|
||||
// ----------------------------------------------------------------------------- : MessageControl
|
||||
|
||||
class ConsoleMessage : public IntrusivePtrBase<ConsoleMessage> {
|
||||
public:
|
||||
MessageType type;
|
||||
String text; // string message
|
||||
Bitmap bitmap; // image message instead of string
|
||||
ScriptValueP value; // other valued message (images? cards?)
|
||||
// location of error messages
|
||||
String source_file;
|
||||
int line_number;
|
||||
// layout
|
||||
bool joined_to_previous;
|
||||
int top;
|
||||
int height;
|
||||
int bottom() const { return top+height; }
|
||||
|
||||
ConsoleMessage(MessageType type, String const& text = _(""))
|
||||
: type(type), text(text), line_number(-1), joined_to_previous(false), top(-1), height(-1)
|
||||
{}
|
||||
};
|
||||
|
||||
class MessageCtrl : public wxScrolledWindow {
|
||||
public:
|
||||
MessageCtrl(wxWindow* parent, int id)
|
||||
: wxScrolledWindow(parent, id, wxDefaultPosition, wxDefaultSize, wxBORDER_THEME)
|
||||
{
|
||||
SetBackgroundStyle(wxBG_STYLE_CUSTOM);
|
||||
SetScrollRate(0, 1);
|
||||
EnableScrolling(false,true);
|
||||
// icons
|
||||
BOOST_STATIC_ASSERT(MESSAGE_TYPE_MAX == 6);
|
||||
icons[MESSAGE_INPUT] = wxBitmap(load_resource_image(_("message_input")));
|
||||
icons[MESSAGE_OUTPUT] = wxBitmap();
|
||||
icons[MESSAGE_INFO] = wxBitmap(load_resource_image(_("message_information")));
|
||||
icons[MESSAGE_WARNING] = wxBitmap(load_resource_image(_("message_warning")));
|
||||
icons[MESSAGE_ERROR] = wxBitmap(load_resource_image(_("message_error")));
|
||||
icons[MESSAGE_FATAL_ERROR] = icons[MESSAGE_ERROR];
|
||||
// color
|
||||
colors[MESSAGE_INPUT] = wxColour(0,80,0);
|
||||
colors[MESSAGE_OUTPUT] = wxColour(255,255,255);
|
||||
colors[MESSAGE_INFO] = wxColour(0,0,255);
|
||||
colors[MESSAGE_WARNING] = wxColour(255,255,0);
|
||||
colors[MESSAGE_ERROR] = colors[MESSAGE_FATAL_ERROR] = wxColour(255,0,0);
|
||||
}
|
||||
|
||||
void add_message(ConsoleMessageP const& msg) {
|
||||
messages.push_back(msg);
|
||||
layout_all(messages.size() - 1);
|
||||
// refresh
|
||||
ensure_visible(*messages.back());
|
||||
Refresh(false);
|
||||
}
|
||||
void add_message(MessageType type, String const& text) {
|
||||
add_message(intrusive(new ConsoleMessage(type,text)));
|
||||
}
|
||||
|
||||
private:
|
||||
DECLARE_EVENT_TABLE();
|
||||
|
||||
// --------------------------------------------------- : Data
|
||||
|
||||
// the messages
|
||||
vector<ConsoleMessageP> messages;
|
||||
size_t selection;
|
||||
wxBitmap icons[MESSAGE_TYPE_MAX];
|
||||
wxColour colors[MESSAGE_TYPE_MAX];
|
||||
|
||||
// --------------------------------------------------- : Events
|
||||
|
||||
void onLeftDown(wxMouseEvent& ev) {
|
||||
int ystart; GetViewStart(nullptr,&ystart);
|
||||
selection = find_point(ystart + ev.GetY());
|
||||
if (selection < messages.size()) {
|
||||
ensure_visible(*messages[selection]);
|
||||
}
|
||||
Refresh(false);
|
||||
}
|
||||
|
||||
int find_point(int y) {
|
||||
// TODO: could do a binary search here
|
||||
for (size_t i = 0 ; i < messages.size() ; ++i) {
|
||||
if (y >= messages[i]->top && y < messages[i]->bottom()) return i;
|
||||
}
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
void ensure_visible(ConsoleMessage const& msg) {
|
||||
int ystart; GetViewStart(nullptr,&ystart);
|
||||
int height = GetClientSize().y;
|
||||
if (msg.top < ystart) {
|
||||
Scroll(0, msg.top);
|
||||
} else if (msg.bottom() > ystart + height) {
|
||||
Scroll(0, msg.bottom() - height);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------- : Drawing
|
||||
|
||||
void onPaint(wxPaintEvent& ev) {
|
||||
wxAutoBufferedPaintDC dc(this);
|
||||
PrepareDC(dc);
|
||||
draw(dc);
|
||||
}
|
||||
void draw(wxDC& dc) const {
|
||||
clearDC(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
dc.SetFont(*wxNORMAL_FONT);
|
||||
FOR_EACH_CONST(msg, messages) {
|
||||
draw(dc, *msg);
|
||||
}
|
||||
if (messages.empty()) {
|
||||
// Say something about no messages?
|
||||
}
|
||||
}
|
||||
|
||||
void draw(wxDC& dc, ConsoleMessage const& msg) const {
|
||||
int left = 0;
|
||||
int top = msg.top;
|
||||
int width = GetClientSize().x;
|
||||
wxColour color = colors[msg.type];
|
||||
wxColour bg, fg;
|
||||
if (selection < messages.size() && messages[selection].get() == &msg) {
|
||||
bg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
|
||||
fg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
|
||||
} else {
|
||||
bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
}
|
||||
|
||||
// draw background
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(lerp(bg,color, 0.05));
|
||||
dc.DrawRectangle(left,top,width,msg.height);
|
||||
|
||||
// draw icon
|
||||
if (icons[msg.type].Ok()) {
|
||||
dc.DrawBitmap(icons[msg.type], left + ICON_PADDING,top + ICON_PADDING);
|
||||
}
|
||||
|
||||
// draw text
|
||||
dc.SetTextForeground(fg);
|
||||
int text_left = TEXT_PADDING_LEFT;
|
||||
int text_top = top + TEXT_PADDING_TOP;
|
||||
// find line breaks in the text
|
||||
String::const_iterator begin = msg.text.begin();
|
||||
String::const_iterator it = begin;
|
||||
while (it != msg.text.end()) {
|
||||
if (*it == _('\n')) {
|
||||
// break here
|
||||
dc.DrawText(String(begin,it), text_left, text_top);
|
||||
begin = it = it + 1;
|
||||
text_top += dc.GetCharHeight() + TEXT_LINE_SPACING;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
if (begin != msg.text.end()) {
|
||||
dc.DrawText(String(begin,it), text_left, text_top);
|
||||
text_top += dc.GetCharHeight() + TEXT_LINE_SPACING;
|
||||
}
|
||||
|
||||
// draw bitmap
|
||||
if (msg.bitmap.Ok()) {
|
||||
dc.DrawBitmap(msg.bitmap, text_left, text_top);
|
||||
text_top += msg.bitmap.GetHeight();
|
||||
}
|
||||
|
||||
// draw line below item
|
||||
dc.SetPen(lerp(bg,fg, 0.3));
|
||||
dc.DrawLine(left, top+msg.height, left+width, top+msg.height);
|
||||
}
|
||||
|
||||
int item_height(wxDC& dc, ConsoleMessage const& msg) const {
|
||||
// text height
|
||||
int text_height = 0;
|
||||
// find line breaks in the text
|
||||
String::const_iterator begin = msg.text.begin();
|
||||
String::const_iterator it = begin;
|
||||
while (it != msg.text.end()) {
|
||||
if (*it == _('\n')) {
|
||||
// break here
|
||||
begin = it = it + 1;
|
||||
text_height += dc.GetCharHeight() + TEXT_LINE_SPACING;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
// TODO: break long lines
|
||||
}
|
||||
if (begin != msg.text.end()) {
|
||||
text_height += dc.GetCharHeight() + TEXT_LINE_SPACING;
|
||||
}
|
||||
|
||||
// height of bitmap
|
||||
int bitmap_height = msg.bitmap.Ok() ? msg.bitmap.GetHeight() : 0;
|
||||
|
||||
return max(MIN_ITEM_HEIGHT, TEXT_PADDING_TOP + TEXT_PADDING_BOTTOM + text_height + bitmap_height) + LIST_SPACING;
|
||||
}
|
||||
|
||||
// --------------------------------------------------- : Layout
|
||||
|
||||
static const int LIST_SPACING = 1;
|
||||
static const int ICON_PADDING = 3;
|
||||
static const int TEXT_PADDING_LEFT = ICON_PADDING + 16 + 4;
|
||||
static const int TEXT_PADDING_RIGHT = 4;
|
||||
static const int TEXT_PADDING_TOP = 4;
|
||||
static const int TEXT_PADDING_BOTTOM = 2;
|
||||
static const int TEXT_LINE_SPACING = 1;
|
||||
static const int MIN_ITEM_HEIGHT = 16 + 2*ICON_PADDING;
|
||||
|
||||
/// Layout all messages, starting from number start
|
||||
/// layout = determine their height
|
||||
void layout_all(size_t start = 0) {
|
||||
// scratch dc, for finding text sizes
|
||||
wxMemoryDC dc;
|
||||
wxBitmap bmp(1,1);
|
||||
dc.SelectObject(bmp);
|
||||
dc.SetFont(*wxNORMAL_FONT);
|
||||
|
||||
for (size_t i = start ; i < messages.size() ; ++i) {
|
||||
// layout a single item
|
||||
ConsoleMessage& msg = *messages[i];
|
||||
msg.top = start == 0 ? 0 : messages[i-1]->bottom() + LIST_SPACING;
|
||||
if (i > 0 && msg.joined_to_previous) msg.top -= LIST_SPACING;
|
||||
// text size
|
||||
msg.height = item_height(dc, msg);
|
||||
}
|
||||
|
||||
// set size of the control
|
||||
if (messages.empty()) {
|
||||
SetVirtualSize(-1, 0);
|
||||
} else {
|
||||
int height = messages.back()->bottom();
|
||||
SetVirtualSize(-1, height);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------- : Layout
|
||||
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(MessageCtrl,wxScrolledWindow)
|
||||
EVT_PAINT(MessageCtrl::onPaint)
|
||||
EVT_LEFT_DOWN(MessageCtrl::onLeftDown)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
// ----------------------------------------------------------------------------- : ConsolePanel
|
||||
|
||||
ConsolePanel::ConsolePanel(Window* parent, int id)
|
||||
: SetWindowPanel(parent, id)
|
||||
{
|
||||
// init controls
|
||||
splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
messages = new MessageCtrl(splitter, wxID_ANY);
|
||||
entry_panel = new Panel(splitter, wxID_ANY);
|
||||
entry = new wxTextCtrl(entry_panel, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
|
||||
wxButton* evaluate = new wxButton(entry_panel, ID_EVALUATE, _BUTTON_("evaluate"));
|
||||
// init sizer for entry_panel
|
||||
wxSizer* se = new wxBoxSizer(wxHORIZONTAL);
|
||||
se->Add(entry, 1, wxEXPAND, 2);
|
||||
se->Add(evaluate, 0, wxEXPAND | wxLEFT, 2);
|
||||
entry_panel->SetSizer(se);
|
||||
// init splitter
|
||||
splitter->SetMinimumPaneSize(40);
|
||||
splitter->SetSashGravity(1.0);
|
||||
splitter->SplitHorizontally(messages, entry_panel, -50);
|
||||
// init sizer
|
||||
wxSizer* s = new wxBoxSizer(wxVERTICAL);
|
||||
s->Add(splitter, 1, wxEXPAND);
|
||||
s->SetSizeHints(this);
|
||||
SetSizer(s);
|
||||
}
|
||||
|
||||
void ConsolePanel::onChangeSet() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : UI
|
||||
|
||||
void ConsolePanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
// Menus
|
||||
// focus on entry
|
||||
entry->SetFocus();
|
||||
}
|
||||
|
||||
void ConsolePanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
// Toolbar
|
||||
// Menus
|
||||
}
|
||||
|
||||
void ConsolePanel::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
if (ev.GetId() == ID_EVALUATE) {
|
||||
ev.Enable(!entry->GetValue().empty());
|
||||
}
|
||||
}
|
||||
|
||||
void ConsolePanel::onEnter(wxCommandEvent& ev) {
|
||||
onCommand(ID_EVALUATE);
|
||||
}
|
||||
void ConsolePanel::onCommand(int id) {
|
||||
if (id == ID_EVALUATE) {
|
||||
exec(entry->GetValue());
|
||||
entry->SetValue(_(""));
|
||||
}
|
||||
}
|
||||
|
||||
void ConsolePanel::onIdle(wxIdleEvent&) {
|
||||
get_pending_errors();
|
||||
}
|
||||
|
||||
void ConsolePanel::get_pending_errors() {
|
||||
// add pending messages
|
||||
MessageType type;
|
||||
String msg;
|
||||
while (get_queued_message(type,msg)) {
|
||||
messages->add_message(type,msg);
|
||||
}
|
||||
// If this panel doesn't have the focus, then highlight it somehow
|
||||
|
||||
}
|
||||
|
||||
void ConsolePanel::exec(String const& command) {
|
||||
if (command.empty()) return;
|
||||
// add input message
|
||||
messages->add_message(MESSAGE_INPUT, command);
|
||||
try {
|
||||
// parse command
|
||||
vector<ScriptParseError> errors;
|
||||
ScriptP script = parse(command,nullptr,false,errors);
|
||||
if (!errors.empty()) {
|
||||
FOR_EACH(error,errors) {
|
||||
// TODO: also squiglify the input?
|
||||
messages->add_message(MESSAGE_ERROR,error.what());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// execute command
|
||||
//WITH_DYNAMIC_ARG(export_info, &ei); // TODO: allow image export
|
||||
Context& ctx = set->getContext();
|
||||
ScriptValueP result = ctx.eval(*script,false);
|
||||
get_pending_errors();
|
||||
// show result
|
||||
ConsoleMessageP message = intrusive(new ConsoleMessage(MESSAGE_OUTPUT));
|
||||
message->joined_to_previous = true;
|
||||
message->value = result;
|
||||
// type of result
|
||||
ScriptType type = result->type();
|
||||
if (type == SCRIPT_IMAGE) {
|
||||
GeneratedImage::Options options(0,0, set->stylesheet.get(), set.get());
|
||||
wxImage image = result->toImage(result)->generate(options);
|
||||
message->bitmap = wxBitmap(image);
|
||||
} else if (type == SCRIPT_COLOR) {
|
||||
message->text = result->toCode();
|
||||
AColor color = (AColor)*result;
|
||||
wxImage image(30,20);
|
||||
fill_image(image,color);
|
||||
set_alpha(image, color.alpha);
|
||||
message->bitmap = wxBitmap(image);
|
||||
} else {
|
||||
message->text = result->toCode();
|
||||
}
|
||||
messages->add_message(message);
|
||||
} catch (ScriptError const& e) {
|
||||
messages->add_message(MESSAGE_ERROR, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(ConsolePanel, wxPanel)
|
||||
EVT_TEXT_ENTER(wxID_ANY,ConsolePanel::onEnter)
|
||||
EVT_IDLE(ConsolePanel::onIdle)
|
||||
END_EVENT_TABLE ()
|
||||
|
||||
// ----------------------------------------------------------------------------- : Clipboard
|
||||
/*
|
||||
bool ConsolePanel::canCut() const { return entry->canCut(); }
|
||||
bool ConsolePanel::canCopy() const { return entry->canCopy(); }
|
||||
bool ConsolePanel::canPaste() const { return entry->canPaste(); }
|
||||
void ConsolePanel::doCut() { entry->doCut(); }
|
||||
void ConsolePanel::doCopy() { entry->doCopy(); }
|
||||
void ConsolePanel::doPaste() { entry->doPaste(); }
|
||||
*/
|
||||
Reference in New Issue
Block a user