bsnes/ananke/phoenix/gtk/window.cpp

363 lines
12 KiB
C++
Raw Normal View History

Update to v091r11 release. byuu says: This release refines HSU1 support as a bidirectional protocol, nests SFC manifests as "release/cartridge" and "release/information" (but release/ is not guaranteed to be finalized just yet), removes the database integration, and adds support for ananke. ananke represents inevitability. It's a library that, when installed, higan can use to load files from the command-line, and also from a new File -> Load Game menu option. I need to change the build rules a bit for it to work on Windows (need to make phoenix a DLL, basically), but it works now on Linux. Right now, it only takes *.sfc file names, looks them up in the included database, converts them to game folders, and returns the game folder path for higan to load. The idea is to continue expanding it to support everything we can that I don't want in the higan core: - load *.sfc, *.smc, *.swc, *.fig files - remove SNES copier headers - split apart merged firmware files - pull in external firmware files (eg dsp1b.rom - these are staying merged, just as SPC7110 prg+dat are merged) - load *.zip and *.7z archives - prompt for selection on multi-file archives - generate manifest files based on heuristics - apply BPS patches The "Load" menu option has been renamed to "Library", to represent games in your library. I'm going to add some sort of suffix to indicate unverified games, and use a different folder icon for those (eg manifests built on heuristics rather than from the database.) So basically, to future end users: File -> Load Game will be how they play games. Library -> (specific system) can be thought of as an infinitely-sized recent games list. purify will likely become a simple stub that invokes ananke's functions. No reason to duplicate all that code.
2012-11-05 08:22:50 +00:00
static gint Window_close(GtkWidget *widget, GdkEvent *event, Window *window) {
window->state.ignore = false;
if(window->onClose) window->onClose();
if(window->state.ignore == false) window->setVisible(false);
return true;
}
static gboolean Window_expose(GtkWidget *widget, GdkEvent *event, Window *window) {
if(window->state.backgroundColorOverride == false) return false;
cairo_t *context = gdk_cairo_create(widget->window);
Color color = window->backgroundColor();
double red = (double)color.red / 255.0;
double green = (double)color.green / 255.0;
double blue = (double)color.blue / 255.0;
double alpha = (double)color.alpha / 255.0;
if(gdk_screen_is_composited(gdk_screen_get_default())) {
cairo_set_source_rgba(context, red, green, blue, alpha);
} else {
cairo_set_source_rgb(context, red, green, blue);
}
cairo_set_operator(context, CAIRO_OPERATOR_SOURCE);
cairo_paint(context);
cairo_destroy(context);
return false;
}
static gboolean Window_configure(GtkWidget *widget, GdkEvent *event, Window *window) {
if(gtk_widget_get_realized(window->p.widget) == false) return false;
Update to higan v091r14 and ananke v00r03 releases. byuu says: higan changelog: - generates title displayed in emulator window by asking the core - core builds title solely from "information/title" ... if it's not there, you don't get a title at all - sub-system load menu is gone ... since there are multiple revisions of the SGB, this never really worked well anyway - to load an SGB, BS-X or ST cartridge, load the base cartridge first - "File->Load Game" moved to "Load->Import Game" ... may cause a bit of confusion to new users, but I don't like having a single-item menu, we'll just have to explain it to new users - browser window redone to look like ananke - home button here goes to ~/Emulation rather than just ~ like ananke, since this is the home of game folders - game folder icon is now the executable icon for the Tango theme (orange diamond), meant to represent a complete game rather than a game file or archive ananke changelog: - outputs GBC games to "Game Boy Color/" instead of "Game Boy/" - adds the file basename to "information/title" Known issues: - using ananke to load a GB game trips the Super Famicom SGB mode and fails (need to make the full-path auto-detection ignore non-bootable systems) - need to dump and test some BS-X media before releasing - ananke lacks BS-X Satellaview cartridge support - v092 isn't going to let you retarget the ananke/higan game folder path of ~/Emulation, you will have to wait for a future version if that bothers you so greatly [Later, after the v092 release, byuu posted this additional changelog: - kill laevateinn - add title() - add bootable, remove load - combine file, library - combine [][][] paths - fix SFC subtype handling XML->BML - update file browser to use buttons - update file browser keyboard handling - update system XML->BML - fix sufami turbo hashing - remove Cartridge::manifest ]
2012-12-25 05:31:55 +00:00
if(window->visible() == false) return false;
Update to v091r11 release. byuu says: This release refines HSU1 support as a bidirectional protocol, nests SFC manifests as "release/cartridge" and "release/information" (but release/ is not guaranteed to be finalized just yet), removes the database integration, and adds support for ananke. ananke represents inevitability. It's a library that, when installed, higan can use to load files from the command-line, and also from a new File -> Load Game menu option. I need to change the build rules a bit for it to work on Windows (need to make phoenix a DLL, basically), but it works now on Linux. Right now, it only takes *.sfc file names, looks them up in the included database, converts them to game folders, and returns the game folder path for higan to load. The idea is to continue expanding it to support everything we can that I don't want in the higan core: - load *.sfc, *.smc, *.swc, *.fig files - remove SNES copier headers - split apart merged firmware files - pull in external firmware files (eg dsp1b.rom - these are staying merged, just as SPC7110 prg+dat are merged) - load *.zip and *.7z archives - prompt for selection on multi-file archives - generate manifest files based on heuristics - apply BPS patches The "Load" menu option has been renamed to "Library", to represent games in your library. I'm going to add some sort of suffix to indicate unverified games, and use a different folder icon for those (eg manifests built on heuristics rather than from the database.) So basically, to future end users: File -> Load Game will be how they play games. Library -> (specific system) can be thought of as an infinitely-sized recent games list. purify will likely become a simple stub that invokes ananke's functions. No reason to duplicate all that code.
2012-11-05 08:22:50 +00:00
GdkWindow *gdkWindow = gtk_widget_get_window(widget);
GdkRectangle border, client;
gdk_window_get_frame_extents(gdkWindow, &border);
gdk_window_get_geometry(gdkWindow, 0, 0, &client.width, &client.height, 0);
gdk_window_get_origin(gdkWindow, &client.x, &client.y);
if(window->state.fullScreen == false) {
//update geometry settings
settings->frameGeometryX = client.x - border.x;
settings->frameGeometryY = client.y - border.y;
settings->frameGeometryWidth = border.width - client.width;
settings->frameGeometryHeight = border.height - client.height;
if(window->state.backgroundColorOverride == false) {
GdkColor color = widget->style->bg[GTK_STATE_NORMAL];
settings->windowBackgroundColor
= ((uint8_t)(color.red >> 8) << 16)
+ ((uint8_t)(color.green >> 8) << 8)
+ ((uint8_t)(color.blue >> 8) << 0);
}
settings->save();
}
Geometry geometry = {
client.x,
client.y + window->p.menuHeight(),
client.width,
client.height - window->p.menuHeight() - window->p.statusHeight()
};
//move
if(geometry.x != window->state.geometry.x || geometry.y != window->state.geometry.y) {
if(window->state.fullScreen == false) {
window->state.geometry.x = geometry.x;
window->state.geometry.y = geometry.y;
}
if(window->p.locked == false && window->onMove) window->onMove();
}
//size
if(geometry.width != window->state.geometry.width || geometry.height != window->state.geometry.height) {
window->p.onSizePending = true;
}
return false;
}
static gboolean Window_keyPressEvent(GtkWidget *widget, GdkEventKey *event, Window *window) {
Keyboard::Keycode key = Keysym(event->keyval);
if(key != Keyboard::Keycode::None && window->onKeyPress) window->onKeyPress(key);
return false;
}
static gboolean Window_keyReleaseEvent(GtkWidget *widget, GdkEventKey *event, Window *window) {
Keyboard::Keycode key = Keysym(event->keyval);
if(key != Keyboard::Keycode::None && window->onKeyRelease) window->onKeyRelease(key);
return false;
}
static void Window_sizeAllocate(GtkWidget *widget, GtkAllocation *allocation, Window *window) {
//size-allocate sent from gtk_fixed_move(); detect if layout unchanged and return
if(allocation->width == window->p.lastAllocation.width
&& allocation->height == window->p.lastAllocation.height) return;
window->state.geometry.width = allocation->width;
window->state.geometry.height = allocation->height;
for(auto &layout : window->state.layout) {
Geometry geometry = window->geometry();
geometry.x = geometry.y = 0;
layout.setGeometry(geometry);
}
if(window->p.onSizePending && window->p.locked == false && window->onSize) {
window->p.onSizePending = false;
window->onSize();
}
window->p.lastAllocation = *allocation;
}
static void Window_sizeRequest(GtkWidget *widget, GtkRequisition *requisition, Window *window) {
requisition->width = window->state.geometry.width;
requisition->height = window->state.geometry.height;
}
Window& pWindow::none() {
static Window *window = nullptr;
if(window == nullptr) window = new Window;
return *window;
}
void pWindow::append(Layout &layout) {
Geometry geometry = this->geometry();
geometry.x = geometry.y = 0;
layout.setGeometry(geometry);
}
void pWindow::append(Menu &menu) {
if(window.state.menuFont != "") menu.p.setFont(window.state.menuFont);
else menu.p.setFont("Sans, 8");
gtk_menu_shell_append(GTK_MENU_SHELL(this->menu), menu.p.widget);
gtk_widget_show(menu.p.widget);
}
void pWindow::append(Widget &widget) {
((Sizable&)widget).state.window = &window;
gtk_fixed_put(GTK_FIXED(formContainer), widget.p.gtkWidget, 0, 0);
if(widget.state.font != "") widget.p.setFont(widget.state.font);
else if(window.state.widgetFont != "") widget.p.setFont(window.state.widgetFont);
else widget.p.setFont("Sans, 8");
widget.setVisible(widget.visible());
}
Color pWindow::backgroundColor() {
if(window.state.backgroundColorOverride) return window.state.backgroundColor;
return {
(uint8_t)(settings->windowBackgroundColor >> 16),
(uint8_t)(settings->windowBackgroundColor >> 8),
(uint8_t)(settings->windowBackgroundColor >> 0),
255
};
}
Geometry pWindow::frameMargin() {
if(window.state.fullScreen) return {
0,
menuHeight(),
0,
menuHeight() + statusHeight()
};
return {
settings->frameGeometryX,
settings->frameGeometryY + menuHeight(),
settings->frameGeometryWidth,
settings->frameGeometryHeight + menuHeight() + statusHeight()
};
}
bool pWindow::focused() {
return gtk_window_is_active(GTK_WINDOW(widget));
}
Geometry pWindow::geometry() {
if(window.state.fullScreen == true) return {
0,
menuHeight(),
Desktop::size().width,
Desktop::size().height - menuHeight() - statusHeight()
};
return window.state.geometry;
}
void pWindow::remove(Layout &layout) {
}
void pWindow::remove(Menu &menu) {
menu.p.orphan();
}
void pWindow::remove(Widget &widget) {
widget.p.orphan();
}
void pWindow::setBackgroundColor(const Color &color) {
GdkColor gdkColor;
gdkColor.pixel = (color.red << 16) | (color.green << 8) | (color.blue << 0);
gdkColor.red = (color.red << 8) | (color.red << 0);
gdkColor.green = (color.green << 8) | (color.green << 0);
gdkColor.blue = (color.blue << 8) | (color.blue << 0);
gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &gdkColor);
}
void pWindow::setFocused() {
gtk_window_present(GTK_WINDOW(widget));
}
void pWindow::setFullScreen(bool fullScreen) {
if(fullScreen == false) {
gtk_window_unfullscreen(GTK_WINDOW(widget));
} else {
gtk_window_fullscreen(GTK_WINDOW(widget));
}
}
void pWindow::setGeometry(const Geometry &geometry) {
Geometry margin = frameMargin();
gtk_window_move(GTK_WINDOW(widget), geometry.x - margin.x, geometry.y - margin.y);
GdkGeometry geom;
geom.min_width = window.state.resizable ? 1 : window.state.geometry.width;
geom.min_height = window.state.resizable ? 1 : window.state.geometry.height;
gtk_window_set_geometry_hints(GTK_WINDOW(widget), GTK_WIDGET(widget), &geom, GDK_HINT_MIN_SIZE);
//gtk_window_set_policy(GTK_WINDOW(widget), true, true, false);
gtk_widget_set_size_request(formContainer, geometry.width, geometry.height);
gtk_window_resize(GTK_WINDOW(widget), geometry.width, geometry.height + menuHeight() + statusHeight());
}
void pWindow::setMenuFont(const string &font) {
for(auto &item : window.state.menu) item.p.setFont(font);
}
void pWindow::setMenuVisible(bool visible) {
gtk_widget_set_visible(menu, visible);
}
void pWindow::setModal(bool modal) {
gtk_window_set_modal(GTK_WINDOW(widget), modal);
}
void pWindow::setResizable(bool resizable) {
gtk_window_set_resizable(GTK_WINDOW(widget), resizable);
gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(status), resizable);
}
void pWindow::setStatusFont(const string &font) {
pFont::setFont(status, font);
}
void pWindow::setStatusText(const string &text) {
gtk_statusbar_pop(GTK_STATUSBAR(status), 1);
gtk_statusbar_push(GTK_STATUSBAR(status), 1, text);
}
void pWindow::setStatusVisible(bool visible) {
gtk_widget_set_visible(status, visible);
}
void pWindow::setTitle(const string &text) {
gtk_window_set_title(GTK_WINDOW(widget), text);
}
void pWindow::setVisible(bool visible) {
gtk_widget_set_visible(widget, visible);
if(visible) {
if(gtk_widget_get_visible(menu)) {
GtkAllocation allocation;
gtk_widget_get_allocation(menu, &allocation);
settings->menuGeometryHeight = allocation.height;
}
if(gtk_widget_get_visible(status)) {
GtkAllocation allocation;
gtk_widget_get_allocation(status, &allocation);
settings->statusGeometryHeight = allocation.height;
}
}
}
void pWindow::setWidgetFont(const string &font) {
for(auto &item : window.state.widget) {
if(item.state.font == "") item.setFont(font);
}
}
void pWindow::constructor() {
lastAllocation.width = 0;
lastAllocation.height = 0;
onSizePending = false;
widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2013-01-14 12:10:20 +00:00
//if program was given a name, try and set the window taskbar icon from one of the pixmaps folders
if(osState.name.empty() == false) {
if(file::exists({"/usr/share/pixmaps/", osState.name, ".png"})) {
gtk_window_set_icon_from_file(GTK_WINDOW(widget), string{"/usr/share/pixmaps/", osState.name, ".png"}, nullptr);
} else if(file::exists({"/usr/local/share/pixmaps/", osState.name, ".png"})) {
gtk_window_set_icon_from_file(GTK_WINDOW(widget), string{"/usr/local/share/pixmaps/", osState.name, ".png"}, nullptr);
}
}
Update to v091r11 release. byuu says: This release refines HSU1 support as a bidirectional protocol, nests SFC manifests as "release/cartridge" and "release/information" (but release/ is not guaranteed to be finalized just yet), removes the database integration, and adds support for ananke. ananke represents inevitability. It's a library that, when installed, higan can use to load files from the command-line, and also from a new File -> Load Game menu option. I need to change the build rules a bit for it to work on Windows (need to make phoenix a DLL, basically), but it works now on Linux. Right now, it only takes *.sfc file names, looks them up in the included database, converts them to game folders, and returns the game folder path for higan to load. The idea is to continue expanding it to support everything we can that I don't want in the higan core: - load *.sfc, *.smc, *.swc, *.fig files - remove SNES copier headers - split apart merged firmware files - pull in external firmware files (eg dsp1b.rom - these are staying merged, just as SPC7110 prg+dat are merged) - load *.zip and *.7z archives - prompt for selection on multi-file archives - generate manifest files based on heuristics - apply BPS patches The "Load" menu option has been renamed to "Library", to represent games in your library. I'm going to add some sort of suffix to indicate unverified games, and use a different folder icon for those (eg manifests built on heuristics rather than from the database.) So basically, to future end users: File -> Load Game will be how they play games. Library -> (specific system) can be thought of as an infinitely-sized recent games list. purify will likely become a simple stub that invokes ananke's functions. No reason to duplicate all that code.
2012-11-05 08:22:50 +00:00
if(gdk_screen_is_composited(gdk_screen_get_default())) {
gtk_widget_set_colormap(widget, gdk_screen_get_rgba_colormap(gdk_screen_get_default()));
} else {
gtk_widget_set_colormap(widget, gdk_screen_get_rgb_colormap(gdk_screen_get_default()));
}
gtk_window_set_resizable(GTK_WINDOW(widget), true);
#if GTK_MAJOR_VERSION >= 3
gtk_window_set_has_resize_grip(GTK_WINDOW(widget), false);
#endif
gtk_widget_set_app_paintable(widget, true);
gtk_widget_add_events(widget, GDK_CONFIGURE);
menuContainer = gtk_vbox_new(false, 0);
gtk_container_add(GTK_CONTAINER(widget), menuContainer);
gtk_widget_show(menuContainer);
menu = gtk_menu_bar_new();
gtk_box_pack_start(GTK_BOX(menuContainer), menu, false, false, 0);
formContainer = gtk_fixed_new();
gtk_box_pack_start(GTK_BOX(menuContainer), formContainer, true, true, 0);
gtk_widget_show(formContainer);
statusContainer = gtk_event_box_new();
status = gtk_statusbar_new();
gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(status), true);
gtk_container_add(GTK_CONTAINER(statusContainer), status);
gtk_box_pack_start(GTK_BOX(menuContainer), statusContainer, false, false, 0);
gtk_widget_show(statusContainer);
setTitle("");
setResizable(window.state.resizable);
setGeometry(window.state.geometry);
setMenuFont("Sans, 8");
setStatusFont("Sans, 8");
g_signal_connect(G_OBJECT(widget), "delete-event", G_CALLBACK(Window_close), (gpointer)&window);
g_signal_connect(G_OBJECT(widget), "expose-event", G_CALLBACK(Window_expose), (gpointer)&window);
g_signal_connect(G_OBJECT(widget), "configure-event", G_CALLBACK(Window_configure), (gpointer)&window);
g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(Window_keyPressEvent), (gpointer)&window);
g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(Window_keyPressEvent), (gpointer)&window);
g_signal_connect(G_OBJECT(formContainer), "size-allocate", G_CALLBACK(Window_sizeAllocate), (gpointer)&window);
g_signal_connect(G_OBJECT(formContainer), "size-request", G_CALLBACK(Window_sizeRequest), (gpointer)&window);
}
unsigned pWindow::menuHeight() {
return window.state.menuVisible ? settings->menuGeometryHeight : 0;
}
unsigned pWindow::statusHeight() {
return window.state.statusVisible ? settings->statusGeometryHeight : 0;
}