gui/gui_init [ Functions ]
NAME
gui_init
SYNOPSIS
static gui_t* gui_init( gui_t* gui, GtkApplication* app, int* pargc, char** pargv[], gint screen_width, gint screen_height )
FUNCTION
Initialize the GUI module.
Start by parsing command-line arguments that are intended for it. Assign default values to various members of the supplied gui_t structure. Calculates the initial base address for the virtual video frame buffer.
Currently, this function also initializes the resources found in the virtual machine environment (e.g., it places ROM at address 0, RAM at high memory addresses, etc.). This is a matter of convenience; future versions of this module may isolate this behavior into a separate module.
INPUTS
gui -- pointer to the gui_t to initialize. pargc, pargv -- Pointers to argc and argv, so GTK look for CLI arguments unique to it. screen_width, screen_height -- Default display resolution for the Kestrel-2 environment. RESULT If successful, it will return the gui_t that was supplied; otherwise, NULL will be returned.
SOURCE
{
if (gui) {
memset(gui, 0xCC, sizeof(gui_t));
gtk_init(pargc, pargv);
gui->screen_width = screen_width;
gui->screen_height = screen_height;
gui->app = app;
fprintf(stderr, "K2INI001 screen dimensions (%d x %d) 1bpp\n",
gui->screen_width, gui->screen_height
);
// Calculate base address of display framebuffer;
// by default, it is placed at the very top of RAM.
//
// Round up to the nearest 16-bit word, since that's
// what the 2EX's RAM bus width is.
const int32_t hwords_per_row = (screen_width + 15) >> 4;
const int32_t hwords_per_screen =
hwords_per_row * screen_height;
const int32_t bytes_per_screen = hwords_per_screen << 1;
gui->screen_base = (uint32_t)(-bytes_per_screen);
fprintf(stderr, "K2INI002 screen_base = $%08X\n",
gui->screen_base
);
gui_init_layout(gui, app);
// Map out the virtual memory resources
slots_init(&gui->slots);
}
return gui;
}
gui/gui_init_layout [ Functions ]
NAME
gui_init_layout
SYNOPSIS
static void gui_init_layout(gui_t* gui, GtkApplication* app)
FUNCTION
Creates the GUI layout; it computes where things appear in the desktop window.
INPUTS
gui -- Pointer to the application's gui_t structure.
SEE ALSO
gui_init
SOURCE
{
// Build main window
GtkWindow* window = gui->window = (GtkWindow*)
gtk_application_window_new(app);
gtk_window_set_title(
GTK_WINDOW(window),
"Kestrel-2EX Emulator V" VERSION
);
gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
g_signal_connect_swapped(
G_OBJECT(window), "destroy",
G_CALLBACK(gtk_widget_destroy), (void*)window
);
// Put a VBox inside the main window
GtkWidget* vbox = gui->vbox = (GtkWidget*)gtk_box_new(
GTK_ORIENTATION_VERTICAL, 0
);
gtk_container_add(GTK_CONTAINER(window), vbox);
// Build the toolbar
GtkToolbar* toolbar = gui->toolbar = (GtkToolbar*)gtk_toolbar_new();
gui->tool_quit = gtk_tool_button_new(NULL, "Quit");
GtkToolItem* sep = (GtkToolItem*)gtk_separator_tool_item_new();
gtk_toolbar_insert(toolbar, gui->tool_quit, -1);
gtk_toolbar_insert(toolbar, sep, -1);
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(toolbar), FALSE, FALSE, 0);
g_signal_connect_swapped(
G_OBJECT(gui->tool_quit), "clicked",
G_CALLBACK(gtk_widget_destroy), (void*)window
);
// Attach the emulator's screen output drawing area widget
GtkDrawingArea* screen = gui->screen = (GtkDrawingArea*)
gtk_drawing_area_new();
gtk_widget_set_size_request(
GTK_WIDGET(screen),
gui->screen_width, gui->screen_height
);
gtk_widget_set_can_focus(GTK_WIDGET(screen), TRUE);
gtk_widget_add_events(GTK_WIDGET(screen),
GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(screen), FALSE, FALSE, 0);
g_signal_connect(
G_OBJECT(screen), "key-press-event",
G_CALLBACK(on_screen_key_down), (void*)gui
);
g_signal_connect(
G_OBJECT(screen), "key-release-event",
G_CALLBACK(on_screen_key_up), (void*)gui
);
g_signal_connect(
G_OBJECT(screen), "draw",
G_CALLBACK(on_screen_draw), (void*)gui
);
g_signal_connect(
G_OBJECT(screen), "button-press-event",
G_CALLBACK(on_screen_button_pressed), (void*)gui
);
gtk_widget_grab_focus(GTK_WIDGET(screen));
gtk_widget_show_all(GTK_WIDGET(window));
gtk_window_present(GTK_WINDOW(gui->window));
}
gui/gui_refresh_screen [ Functions ]
NAME
gui_refresh_screen
SYNOPSIS
void gui_refresh_screen(gui_t* gui)
FUNCTION
Causes the emulator's window to redraw, eventually causing on_screen_draw to be called.
INPUTS
gui Pointer to a gui_t structure. RESULT
SEE ALSO
on_screen_draw
SOURCE
{
gtk_widget_queue_draw(GTK_WIDGET(gui->screen));
}
gui/gui_terminate [ Functions ]
NAME
gui_terminate
SYNOPSIS
void gui_terminate(gui_t* gui)
FUNCTION
Cleans up all the resources acquired during the processing in gui_init().
This function is expected to ALWAYS succeed, even if initialization is incomplete.
INPUTS
gui -- pointer to the gui_t for the application. RESULT
SOURCE
{
slots_terminate(&gui->slots);
// GTK uses atexit() to automatically clean itself up.
}
gui/gui_terminate_emulator [ Functions ]
NAME
gui_terminate_emulator
SYNOPSIS
void gui_terminate_emulator(int rc)
FUNCTION
Requests the emulator to shutdown cleanly, returning result code rc to the OS that launched it.
NOTE Not to be confused with gui_terminate()!
INPUTS
rc Result code; typically 0 indicates success, while values
between 1..127 inclusive indicate some kind of error.
Values 128 and above may be supported as well, depending on
host OS.
RESULT
SEE ALSO
gui_terminate
SOURCE
{
g_result_code = rc;
g_application_quit(G_APPLICATION(g_gui->app));
}
gui/on_screen_button_pressed [ Functions ]
NAME
on_screen_button_pressed
SYNOPSIS
static gboolean on_screen_button_pressed(GtkWidget* widget, GdkEventButton* e, gpointer data)
FUNCTION
Handle mouse button presses in the virtual screen area.
INPUTS
widget -- set by GTK to the GtkDrawingArea containing the video output. e -- set by GTK to the GdkEventButton describing the specific event. data -- A gpointer back to the gui_t for the application. RESULT Answers whether or not the event has been "handled". Return FALSE to allow other GTK event handlers to run; or TRUE to denote all further processing is unnecessary. This procedure will always return FALSE.
SEE ALSO
on_screen_button_released
SOURCE
{
gui_t* gui = (gui_t*)data;
(void)gui;
(void)e;
gtk_widget_grab_focus(widget);
return FALSE;
}
gui/on_screen_button_released [ Functions ]
NAME
on_screen_button_released
SYNOPSIS
static gboolean on_screen_button_released(GtkWidget* widget, GdkEventButton* e, gpointer data)
FUNCTION
Handle mouse button releases in the virtual screen area.
INPUTS
widget -- set by GTK to the GtkDrawingArea containing the video output. e -- set by GTK to the GdkEventButton describing the specific event. data -- A gpointer back to the gui_t for the application. RESULT Answers whether or not the event has been "handled". Return FALSE to allow other GTK event handlers to run; or TRUE to denote all further processing is unnecessary. This procedure will always return FALSE.
SEE ALSO
on_screen_button_pressed
SOURCE
{
gui_t* gui = (gui_t*)data;
(void)gui;
(void)e;
return FALSE;
}
gui/on_screen_draw [ Functions ]
NAME
on_screen_draw
SYNOPSIS
static gboolean on_screen_draw(GtkWidget* widget, cairo_t* cr, gpointer data)
FUNCTION
Redraw the Kestrel 2's MGIA (or, in later models, CGIA) video output.
INPUTS
widget -- set by GTK to the GtkDrawingArea containing the video output. cr -- set by GTK to the Cairo drawing context. data -- A gpointer back to the gui_t for the application. RESULT Answers whether or not the event has been "handled". Return FALSE to allow other GTK event handlers to run; or TRUE to denote all further processing is unnecessary. This procedure will always return FALSE.
SEE ALSO
gui_init
SOURCE
{
// Get our own widget dimensions
gui_t* gui = (gui_t*)data;
gint width = gtk_widget_get_allocated_width(widget);
gint height = gtk_widget_get_allocated_height(widget);
// Draw monitor "bezel", if the widget is large enough to show it.
if (gui->screen_width < width) {
cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
cairo_rectangle(cr, 0, 0, width, height);
cairo_fill(cr);
}
// Zero pixels are, by default, black.
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_rectangle(cr, 0, 0, gui->screen_width, gui->screen_height);
cairo_fill(cr);
// Draw all the white pixels.
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
bool error = false;
uint8_t* bitmap_data;
slots_translate_address_from_virtual(
&gui->slots,
gui->screen_base, &bitmap_data,
&error
);
if (!error) {
const int stride = cairo_format_stride_for_width(
CAIRO_FORMAT_A1, gui->screen_width
);
cairo_surface_t* bitmap_surface =
cairo_image_surface_create_for_data(
bitmap_data,
CAIRO_FORMAT_A1,
gui->screen_width,
gui->screen_height,
stride
);
cairo_mask_surface(cr, bitmap_surface, 0, 0);
cairo_surface_destroy(bitmap_surface);
}
else {
fprintf(stderr,
"K2GUI001 Attempt to refresh display from "
"something not RAM: $%08X\n", gui->screen_base
);
}
return FALSE;
}
gui/on_screen_key_down [ Functions ]
NAME
on_screen_key_down
SYNOPSIS
static bool on_screen_key_down(GtkEventControllerKey *controller, GdkEventKey* event, gpointer data)
FUNCTION
Handle keyboard button presses in the virtual screen area.
INPUTS
controller -- set by GTK to the GtkDrawingArea's keyboard controller. event -- set by GTK to the GdkEventKey describing the specific event. data -- A gpointer back to the gui_t for the application. RESULT Answers whether or not the event has been "handled". Return FALSE to allow other GTK event handlers to run; or TRUE to denote all further processing is unnecessary.
SEE ALSO
on_screen_key_up
SOURCE
{
return rose_event_key_down((gui_t*)data, event->keyval);
}
gui/on_screen_key_up [ Functions ]
NAME
on_screen_key_up
SYNOPSIS
static bool on_screen_key_up(GtkEventControllerKey *controller, GdkEventKey* event, gpointer data)
FUNCTION
Handle keyboard button releases in the virtual screen area.
INPUTS
controller -- set by GTK to the GtkDrawingArea's keyboard controller. event -- set by GTK to the GdkEventKey describing the specific event. data -- A gpointer back to the gui_t for the application. RESULT Answers whether or not the event has been "handled". Return FALSE to allow other GTK event handlers to run; or TRUE to denote all further processing is unnecessary.
SEE ALSO
on_screen_key_down
SOURCE
{
return rose_event_key_up((gui_t*)data, event->keyval);
}