Unix

Embedding python shell in GUI applications

ForceCore 2014. 3. 7. 16:55

wxWidgets 3.0, Python 3.3 기준이다.


http://docs.python.org/3.4/faq/extending.html

여기 나온 코드를 기반으로 개조해서 만들었다.



PythonConsole.h


#pragma once

#ifndef PYTHONCONSOLE_H

#define PYTHONCONSOLE_H


#include <Python.h>

#include <string>




class PythonConsole

{

public:

PythonConsole();

virtual ~PythonConsole();


public :

void feed_line( const char *line );

const char *get_output();


// Public vars

public :

const char *prompt;

const char *error_msg;


private:

const char *get_error();


protected:

private:

const char *ps1;

const char *ps2;


PyObject *glb, *loc;

PyObject *exc, *val, *trb, *obj, *dum;


std::string code;

};


#endif // PYTHONCONSOLE_H




PythonConsole.cpp


두둥!


#include "PythonConsole.h"

#include <Python.h>

//#include <iostream>

#include <cassert>


using namespace std;




PythonConsole::PythonConsole()

{

//ctor

ps1 = ">>> ";

ps2 = "... ";

prompt = ps1;

error_msg = NULL;


Py_Initialize();

loc = PyDict_New();

glb = PyDict_New();

PyDict_SetItemString( glb, "__builtins__", PyEval_GetBuiltins () );


// Redirection of stdout and stderr to stream

PyRun_String( "from io import StringIO", Py_single_input, glb, loc );

PyRun_String( "import sys", Py_single_input, glb, loc );

PyRun_String( "old_stdout = sys.stdout", Py_single_input, glb, loc );

PyRun_String( "old_stderr = sys.stderr", Py_single_input, glb, loc );

PyRun_String( "sys.stdout = StringIO()", Py_single_input, glb, loc );

PyRun_String( "sys.stderr = StringIO()", Py_single_input, glb, loc );

PyRun_String( "print( sys.version )", Py_single_input, glb, loc );


exc = val = trb = obj = dum = NULL;


    code.clear();

}


PythonConsole::~PythonConsole()

{

PyRun_String( "sys.stdout = old_stdout", Py_single_input, glb, loc );

PyRun_String( "sys.stderr = old_stderr", Py_single_input, glb, loc );


Py_XDECREF(glb);

Py_XDECREF(loc);

Py_Finalize();

}


const char *PythonConsole::get_error()

{

PyRun_String( "ss = sys.stderr.getvalue()", Py_single_input, glb, loc );

PyObject *str = PyDict_GetItem( loc, PyUnicode_FromString( "ss" ) );

PyRun_String( "sys.stderr = StringIO()", Py_single_input, glb, loc ); // clear buffer

return PyUnicode_AsUTF8( str );

}


const char *PythonConsole::get_output()

{

PyRun_String( "ss = sys.stdout.getvalue()", Py_single_input, glb, loc );

PyObject *str = PyDict_GetItem( loc, PyUnicode_FromString( "ss" ) );

PyRun_String( "sys.stdout = StringIO()", Py_single_input, glb, loc );

return PyUnicode_AsUTF8( str );

}


void PythonConsole::feed_line( const char *line )

{

code += line;

code += '\n'; // For python interpreter

//cout << line << endl;

//cout << code;


PyObject *src = Py_CompileString( code.c_str(), "<stdin>", Py_single_input );

error_msg = NULL;


if (NULL != src)                         // compiled just fine

{

assert( code[ code.size()-1 ] == '\n' );

if (ps1 == prompt ||                  // ">>>"

'\n' == code[ code.size()-2 ] )           // ... " and double '\n'

{                                               // so execute it

dum = PyEval_EvalCode( src, glb, loc );

Py_XDECREF (dum);

Py_XDECREF (src);

code.clear();

if (PyErr_Occurred ()) {

PyErr_Print();

error_msg = get_error();

}

prompt = ps1;

}

}

else if (PyErr_ExceptionMatches (PyExc_SyntaxError))

{

const char *msg = NULL;

PyErr_Fetch (&exc, &val, &trb);        // clears exception!


if (PyArg_ParseTuple (val, "sO", &msg, &obj) &&

!strcmp (msg, "unexpected EOF while parsing")) // E_EOF

{

Py_XDECREF (exc);

Py_XDECREF (val);

Py_XDECREF (trb);

prompt = ps2;

}

else                                   // some other syntax error

{

PyErr_Restore (exc, val, trb);

PyErr_Print();

error_msg = get_error();

code.clear();

prompt = ps1;

}

}

else                                     // some non-syntax error

{

PyErr_Print();

error_msg = get_error();

code.clear();

prompt = ps1;

}

}



