GUI thoughts
"John W. Eaton" <[email protected]> Tue, 28 Feb 2006 16:49:15 -0500
| Newsgroups | gmane.comp.gnu.octave.graphics |
|---|---|
| Message-ID | <[email protected]> |
I have some thoughts and questions about how we would like a GUI for
Octave to work. Mostly, they are about how the GUI and Octave should
be linked together, who should be in charge of events, etc.
In Sebastien's Octave Workshop, I think the GUI handles all events
(correct me if I'm wrong). Octave is embedded in the GUI and gets
input via Octave's eval_string function. Output is captured by
grabbing std::cout in some way. This sort of works, but misses a few
things like handling the diary, automatic function updating based on
the last prompt time, the actual readline library for command-line
editing, etc. If you want to stick with eval_string, then it would
still be possible to handle the all these things, but you have to
duplicate a lot of functionality that is already in Octave. The
eval_string function was not really meant for embedding an interactive
Octave session (for one thing, embedding Octave this way turns off the
internal flag that tells it that it is running in an interactive
session). You could probably work around that too, but I think there
must be a better way.
Also, if you are embedding Octave with eval_string, I think your GUI
will be unresponsive while Octave is performing computations. Unless
you are running Octave in a separate thread, I think you have to wait
until eval_string returns to continue with the GUI operations. If you
are running Octave in a separate thread and then having the GUI touch
Octave internals while eval_string is running, you will likely have
unexpected results since Octave is not currently thread safe (there
is no mechanism to prevent global data from being modified at the
wrong time).
To avoid threads, you could start Octave as a coprocess to your GUI,
but you will still need some modifications to Octave so that it can
process requests while it is off running user code. You won't want to
use a pipe for the communication though, because that will not be
recognized as tty, so the interactive features won't work properly.
Opening a pty connection to a separate process is better (Octave will
see the pty as a tty, so it will run in interactive mode).
There is a terminal widget for GTK (VTE) that would make it easy to
start Octave as a separate process and communicate with it using a
pty. It provides a terminal window that is (more than) sufficient for
running Octave. Running Octave this way, you get readline, Octave
thinks it is interactive, diary works, etc. Perhaps there is
something similar for Qt?
The difficulty with this approach is that if you want the GUI to do
more than you can get by running Octave in a terminal window, then the
GUI and Octave have to pass some extra information around. I'm sure
that this can be done, but it will mean some extra effort. Will you
parse text data or design a protocol for passing binary data? Will
Octave or the GUI be in charge of that, and how much overhead will it
add? In terms of passing data back and forth, it would be much
simpler if the GUI and Octave were in the same process and the GUI
could just call internal Octave functions to do its thing.
At least with GTK on Unixy systems, it should be possible to embed
Octave in a terminal widget and make Octave think it is talking to a
tty without having to run Octave as a separate process. To do this,
you will need a thread-safe Octave (but you need that anyway with the
eval_string approach) and a pty implementation. The idea is that
instead of spawning a subprocess, you set up some ptys, dup some file
descriptors, start Octave in a separate thread, then attach the master
pty to the terminal widget.
Unfortunately, the VTE widget will not allow this mode of operation
out of the box. Fixing it requires the addition of one small
function, but it must be added to the core library because it relies
on some private internal data to work.
So, some questions and things to think about.
* Should we work to make Octave thread-safe? I think I would prefer
to work on this instead of a protocol that could be used to
communicate with a separate Octave process.
* Is there a terminal widget for Qt that could do the same thing as
VTE? I don't really want a GUI for Octave to be useful with just
one toolkit.
* Would any of this work in a Windows environment?
I'm appending a simple example program that illustrates this. It
depends on the modified VTE terminal widget and GTK. Building it is a
bit tricky since you need to first patch the VTE sources and build the
modified VTE library. Then you need to ensure that you use that
library for building and running the example program.
I'm assuming that if we choose this sort of approach, we can get the
necessary changes rolled into the default vte (or other) terminal
widget(s). If not, then I suppose we could provide our own terminal
widget with Octave (though that would definitely not be my
preference).
jwe
First, here is the function that must be added to vte:
/* We need the following additional function for vte:
int vte_terminal_set_pty(VteTerminal *terminal, int pty_master);
It must be added to vte because it calls static private functions
in the vte library.
This function was written by Eric Smith <[email protected]>,
archived here:
http://www.brouhaha.com/~eric/software/vte/vte-0.11.10-add-pty.patch
as a patch and was submitted to the Gnome project here:
http://bugzilla.gnome.org/show_bug.cgi?id=135230
(that was Feb 2004, and still not accepted).
*/
int
vte_terminal_set_pty(VteTerminal *terminal, int pty_master)
{
GtkWidget *widget;
g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
widget = GTK_WIDGET(terminal);
if (terminal->pvt->pty_master != -1) {
_vte_pty_close(terminal->pvt->pty_master);
close(terminal->pvt->pty_master);
}
terminal->pvt->pty_master = pty_master;
/* Open channels to listen for input on. */
xvte_terminal_connect_pty_read(terminal);
/* Open channels to write output to. */
xvte_terminal_connect_pty_write(terminal);
}
Now the main program. This is just a much-simplified version of an
example program that is distributed with readline. I installed the
modified vte library in /home/jwe/vte, so I compile the example
program with the following command
gcc -I/home/jwe/vte/include -g $(pkg-config gtk+-2.0 --cflags) \
example.c -o example \
-L/home/jwe/vte/lib -lvte $(pkg-config gtk+-2.0 --libs) \
-lreadline -lpthread -lutil
and run it with
LD_LIBRARY_PATH=/home/jwe/vte/lib ./example
This example is extremely simple and does almost nothing. The point
is to demonstrate using the terminal widget to embed an interactive
application without using a coprocess. The next step would be to add
a button or two that modifies some global data and protect the
accesses to the global data as needed. Add in some signal handling
and I think that would demonstrate most of what Octave does.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include <vte/vte.h>
#include <pthread.h>
#include <pty.h>
#include <readline/readline.h>
#include <readline/history.h>
void
execute_line (char *line)
{
static char *err_hilite_on = "\e[01;31m";
static char *err_hilite_off = "\e[0m";
if (! strcmp (line, "ls"))
system ("ls -FClg");
else if (! strcmp (line, "quit"))
exit (0);
else
fprintf (stderr, "%sunknown command `%s'%s\n",
err_hilite_on, line, err_hilite_off);
}
void *
fileman_main (void *dummy)
{
rl_readline_name = "FileMan";
/* Loop reading and executing lines until the user quits. */
while (1)
{
char *line = readline ("FileMan: ");
if (! line)
break;
if (*line)
{
add_history (line);
execute_line (line);
}
else
free (line);
}
return 0;
}
int
main (int argc, char **argv)
{
#ifndef FORK_COMMAND
pthread_t fileman_thread;
int fdm;
int fds;
#endif
/* GtkWidget is the storage type for widgets */
GtkWidget *window;
GtkWidget *terminal;
/* This is called in all GTK applications. Arguments are parsed
* from the command line and are returned to the application. */
gtk_init (&argc, &argv);
/* create a new window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
terminal = vte_terminal_new ();
#ifdef FORK_COMMAND
vte_terminal_fork_command (VTE_TERMINAL (terminal),
"/usr/bin/octave", 0, 0, 0, 0, 0, 0);
#else
if (openpty (&fdm, &fds, 0, 0, 0) < 0)
fprintf (stderr, "oops!\n");
dup2 (fds, 0);
dup2 (fds, 1);
dup2 (fds, 2);
pthread_create (&fileman_thread, 0, fileman_main, 0);
vte_terminal_set_pty (VTE_TERMINAL (terminal), fdm);
#endif
/* This packs the terminal into the window (a gtk container). */
gtk_container_add (GTK_CONTAINER (window), terminal);
vte_terminal_set_font_from_string (VTE_TERMINAL (terminal), "Fixed 11");
vte_terminal_set_size (VTE_TERMINAL (terminal), 80, 24);
vte_terminal_set_scrollback_lines (VTE_TERMINAL (terminal), 1024);
/* The final step is to display this newly created widget. */
gtk_widget_show (terminal);
/* and the window */
gtk_widget_show (window);
/* All GTK applications must have a gtk_main(). Control ends here
* and waits for an event to occur (like a key press or
* mouse event). */
gtk_main ();
return 0;
}