쓰는 방법.

cons = new PythonConsole()

...

cons->feed_line( "print( 'hello world!' )" );
이 때
if( cons->error_msg ) {
  cout << cons->error_msg
}

else {

  cout << cons->get_output();

}


...


delete cons;

이런식으로 운영해주면 된다.


그러니까, GUI의 frame에서 사용하려면 frame 초기화 부분에는 new 로 콘솔을 잡는 부분.

그리고 text edit같은걸로 code를 적은 뒤에는 feed_line으로 먹여준다.

마지막 frame 종료시엔 delete cons.


wxWidgets로 만든 예제 콘솔 frame:


ConsoleFrame.h:

#ifndef CONSOLEFRAME_H

#define CONSOLEFRAME_H


//(*Headers(ConsoleFrame)

#include <wx/sizer.h>

#include <wx/button.h>

#include <wx/frame.h>

#include <wx/stattext.h>

#include <wx/textctrl.h>

//*)


#include "PythonConsole.h"


class ConsoleFrame: public wxFrame

{

public:


ConsoleFrame(wxWindow* parent,wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize);

virtual ~ConsoleFrame();


//(*Declarations(ConsoleFrame)

wxTextCtrl* TextCmd;

wxButton* ButtonSend;

wxTextCtrl* TextConsoleOutput;

wxStaticText* StaticTextPrompt;

//*)


protected:


//(*Identifiers(ConsoleFrame)

static const long ID_TEXTCTRL_CONSOLE;

static const long ID_STATICTEXT1;

static const long ID_TEXTCTRL_CMD;

static const long ID_BUTTON_SEND;

//*)

static const long ID_CLOSE;


private:


//(*Handlers(ConsoleFrame)

void OnTextCmdTextEnter(wxCommandEvent& event);

void OnTextCmdText(wxCommandEvent& event);

//*)


PythonConsole *cons;


void OnEscPress( wxCommandEvent &event );


DECLARE_EVENT_TABLE()

};


#endif



ConsoleFrame.cpp:
#include "ConsoleFrame.h"

#include <wx/msgdlg.h>
#include <wx/accel.h>
//#include <wx/textctrl.h>

/*
#ifdef _WIN32
#include <windows.h>
// ?? dunno what to load, yet
// http://stackoverflow.com/questions/2746168/how-to-construct-a-c-fstream-from-a-posix-file-descriptor
#else
#include <ext/stdio_filebuf.h>
#include <fstream>
#endif
*/

//(*InternalHeaders(ConsoleFrame)
#include <wx/string.h>
#include <wx/intl.h>
//*)

//(*IdInit(ConsoleFrame)
const long ConsoleFrame::ID_TEXTCTRL_CONSOLE = wxNewId();
const long ConsoleFrame::ID_STATICTEXT1 = wxNewId();
const long ConsoleFrame::ID_TEXTCTRL_CMD = wxNewId();
const long ConsoleFrame::ID_BUTTON_SEND = wxNewId();
//*)
const long ConsoleFrame::ID_CLOSE = wxNewId(); // close ESC key accelerator

BEGIN_EVENT_TABLE(ConsoleFrame,wxFrame)
//(*EventTable(ConsoleFrame)
//*)
END_EVENT_TABLE()

ConsoleFrame::ConsoleFrame(wxWindow* parent,wxWindowID id,const wxPoint& pos,const wxSize& size)
{
//(*Initialize(ConsoleFrame)
wxBoxSizer* BoxSizer2;
wxBoxSizer* BoxSizer1;

Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, _T("wxID_ANY"));
BoxSizer1 = new wxBoxSizer(wxVERTICAL);
TextConsoleOutput = new wxTextCtrl(this, ID_TEXTCTRL_CONSOLE, wxEmptyString, wxDefaultPosition, wxSize(427,235), wxTE_AUTO_SCROLL|wxTE_MULTILINE|wxTE_READONLY, wxDefaultValidator, _T("ID_TEXTCTRL_CONSOLE"));
BoxSizer1->Add(TextConsoleOutput, 1, wxALL|wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 3);
BoxSizer2 = new wxBoxSizer(wxHORIZONTAL);
StaticTextPrompt = new wxStaticText(this, ID_STATICTEXT1, _(">>>"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT1"));
StaticTextPrompt->SetMinSize(wxSize(30,-1));
BoxSizer2->Add(StaticTextPrompt, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
TextCmd = new wxTextCtrl(this, ID_TEXTCTRL_CMD, wxEmptyString, wxDefaultPosition, wxSize(264,20), wxTE_PROCESS_ENTER, wxDefaultValidator, _T("ID_TEXTCTRL_CMD"));
TextCmd->SetFocus();
BoxSizer2->Add(TextCmd, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 1);
ButtonSend = new wxButton(this, ID_BUTTON_SEND, _("Send"), wxDefaultPosition, wxSize(55,27), 0, wxDefaultValidator, _T("ID_BUTTON_SEND"));
BoxSizer2->Add(ButtonSend, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 1);
BoxSizer1->Add(BoxSizer2, 0, wxALL|wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 0);
SetSizer(BoxSizer1);
BoxSizer1->Fit(this);
BoxSizer1->SetSizeHints(this);

Connect(ID_TEXTCTRL_CMD,wxEVT_COMMAND_TEXT_ENTER,(wxObjectEventFunction)&ConsoleFrame::OnTextCmdTextEnter);
Connect(ID_BUTTON_SEND,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&ConsoleFrame::OnTextCmdTextEnter);
//*)

//TextCmd->Connect( wxEVT_KEY_DOWN, (wxObjectEventFunction)&ConsoleFrame::OnTextCmdKeyDown );

// OK, esc key interception failed. Trying accelerators

wxButton *btn = new wxButton( this, ID_CLOSE, _("Close") );
btn->Connect( wxEVT_COMMAND_BUTTON_CLICKED, (wxObjectEventFunction)&ConsoleFrame::OnEscPress );
btn->Hide(); // Make this button invisible to users

wxAcceleratorEntry entries[ 1 ];
entries[0].Set( wxACCEL_NORMAL, WXK_ESCAPE, ID_CLOSE );

wxAcceleratorTable accel( 1, entries );
this->SetAcceleratorTable( accel );

Connect( ID_CLOSE, wxEVT_COMMAND_BUTTON_CLICKED, (wxObjectEventFunction)&ConsoleFrame::OnEscPress );

this->cons = new PythonConsole();
TextConsoleOutput->AppendText( wxString::FromUTF8( cons->get_output() ) ); // Get the initial version message
}

ConsoleFrame::~ConsoleFrame()
{
//(*Destroy(ConsoleFrame)
//*)
delete this->cons;
}

void ConsoleFrame::OnTextCmdTextEnter(wxCommandEvent& event)
{
// wxMessageBox( TextCmd->GetLineText( 0 ) );
cons->feed_line( TextCmd->GetLineText( 0 ).mb_str() );
TextConsoleOutput->AppendText( StaticTextPrompt->GetLabel() + ' ' + TextCmd->GetLineText( 0 ) + '\n' );
TextCmd->Clear();

if( cons->error_msg ) {
TextConsoleOutput->AppendText( wxString::FromUTF8( cons->error_msg ) );
}
else {
const char *out = cons->get_output();
if( out )
TextConsoleOutput->AppendText( wxString::FromUTF8( out ) );
}

StaticTextPrompt->SetLabel( wxString::FromUTF8( cons->prompt ) );
}

void ConsoleFrame::OnEscPress( wxCommandEvent &event )
{
//wxMessageBox( _("I'm on a boat") );
event.Skip(); // need this!
Close();
}

후우;; 코드는 짧은데 이렇게 본격적으로는 처음 해봐서 한참 걸렸다. 한 2~3일?