GTK*: Action Replay cheat menu (+ other cheat improvements) (#847)
* GTK cheats UI inputs/displays hex for address (offset) * Added range bound to keep internal cheat data within specified size The decision to change the value column to store G_TYPE_UINT is purely pragmatic--having all data be treated and displayed as unsized is simpler than writing a custom display to handle signedness and cleaner than 1-3 byte ints appearing unsigned and 4 byte int appearing signed. * Added index and cheat type data to ListStore My implementation plan is to use a GtkTreeModelFilter to create separate section for internal and ActionReplay cheats, but I was not convinced the indices in the tree path would correspond to the indices in , hence the additional column. * Filter raw and AR cheats, display both filters in UI, patch up raw update/delete * Action Replay UI elements [GTK] * Memory leak fixes + some additional clean-up comments and small bug patches * Backport to GTK2
This commit is contained in:
parent
2bc5b0d86b
commit
efdd938dc3
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2009-2023 DeSmuME team
|
Copyright (C) 2009-2024 DeSmuME team
|
||||||
|
|
||||||
This file is free software: you can redistribute it and/or modify
|
This file is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -849,6 +849,16 @@ bool CHEATS::remove(const size_t pos)
|
||||||
return didRemoveItem;
|
return didRemoveItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CHEATS::toggle(bool enabled, const size_t pos)
|
||||||
|
{
|
||||||
|
this->_list[pos].enabled = (enabled) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHEATS::toggle(u8 enabled, const size_t pos)
|
||||||
|
{
|
||||||
|
this->toggle((enabled != 0), pos);
|
||||||
|
}
|
||||||
|
|
||||||
void CHEATS::getListReset()
|
void CHEATS::getListReset()
|
||||||
{
|
{
|
||||||
this->_currentGet = 0;
|
this->_currentGet = 0;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2009-2023 DeSmuME team
|
Copyright (C) 2009-2024 DeSmuME team
|
||||||
|
|
||||||
This file is free software: you can redistribute it and/or modify
|
This file is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -120,6 +120,9 @@ public:
|
||||||
|
|
||||||
bool remove(const size_t pos);
|
bool remove(const size_t pos);
|
||||||
|
|
||||||
|
void toggle(bool enabled, const size_t pos);
|
||||||
|
void toggle(u8 enablbed, const size_t pos);
|
||||||
|
|
||||||
void getListReset();
|
void getListReset();
|
||||||
bool getList(CHEATS_LIST *cheat);
|
bool getList(CHEATS_LIST *cheat);
|
||||||
CHEATS_LIST* getListPtr();
|
CHEATS_LIST* getListPtr();
|
||||||
|
|
|
@ -21,6 +21,7 @@ desmume_SOURCES = \
|
||||||
desmume.h desmume.cpp \
|
desmume.h desmume.cpp \
|
||||||
dTool.h dToolsList.cpp \
|
dTool.h dToolsList.cpp \
|
||||||
tools/ioregsView.cpp tools/ioregsView.h \
|
tools/ioregsView.cpp tools/ioregsView.h \
|
||||||
|
utilsGTK.h utilsGTK.cpp \
|
||||||
cheatsGTK.h cheatsGTK.cpp \
|
cheatsGTK.h cheatsGTK.cpp \
|
||||||
main.cpp main.h
|
main.cpp main.h
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* cheats.cpp - this file is part of DeSmuME
|
/* cheatsGTK.cpp - this file is part of DeSmuME
|
||||||
*
|
*
|
||||||
* Copyright (C) 2006-2023 DeSmuME Team
|
* Copyright (C) 2006-2024 DeSmuME Team
|
||||||
*
|
*
|
||||||
* This file is free software; you can redistribute it and/or modify
|
* This file is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,12 +18,12 @@
|
||||||
* Boston, MA 02111-1307, USA.
|
* Boston, MA 02111-1307, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include "cheatsGTK.h"
|
#include "cheatsGTK.h"
|
||||||
#include "cheatSystem.h"
|
#include "cheatSystem.h"
|
||||||
|
#include "utilsGTK.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "desmume.h"
|
#include "desmume.h"
|
||||||
|
|
||||||
|
@ -31,10 +31,13 @@
|
||||||
#define GPOINTER_TO_INT(p) ((gint) (glong) (p))
|
#define GPOINTER_TO_INT(p) ((gint) (glong) (p))
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
COLUMN_INDEX,
|
||||||
|
COLUMN_TYPE,
|
||||||
COLUMN_ENABLED,
|
COLUMN_ENABLED,
|
||||||
COLUMN_SIZE,
|
COLUMN_SIZE,
|
||||||
COLUMN_HI,
|
COLUMN_HI,
|
||||||
COLUMN_LO,
|
COLUMN_LO,
|
||||||
|
COLUMN_AR,
|
||||||
COLUMN_DESC,
|
COLUMN_DESC,
|
||||||
NUM_COL
|
NUM_COL
|
||||||
};
|
};
|
||||||
|
@ -56,10 +59,13 @@ static struct {
|
||||||
gint type;
|
gint type;
|
||||||
gint column;
|
gint column;
|
||||||
} columnTable[]={
|
} columnTable[]={
|
||||||
|
{ "Index", TYPE_STRING, COLUMN_INDEX},
|
||||||
|
{ "Type", TYPE_STRING, COLUMN_TYPE},
|
||||||
{ "Enabled", TYPE_TOGGLE, COLUMN_ENABLED},
|
{ "Enabled", TYPE_TOGGLE, COLUMN_ENABLED},
|
||||||
{ "Size", TYPE_COMBO, COLUMN_SIZE},
|
{ "Size", TYPE_COMBO, COLUMN_SIZE},
|
||||||
{ "Offset", TYPE_STRING, COLUMN_HI},
|
{ "Address", TYPE_STRING, COLUMN_HI},
|
||||||
{ "Value", TYPE_STRING, COLUMN_LO},
|
{ "Value", TYPE_STRING, COLUMN_LO},
|
||||||
|
{ "AR Code", TYPE_STRING, COLUMN_AR},
|
||||||
{ "Description", TYPE_STRING, COLUMN_DESC}
|
{ "Description", TYPE_STRING, COLUMN_DESC}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,7 +73,7 @@ static GtkWidget *win = NULL;
|
||||||
static BOOL shouldBeRunning = FALSE;
|
static BOOL shouldBeRunning = FALSE;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------
|
||||||
// SEARCH
|
// CHEATS MENU
|
||||||
// ---------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -75,28 +81,28 @@ enabled_toggled(GtkCellRendererToggle * cell,
|
||||||
gchar * path_str, gpointer data)
|
gchar * path_str, gpointer data)
|
||||||
{
|
{
|
||||||
GtkTreeModel *model = (GtkTreeModel *) data;
|
GtkTreeModel *model = (GtkTreeModel *) data;
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter, f_iter;
|
||||||
GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
|
GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
|
||||||
gboolean guiEnabled;
|
gboolean guiEnabled;
|
||||||
|
|
||||||
gtk_tree_model_get_iter(model, &iter, path);
|
gtk_tree_model_get_iter(model, &f_iter, path);
|
||||||
gtk_tree_model_get(model, &iter, COLUMN_ENABLED, &guiEnabled, -1);
|
gtk_tree_model_filter_convert_iter_to_child_iter(
|
||||||
|
GTK_TREE_MODEL_FILTER(model), &iter, &f_iter);
|
||||||
|
GtkTreeModel *store =
|
||||||
|
gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
|
||||||
|
|
||||||
|
gtk_tree_model_get(store, &iter, COLUMN_ENABLED, &guiEnabled, -1);
|
||||||
|
|
||||||
guiEnabled ^= 1;
|
guiEnabled ^= 1;
|
||||||
const bool cheatEnabled = (guiEnabled) ? true : false;
|
const bool cheatEnabled = (guiEnabled) ? true : false;
|
||||||
CHEATS_LIST tempCheatItem;
|
CHEATS_LIST tempCheatItem;
|
||||||
u32 ii;
|
u32 ii;
|
||||||
GtkTreePath *path1;
|
|
||||||
|
|
||||||
path1 = gtk_tree_model_get_path (model, &iter);
|
gtk_tree_model_get(store, &iter, COLUMN_INDEX, &ii, -1);
|
||||||
ii = gtk_tree_path_get_indices (path)[0];
|
|
||||||
|
|
||||||
cheats->copyItemFromIndex(ii, tempCheatItem);
|
cheats->toggle(cheatEnabled, ii);
|
||||||
|
|
||||||
cheats->update(tempCheatItem.size, tempCheatItem.code[0][0], tempCheatItem.code[0][1], tempCheatItem.description,
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, COLUMN_ENABLED, guiEnabled, -1);
|
||||||
cheatEnabled, ii);
|
|
||||||
|
|
||||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLUMN_ENABLED, guiEnabled, -1);
|
|
||||||
|
|
||||||
gtk_tree_path_free(path);
|
gtk_tree_path_free(path);
|
||||||
}
|
}
|
||||||
|
@ -107,51 +113,74 @@ static void cheat_list_modify_cheat(GtkCellRendererText * cell,
|
||||||
{
|
{
|
||||||
GtkTreeModel *model = (GtkTreeModel *) data;
|
GtkTreeModel *model = (GtkTreeModel *) data;
|
||||||
GtkTreePath *path = gtk_tree_path_new_from_string(path_string);
|
GtkTreePath *path = gtk_tree_path_new_from_string(path_string);
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter, f_iter;
|
||||||
|
|
||||||
gint column =
|
gint column =
|
||||||
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column"));
|
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column"));
|
||||||
|
|
||||||
gtk_tree_model_get_iter(model, &iter, path);
|
gtk_tree_model_get_iter(model, &f_iter, path);
|
||||||
|
gtk_tree_path_free(path);
|
||||||
|
|
||||||
|
gtk_tree_model_filter_convert_iter_to_child_iter(
|
||||||
|
GTK_TREE_MODEL_FILTER(model), &iter, &f_iter);
|
||||||
|
GtkTreeModel *store =
|
||||||
|
gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
|
||||||
|
|
||||||
{
|
{
|
||||||
u32 ii;
|
u32 ii;
|
||||||
GtkTreePath *path1;
|
|
||||||
CHEATS_LIST cheat;
|
CHEATS_LIST cheat;
|
||||||
|
|
||||||
path1 = gtk_tree_model_get_path (model, &iter);
|
gtk_tree_model_get(store, &iter, COLUMN_INDEX, &ii, -1);
|
||||||
ii = gtk_tree_path_get_indices (path)[0];
|
|
||||||
|
|
||||||
cheats->copyItemFromIndex(ii, cheat);
|
cheats->copyItemFromIndex(ii, cheat);
|
||||||
|
|
||||||
gtk_tree_path_free (path1);
|
|
||||||
|
|
||||||
if (column == COLUMN_LO || column == COLUMN_HI
|
if (column == COLUMN_LO || column == COLUMN_HI
|
||||||
|| column == COLUMN_SIZE) {
|
|| column == COLUMN_SIZE) {
|
||||||
u32 v = atoi(new_text);
|
u32 v = 0;
|
||||||
|
u32 data;
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case COLUMN_SIZE:
|
case COLUMN_SIZE:
|
||||||
cheats->update(v-1, cheat.code[0][0], cheat.code[0][1],
|
v = atoi(new_text);
|
||||||
|
// If the size is reduced, the data is currently contains may be
|
||||||
|
// out of range, so cap it at its maximum value.
|
||||||
|
data = std::min(0xFFFFFFFF >> (24 - ((v - 1) << 3)),
|
||||||
|
cheat.code[0][1]);
|
||||||
|
cheats->update(v-1, cheat.code[0][0], data,
|
||||||
cheat.description, cheat.enabled, ii);
|
cheat.description, cheat.enabled, ii);
|
||||||
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, COLUMN_LO,
|
||||||
|
data, -1);
|
||||||
break;
|
break;
|
||||||
case COLUMN_HI:
|
case COLUMN_HI:
|
||||||
|
sscanf(new_text, "%x", &v);
|
||||||
|
v &= 0x0FFFFFFF;
|
||||||
cheats->update(cheat.size, v, cheat.code[0][1], cheat.description,
|
cheats->update(cheat.size, v, cheat.code[0][1], cheat.description,
|
||||||
cheat.enabled, ii);
|
cheat.enabled, ii);
|
||||||
break;
|
break;
|
||||||
case COLUMN_LO:
|
case COLUMN_LO:
|
||||||
|
v = atoi(new_text);
|
||||||
|
v = std::min(0xFFFFFFFF >> (24 - (cheat.size << 3)), v);
|
||||||
cheats->update(cheat.size, cheat.code[0][0], v, cheat.description,
|
cheats->update(cheat.size, cheat.code[0][0], v, cheat.description,
|
||||||
cheat.enabled, ii);
|
cheat.enabled, ii);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, column,
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, column, v, -1);
|
||||||
atoi(new_text), -1);
|
|
||||||
} else if (column == COLUMN_DESC){
|
} else if (column == COLUMN_DESC){
|
||||||
cheats->update(cheat.size, cheat.code[0][0], cheat.code[0][1],
|
cheats->setDescription(new_text, ii);
|
||||||
g_strdup(new_text), cheat.enabled, ii);
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, column,
|
||||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, column,
|
new_text, -1);
|
||||||
g_strdup(new_text), -1);
|
} else if (column == COLUMN_AR) {
|
||||||
|
// Safety: CHEATS::update_AR, though it takes `code` as not const,
|
||||||
|
// only performs a non-null check and passes `code` to
|
||||||
|
// CHEATS::XXCodeFromString as const, therefore new_text (should)
|
||||||
|
// never be modified
|
||||||
|
bool isValid =
|
||||||
|
cheats->update_AR(const_cast<char *>(new_text),
|
||||||
|
cheat.description, cheat.enabled, ii);
|
||||||
|
if (isValid) {
|
||||||
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, column,
|
||||||
|
new_text, -1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,17 +189,29 @@ static void cheat_list_remove_cheat(GtkWidget * widget, gpointer data)
|
||||||
GtkTreeView *tree = (GtkTreeView *) data;
|
GtkTreeView *tree = (GtkTreeView *) data;
|
||||||
GtkTreeSelection *selection = gtk_tree_view_get_selection (tree);
|
GtkTreeSelection *selection = gtk_tree_view_get_selection (tree);
|
||||||
GtkTreeModel *model = gtk_tree_view_get_model (tree);
|
GtkTreeModel *model = gtk_tree_view_get_model (tree);
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter, f_iter;
|
||||||
|
|
||||||
if (gtk_tree_selection_get_selected (selection, NULL, &iter)){
|
if (gtk_tree_selection_get_selected (selection, NULL, &f_iter)){
|
||||||
u32 ii;
|
u32 ii;
|
||||||
|
gboolean valid;
|
||||||
GtkTreePath *path;
|
GtkTreePath *path;
|
||||||
|
|
||||||
path = gtk_tree_model_get_path (model, &iter);
|
path = gtk_tree_model_get_path (model, &f_iter);
|
||||||
ii = gtk_tree_path_get_indices (path)[0];
|
gtk_tree_model_filter_convert_iter_to_child_iter(
|
||||||
|
GTK_TREE_MODEL_FILTER(model), &iter, &f_iter);
|
||||||
|
GtkTreeModel *store =
|
||||||
|
gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
|
||||||
|
|
||||||
gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
|
gtk_tree_model_get(store, &iter, COLUMN_INDEX, &ii, -1);
|
||||||
|
|
||||||
|
valid = gtk_list_store_remove(GTK_LIST_STORE(store), &iter);
|
||||||
cheats->remove(ii);
|
cheats->remove(ii);
|
||||||
|
while (valid) {
|
||||||
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, COLUMN_INDEX, ii,
|
||||||
|
-1);
|
||||||
|
ii++;
|
||||||
|
valid = gtk_tree_model_iter_next(store, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
gtk_tree_path_free (path);
|
gtk_tree_path_free (path);
|
||||||
}
|
}
|
||||||
|
@ -181,17 +222,42 @@ static void cheat_list_add_cheat(GtkWidget * widget, gpointer data)
|
||||||
#define NEW_DESC "New cheat"
|
#define NEW_DESC "New cheat"
|
||||||
GtkListStore *store = (GtkListStore *) data;
|
GtkListStore *store = (GtkListStore *) data;
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
cheats->add(1, 0, 0, g_strdup(NEW_DESC), false);
|
// Safety: CHEATS::add only uses `description` to call CHEATS::update, which
|
||||||
|
// only uses it to call CHEATS::setDescription, which takes `description` as
|
||||||
|
// const.
|
||||||
|
cheats->add(0, 0, 0, const_cast<char *>(NEW_DESC), false);
|
||||||
gtk_list_store_append(store, &iter);
|
gtk_list_store_append(store, &iter);
|
||||||
gtk_list_store_set(store, &iter,
|
gtk_list_store_set(store, &iter,
|
||||||
|
COLUMN_INDEX, cheats->getListSize() - 1,
|
||||||
|
COLUMN_TYPE, 0,
|
||||||
COLUMN_ENABLED, FALSE,
|
COLUMN_ENABLED, FALSE,
|
||||||
COLUMN_SIZE, 1,
|
COLUMN_SIZE, 1,
|
||||||
COLUMN_HI, 0,
|
COLUMN_HI, 0,
|
||||||
COLUMN_LO, 0, COLUMN_DESC, NEW_DESC, -1);
|
COLUMN_LO, 0, COLUMN_DESC, NEW_DESC, -1);
|
||||||
|
|
||||||
#undef NEW_DESC
|
#undef NEW_DESC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void cheat_list_add_cheat_AR(GtkWidget *widget, gpointer data)
|
||||||
|
{
|
||||||
|
#define NEW_DESC "New cheat"
|
||||||
|
#define NEW_AR "00000000 00000000"
|
||||||
|
GtkListStore *store = (GtkListStore *) data;
|
||||||
|
GtkTreeIter iter;
|
||||||
|
// Safety: CHEATS::add_AR only uses `code` to call , `description` to call
|
||||||
|
// CHEATS::setDescription, which takes the variable as const.
|
||||||
|
cheats->add_AR(const_cast<char *>(NEW_AR), const_cast<char *>(NEW_DESC),
|
||||||
|
false);
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter,
|
||||||
|
COLUMN_INDEX, cheats->getListSize() - 1,
|
||||||
|
COLUMN_TYPE, 1,
|
||||||
|
COLUMN_ENABLED, FALSE,
|
||||||
|
COLUMN_AR, NEW_AR,
|
||||||
|
COLUMN_DESC, NEW_DESC, -1);
|
||||||
|
#undef NEW_DESC
|
||||||
|
#undef NEW_AR
|
||||||
|
}
|
||||||
|
|
||||||
static GtkTreeModel * create_numbers_model (void)
|
static GtkTreeModel * create_numbers_model (void)
|
||||||
{
|
{
|
||||||
#define N_NUMBERS 4
|
#define N_NUMBERS 4
|
||||||
|
@ -222,9 +288,21 @@ static GtkTreeModel * create_numbers_model (void)
|
||||||
#undef N_NUMBERS
|
#undef N_NUMBERS
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store)
|
static void cheat_list_address_to_hex(GtkTreeViewColumn *column,
|
||||||
|
GtkCellRenderer *renderer,
|
||||||
|
GtkTreeModel *model, GtkTreeIter *iter,
|
||||||
|
gpointer data)
|
||||||
{
|
{
|
||||||
|
gint addr;
|
||||||
|
gtk_tree_model_get(model, iter, COLUMN_HI, &addr, -1);
|
||||||
|
gchar *hex_addr = g_strdup_printf("0x0%07X", addr);
|
||||||
|
g_object_set(renderer, "text", hex_addr, NULL);
|
||||||
|
g_free(hex_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store,
|
||||||
|
u8 cheat_type)
|
||||||
|
{
|
||||||
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
|
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
|
||||||
static GtkTreeModel * size_model;
|
static GtkTreeModel * size_model;
|
||||||
|
|
||||||
|
@ -240,10 +318,13 @@ static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store)
|
||||||
attrib = "active";
|
attrib = "active";
|
||||||
break;
|
break;
|
||||||
case TYPE_STRING:
|
case TYPE_STRING:
|
||||||
|
if (cheat_type == CHEAT_TYPE_INTERNAL)
|
||||||
renderer = gtk_cell_renderer_text_new();
|
renderer = gtk_cell_renderer_text_new();
|
||||||
|
else if (cheat_type == CHEAT_TYPE_AR)
|
||||||
|
renderer = desmume_cell_renderer_ndtext_new();
|
||||||
g_object_set(renderer, "editable", TRUE, NULL);
|
g_object_set(renderer, "editable", TRUE, NULL);
|
||||||
g_signal_connect(renderer, "edited",
|
g_signal_connect(renderer, "edited",
|
||||||
G_CALLBACK(cheat_list_modify_cheat), store);
|
G_CALLBACK(cheat_list_modify_cheat), model);
|
||||||
attrib = "text";
|
attrib = "text";
|
||||||
break;
|
break;
|
||||||
case TYPE_COMBO:
|
case TYPE_COMBO:
|
||||||
|
@ -257,20 +338,29 @@ static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store)
|
||||||
NULL);
|
NULL);
|
||||||
g_object_unref(size_model);
|
g_object_unref(size_model);
|
||||||
g_signal_connect(renderer, "edited",
|
g_signal_connect(renderer, "edited",
|
||||||
G_CALLBACK(cheat_list_modify_cheat), store);
|
G_CALLBACK(cheat_list_modify_cheat), model);
|
||||||
attrib = "text";
|
attrib = "text";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
gint c = columnTable[ii].column;
|
||||||
column =
|
column =
|
||||||
gtk_tree_view_column_new_with_attributes(columnTable[ii].
|
gtk_tree_view_column_new_with_attributes(columnTable[ii].
|
||||||
caption, renderer,
|
caption, renderer,
|
||||||
attrib, columnTable[ii].column,
|
attrib, c,
|
||||||
NULL);
|
NULL);
|
||||||
|
if (c == COLUMN_HI && cheat_type == CHEAT_TYPE_INTERNAL) {
|
||||||
|
gtk_tree_view_column_set_cell_data_func(
|
||||||
|
column, renderer, cheat_list_address_to_hex, NULL, NULL);
|
||||||
|
}
|
||||||
|
if (c == COLUMN_ENABLED || c == COLUMN_DESC ||
|
||||||
|
((c == COLUMN_SIZE || c == COLUMN_HI || c == COLUMN_LO) &&
|
||||||
|
cheat_type == CHEAT_TYPE_INTERNAL) ||
|
||||||
|
(c == COLUMN_AR && cheat_type == CHEAT_TYPE_AR)) {
|
||||||
g_object_set_data(G_OBJECT(renderer), "column",
|
g_object_set_data(G_OBJECT(renderer), "column",
|
||||||
GINT_TO_POINTER(columnTable[ii].column));
|
GINT_TO_POINTER(columnTable[ii].column));
|
||||||
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cheatListEnd()
|
static void cheatListEnd()
|
||||||
|
@ -282,8 +372,11 @@ static void cheatListEnd()
|
||||||
|
|
||||||
static GtkListStore *cheat_list_populate()
|
static GtkListStore *cheat_list_populate()
|
||||||
{
|
{
|
||||||
GtkListStore *store = gtk_list_store_new (5, G_TYPE_BOOLEAN,
|
// COLUMN_INDEX, COLUMN_TYPE, COLUMN_ENABLED, COLUMN_SIZE,
|
||||||
G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
|
// COLUMN_HI, COLUMN_LO, COLUMN_AR, COLUMN_DESC
|
||||||
|
GtkListStore *store = gtk_list_store_new (
|
||||||
|
8, G_TYPE_INT, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_INT,
|
||||||
|
G_TYPE_INT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
|
||||||
|
|
||||||
CHEATS_LIST cheat;
|
CHEATS_LIST cheat;
|
||||||
u32 chsize = cheats->getListSize();
|
u32 chsize = cheats->getListSize();
|
||||||
|
@ -291,45 +384,113 @@ static GtkListStore *cheat_list_populate()
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
cheats->copyItemFromIndex(ii, cheat);
|
cheats->copyItemFromIndex(ii, cheat);
|
||||||
gtk_list_store_append(store, &iter);
|
gtk_list_store_append(store, &iter);
|
||||||
gtk_list_store_set(store, &iter,
|
if (cheat.type == CHEAT_TYPE_INTERNAL) {
|
||||||
|
gtk_list_store_set(store, &iter, COLUMN_INDEX, ii,
|
||||||
|
COLUMN_TYPE, cheat.type,
|
||||||
COLUMN_ENABLED, cheat.enabled,
|
COLUMN_ENABLED, cheat.enabled,
|
||||||
COLUMN_SIZE, cheat.size+1,
|
COLUMN_SIZE, cheat.size + 1,
|
||||||
COLUMN_HI, cheat.code[0][0],
|
COLUMN_HI, cheat.code[0][0],
|
||||||
COLUMN_LO, cheat.code[0][1],
|
COLUMN_LO, cheat.code[0][1],
|
||||||
COLUMN_DESC, cheat.description,
|
COLUMN_DESC, cheat.description, -1);
|
||||||
-1);
|
} else if (cheat.type == CHEAT_TYPE_AR) {
|
||||||
|
u32 cheat_len = cheat.num;
|
||||||
|
char *cheat_str = (char *) malloc(18 * cheat_len);
|
||||||
|
cheat_str[0] = '\0';
|
||||||
|
|
||||||
|
// Safety: "%08X" is 8 bytes (x2), " " and "\n" are 1 each for 18
|
||||||
|
// bytes each strdup_printf called cheat_len times for the size of
|
||||||
|
// the malloc. g_strlcat emulates BSD's strlcat, so on the last
|
||||||
|
// iteration, a NUL-terminator is writted instead of the last
|
||||||
|
// trailing newline.
|
||||||
|
for (u32 jj = 0; jj < cheat_len; jj++) {
|
||||||
|
gchar *tmp = g_strdup_printf("%08X %08X\n", cheat.code[jj][0],
|
||||||
|
cheat.code[jj][1]);
|
||||||
|
g_strlcat(cheat_str, tmp, 18 * cheat_len);
|
||||||
|
g_free(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_list_store_set(store, &iter, COLUMN_INDEX, ii,
|
||||||
|
COLUMN_TYPE, cheat.type,
|
||||||
|
COLUMN_ENABLED, cheat.enabled,
|
||||||
|
COLUMN_AR, cheat_str,
|
||||||
|
COLUMN_DESC, cheat.description, -1);
|
||||||
|
free(cheat_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkWidget *cheat_list_create_ui()
|
static gboolean cheat_list_is_raw(GtkTreeModel *model, GtkTreeIter *iter,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
gint type = CHEAT_TYPE_EMPTY;
|
||||||
|
gtk_tree_model_get(model, iter, COLUMN_TYPE, &type, -1);
|
||||||
|
return type == CHEAT_TYPE_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean cheat_list_is_ar(GtkTreeModel *model, GtkTreeIter *iter,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
gint type = CHEAT_TYPE_EMPTY;
|
||||||
|
gtk_tree_model_get(model, iter, COLUMN_TYPE, &type, -1);
|
||||||
|
return type == CHEAT_TYPE_AR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cheat_list_create_ui()
|
||||||
{
|
{
|
||||||
GtkListStore *store = cheat_list_populate();
|
GtkListStore *store = cheat_list_populate();
|
||||||
GtkWidget *tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
|
GtkTreeModel *filter_raw =
|
||||||
|
gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL);
|
||||||
|
gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filter_raw),
|
||||||
|
cheat_list_is_raw, NULL, NULL);
|
||||||
|
GtkWidget *tree_raw = gtk_tree_view_new_with_model(filter_raw);
|
||||||
|
|
||||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1);
|
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1);
|
||||||
GtkWidget *hbbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
|
GtkWidget *hbbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
|
||||||
GtkWidget *button;
|
GtkWidget *button;
|
||||||
|
|
||||||
gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(tree));
|
gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(tree_raw));
|
||||||
gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(hbbox));
|
gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(hbbox));
|
||||||
gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(box));
|
gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(box));
|
||||||
|
|
||||||
button = gtk_button_new_with_label("add cheat");
|
button = gtk_button_new_with_label("Add internal cheat");
|
||||||
g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_add_cheat), store);
|
g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_add_cheat), store);
|
||||||
gtk_container_add(GTK_CONTAINER(hbbox),button);
|
gtk_container_add(GTK_CONTAINER(hbbox),button);
|
||||||
|
|
||||||
button = gtk_button_new_with_label("Remove cheat");
|
button = gtk_button_new_with_label("Remove internal cheat");
|
||||||
g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_remove_cheat), tree);
|
g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_remove_cheat),
|
||||||
|
GTK_TREE_VIEW(tree_raw));
|
||||||
gtk_container_add(GTK_CONTAINER(hbbox),button);
|
gtk_container_add(GTK_CONTAINER(hbbox),button);
|
||||||
|
|
||||||
cheat_list_add_columns(GTK_TREE_VIEW(tree), store);
|
GtkTreeModel *filter_ar =
|
||||||
|
gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL);
|
||||||
|
gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filter_ar),
|
||||||
|
cheat_list_is_ar, NULL, NULL);
|
||||||
|
GtkWidget *tree_ar = gtk_tree_view_new_with_model(filter_ar);
|
||||||
|
|
||||||
|
hbbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
|
||||||
|
gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(tree_ar));
|
||||||
|
gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(hbbox));
|
||||||
|
|
||||||
|
button = gtk_button_new_with_label("Add Action Replay cheat");
|
||||||
|
gtk_container_add(GTK_CONTAINER(hbbox), button);
|
||||||
|
g_signal_connect(button, "clicked", G_CALLBACK(cheat_list_add_cheat_AR),
|
||||||
|
store);
|
||||||
|
|
||||||
|
button = gtk_button_new_with_label("Remove Action Replay cheat");
|
||||||
|
g_signal_connect(button, "clicked", G_CALLBACK(cheat_list_remove_cheat),
|
||||||
|
GTK_TREE_VIEW(tree_ar));
|
||||||
|
gtk_container_add(GTK_CONTAINER(hbbox), button);
|
||||||
|
|
||||||
|
cheat_list_add_columns(GTK_TREE_VIEW(tree_raw), store, CHEAT_TYPE_INTERNAL);
|
||||||
|
cheat_list_add_columns(GTK_TREE_VIEW(tree_ar), store, CHEAT_TYPE_AR);
|
||||||
|
|
||||||
/* Setup the selection handler */
|
/* Setup the selection handler */
|
||||||
GtkTreeSelection *select;
|
GtkTreeSelection *select;
|
||||||
select = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
|
select = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_raw));
|
||||||
|
gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
|
||||||
|
select = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_ar));
|
||||||
gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
|
gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheatList(GSimpleAction *action, GVariant *parameter, gpointer user_data)
|
void CheatList(GSimpleAction *action, GVariant *parameter, gpointer user_data)
|
||||||
|
|
|
@ -19,6 +19,7 @@ desmume_src = [
|
||||||
'desmume.cpp',
|
'desmume.cpp',
|
||||||
'dToolsList.cpp',
|
'dToolsList.cpp',
|
||||||
'tools/ioregsView.cpp',
|
'tools/ioregsView.cpp',
|
||||||
|
'utilsGTK.cpp',
|
||||||
'cheatsGTK.cpp',
|
'cheatsGTK.cpp',
|
||||||
'main.cpp',
|
'main.cpp',
|
||||||
gresource,
|
gresource,
|
||||||
|
|
|
@ -0,0 +1,326 @@
|
||||||
|
/* utilsGTK.cpp - this file is part of DeSmuME
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 DeSmuME Team
|
||||||
|
*
|
||||||
|
* This file is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This file is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "utilsGTK.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
A C++ implementation of a GtkCellRendererText subclass which handles
|
||||||
|
newline-delimited text and allows for editing that text with the ability
|
||||||
|
for users to add newlines, based off of a GPLv2+ Python implementation here:
|
||||||
|
https://gitlab.gnome.org/GNOME/gtk/-/issues/175#note_487323
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
DESMUME_ENTRY_ND:
|
||||||
|
An object similar to an Entry, but which allows for newlines to be
|
||||||
|
inserted by holding Shift, Ctrl, or Alt along with pressing Enter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
GtkWidget *scroll;
|
||||||
|
GtkWidget *editor;
|
||||||
|
gboolean editing_canceled;
|
||||||
|
} DesmumeEntryNdPrivate;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
PROP_EDITING_CANCELED = 1,
|
||||||
|
ENTRY_ND_NUM_PROP,
|
||||||
|
} DesmumeEntryNdProperty;
|
||||||
|
|
||||||
|
static GParamSpec *entry_nd_properties[ENTRY_ND_NUM_PROP] = {
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Declared here to statisfy the type creation macro, but defined further down.
|
||||||
|
static void desmume_entry_nd_editable_init(GtkEditableInterface *iface);
|
||||||
|
static void desmume_entry_nd_cell_editable_init(GtkCellEditableIface *iface);
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_CODE(
|
||||||
|
DesmumeEntryNd, desmume_entry_nd, GTK_TYPE_EVENT_BOX,
|
||||||
|
G_ADD_PRIVATE(DesmumeEntryNd)
|
||||||
|
G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE, desmume_entry_nd_editable_init)
|
||||||
|
G_IMPLEMENT_INTERFACE(GTK_TYPE_CELL_EDITABLE,
|
||||||
|
desmume_entry_nd_cell_editable_init))
|
||||||
|
|
||||||
|
static void desmume_entry_nd_set_property(GObject *object, guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(object);
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
switch ((DesmumeEntryNdProperty) property_id) {
|
||||||
|
case PROP_EDITING_CANCELED:
|
||||||
|
if (priv->editing_canceled != g_value_get_boolean(value)) {
|
||||||
|
priv->editing_canceled = g_value_get_boolean(value);
|
||||||
|
g_object_notify(object, "editing-canceled");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_get_property(GObject *object, guint property_id,
|
||||||
|
GValue *value, GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(object);
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
switch ((DesmumeEntryNdProperty) property_id) {
|
||||||
|
case PROP_EDITING_CANCELED:
|
||||||
|
g_value_set_boolean(value, priv->editing_canceled);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean desmume_entry_nd_key_press(GtkWidget *widget,
|
||||||
|
GdkEventKey *event, gpointer data)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = (DesmumeEntryNd *) data;
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
// Allow the editor to decide how to handle the key event, except key events
|
||||||
|
// which its parent TextView needs to handle itself
|
||||||
|
gboolean doPropagate = GDK_EVENT_PROPAGATE;
|
||||||
|
guint kv = event->keyval;
|
||||||
|
guint mod = event->state;
|
||||||
|
|
||||||
|
if ((kv == GDK_KEY_Return || kv == GDK_KEY_KP_Enter ||
|
||||||
|
kv == GDK_KEY_ISO_Enter) &&
|
||||||
|
!(mod & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK))) {
|
||||||
|
// Enter + Ctrl, Shift, or Mod1 (commonly Alt), enter a newline in the
|
||||||
|
// editor, but otherwise act as confirm for the TextView
|
||||||
|
priv->editing_canceled = FALSE;
|
||||||
|
doPropagate = GDK_EVENT_STOP;
|
||||||
|
gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry_nd));
|
||||||
|
} else if (kv == GDK_KEY_Escape) {
|
||||||
|
priv->editing_canceled = TRUE;
|
||||||
|
doPropagate = GDK_EVENT_STOP;
|
||||||
|
gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry_nd));
|
||||||
|
}
|
||||||
|
return doPropagate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean desmume_entry_nd_button_press(GtkWidget *widget,
|
||||||
|
GdkEventButton *event,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
GtkTextView *editor = (GtkTextView *) widget;
|
||||||
|
GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS(editor);
|
||||||
|
klass->button_press_event(widget, event);
|
||||||
|
// We have explicitly described how to handle mouse button events, so do not
|
||||||
|
// propagate.
|
||||||
|
return GDK_EVENT_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_dispose(GObject *object)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(object);
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
// Recursively destroys contained objects, so destroys the editor as well
|
||||||
|
gtk_widget_destroy(priv->scroll);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS(desmume_entry_nd_parent_class)->dispose(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_finalize(GObject *object)
|
||||||
|
{
|
||||||
|
G_OBJECT_CLASS(desmume_entry_nd_parent_class)->finalize(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_class_init(DesmumeEntryNdClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
||||||
|
object_class->set_property = desmume_entry_nd_set_property;
|
||||||
|
object_class->get_property = desmume_entry_nd_get_property;
|
||||||
|
|
||||||
|
object_class->dispose = desmume_entry_nd_dispose;
|
||||||
|
object_class->finalize = desmume_entry_nd_finalize;
|
||||||
|
|
||||||
|
entry_nd_properties[PROP_EDITING_CANCELED] =
|
||||||
|
g_param_spec_boolean("editing-canceled", "Editing Canceled",
|
||||||
|
"The edit was canceled", FALSE, G_PARAM_READWRITE);
|
||||||
|
g_object_class_install_properties(object_class, ENTRY_ND_NUM_PROP,
|
||||||
|
entry_nd_properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_init(DesmumeEntryNd *entry_nd)
|
||||||
|
{
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
priv->scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(priv->scroll),
|
||||||
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||||
|
|
||||||
|
priv->editor = gtk_text_view_new();
|
||||||
|
gtk_text_view_set_editable(GTK_TEXT_VIEW(priv->editor), TRUE);
|
||||||
|
gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(priv->editor), FALSE);
|
||||||
|
|
||||||
|
g_signal_connect(priv->editor, "key-press-event",
|
||||||
|
G_CALLBACK(desmume_entry_nd_key_press), entry_nd);
|
||||||
|
g_signal_connect(priv->editor, "button-press-event",
|
||||||
|
G_CALLBACK(desmume_entry_nd_button_press), NULL);
|
||||||
|
|
||||||
|
gtk_container_add(GTK_CONTAINER(priv->scroll), priv->editor);
|
||||||
|
gtk_container_add(GTK_CONTAINER(entry_nd), priv->scroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_start_editing(GtkCellEditable *cell_editable,
|
||||||
|
GdkEvent *event)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(cell_editable);
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
gtk_widget_show_all(GTK_WIDGET(entry_nd));
|
||||||
|
gtk_widget_grab_focus(GTK_WIDGET(priv->editor));
|
||||||
|
|
||||||
|
// Highlight the entirety of the editor's text
|
||||||
|
GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->editor));
|
||||||
|
GtkTextIter start, end;
|
||||||
|
gtk_text_buffer_get_bounds(buf, &start, &end);
|
||||||
|
gtk_text_buffer_select_range(buf, &start, &end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gchar *desmume_entry_nd_get_text(DesmumeEntryNd *entry_nd)
|
||||||
|
{
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->editor));
|
||||||
|
GtkTextIter start, end;
|
||||||
|
gtk_text_buffer_get_bounds(buf, &start, &end);
|
||||||
|
|
||||||
|
return gtk_text_buffer_get_text(buf, &start, &end, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_set_text(DesmumeEntryNd *entry_nd,
|
||||||
|
const gchar *text)
|
||||||
|
{
|
||||||
|
DesmumeEntryNdPrivate *priv;
|
||||||
|
priv = (DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->editor));
|
||||||
|
gtk_text_buffer_set_text(buf, text, strlen(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_editable_init(GtkEditableInterface *iface) { }
|
||||||
|
|
||||||
|
static void desmume_entry_nd_cell_editable_init(GtkCellEditableIface *iface)
|
||||||
|
{
|
||||||
|
iface->start_editing = desmume_entry_nd_start_editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *desmume_entry_nd_new()
|
||||||
|
{
|
||||||
|
return (GtkWidget *) g_object_new(DESMUME_TYPE_ENTRY_ND, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
DESMUME_CELL_RENDERER_ND_TEXT:
|
||||||
|
A subclass of GtkCellRendererText which creates our DesmumeEntryNd instead
|
||||||
|
of a GtkEntry, which allows a cell in a TreeView to accepts newlines.
|
||||||
|
*/
|
||||||
|
|
||||||
|
G_DEFINE_TYPE(DesmumeCellRendererNdtext, desmume_cell_renderer_ndtext,
|
||||||
|
GTK_TYPE_CELL_RENDERER_TEXT)
|
||||||
|
|
||||||
|
static void desmume_cell_renderer_ndtext_editing_done(GtkCellEditable *entry_nd,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
gboolean canceled;
|
||||||
|
g_object_get(entry_nd, "editing-canceled", &canceled, NULL);
|
||||||
|
if (!canceled) {
|
||||||
|
const gchar *path =
|
||||||
|
(gchar *) g_object_get_data(G_OBJECT(entry_nd), "full-text");
|
||||||
|
gchar *new_text = desmume_entry_nd_get_text(DESMUME_ENTRY_ND(entry_nd));
|
||||||
|
|
||||||
|
guint signal_id =
|
||||||
|
g_signal_lookup("edited", DESMUME_TYPE_CELL_RENDERER_NDTEXT);
|
||||||
|
g_signal_emit(data, signal_id, 0, path, new_text);
|
||||||
|
g_free(new_text);
|
||||||
|
}
|
||||||
|
gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(entry_nd));
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkCellEditable *desmume_cell_renderer_ndtext_start_editing(
|
||||||
|
GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget,
|
||||||
|
const gchar *path, const GdkRectangle *background_area,
|
||||||
|
const GdkRectangle *cell_area, GtkCellRendererState flags)
|
||||||
|
{
|
||||||
|
DesmumeCellRendererNdtext *ndtext = DESMUME_CELL_RENDERER_NDTEXT(cell);
|
||||||
|
gboolean editable;
|
||||||
|
g_object_get(G_OBJECT(ndtext), "editable", &editable, NULL);
|
||||||
|
if (!editable)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
gchar *text;
|
||||||
|
g_object_get(G_OBJECT(ndtext), "text", &text, NULL);
|
||||||
|
|
||||||
|
GtkWidget *entry_nd = desmume_entry_nd_new();
|
||||||
|
if (text != NULL) {
|
||||||
|
desmume_entry_nd_set_text(DESMUME_ENTRY_ND(entry_nd), text);
|
||||||
|
g_free(text);
|
||||||
|
}
|
||||||
|
g_object_set_data_full(G_OBJECT(entry_nd), "full-text", g_strdup(path),
|
||||||
|
g_free);
|
||||||
|
|
||||||
|
g_signal_connect(entry_nd, "editing-done",
|
||||||
|
G_CALLBACK(desmume_cell_renderer_ndtext_editing_done),
|
||||||
|
ndtext);
|
||||||
|
return GTK_CELL_EDITABLE(entry_nd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
desmume_cell_renderer_ndtext_class_init(DesmumeCellRendererNdtextClass *klass)
|
||||||
|
{
|
||||||
|
GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass);
|
||||||
|
cell_class->start_editing = desmume_cell_renderer_ndtext_start_editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_cell_renderer_ndtext_init(DesmumeCellRendererNdtext *ndtext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkCellRenderer *desmume_cell_renderer_ndtext_new()
|
||||||
|
{
|
||||||
|
return GTK_CELL_RENDERER(
|
||||||
|
g_object_new(DESMUME_TYPE_CELL_RENDERER_NDTEXT, NULL));
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/* utilsGTK.h - this file is part of DeSmuME
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 DeSmuME Team
|
||||||
|
*
|
||||||
|
* This file is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This file is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __UTILS_GTK_H__
|
||||||
|
#define __UTILS_GTK_H__
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define DESMUME_TYPE_ENTRY_ND (desmume_entry_nd_get_type())
|
||||||
|
G_DECLARE_FINAL_TYPE(DesmumeEntryNd, desmume_entry_nd, DESMUME, ENTRY_ND,
|
||||||
|
GtkEventBox)
|
||||||
|
|
||||||
|
struct _DesmumeEntryNd
|
||||||
|
{
|
||||||
|
GtkEventBox parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _DesmumeEntryNdClass
|
||||||
|
{
|
||||||
|
GtkEventBoxClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
GType desmume_entry_nd_get_type(void) G_GNUC_CONST;
|
||||||
|
GtkEventBox *entry_nd_new(void);
|
||||||
|
|
||||||
|
#define DESMUME_TYPE_CELL_RENDERER_NDTEXT \
|
||||||
|
(desmume_cell_renderer_ndtext_get_type())
|
||||||
|
G_DECLARE_FINAL_TYPE(DesmumeCellRendererNdtext, desmume_cell_renderer_ndtext,
|
||||||
|
DESMUME, CELL_RENDERER_NDTEXT, GtkCellRendererText)
|
||||||
|
|
||||||
|
struct _DesmumeCellRendererNdtext
|
||||||
|
{
|
||||||
|
GtkCellRendererText parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _DesmumeCellRendererNdtextClass
|
||||||
|
{
|
||||||
|
GtkCellRendererTextClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
GType desmume_cell_renderer_ndtext_get_type(void) G_GNUC_CONST;
|
||||||
|
GtkCellRenderer *desmume_cell_renderer_ndtext_new(void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif /*__UTILS_GTK_H__*/
|
|
@ -21,6 +21,7 @@ desmume_SOURCES = \
|
||||||
desmume.h desmume.cpp \
|
desmume.h desmume.cpp \
|
||||||
dTool.h dToolsList.cpp \
|
dTool.h dToolsList.cpp \
|
||||||
tools/ioregsView.cpp tools/ioregsView.h \
|
tools/ioregsView.cpp tools/ioregsView.h \
|
||||||
|
utilsGTK.h utilsGTK.cpp \
|
||||||
cheatsGTK.h cheatsGTK.cpp \
|
cheatsGTK.h cheatsGTK.cpp \
|
||||||
main.cpp main.h
|
main.cpp main.h
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* cheats.cpp - this file is part of DeSmuME
|
/* cheatsGTK.cpp - this file is part of DeSmuME
|
||||||
*
|
*
|
||||||
* Copyright (C) 2006-2023 DeSmuME Team
|
* Copyright (C) 2006-2024 DeSmuME Team
|
||||||
*
|
*
|
||||||
* This file is free software; you can redistribute it and/or modify
|
* This file is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,12 +18,12 @@
|
||||||
* Boston, MA 02111-1307, USA.
|
* Boston, MA 02111-1307, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include "cheatsGTK.h"
|
#include "cheatsGTK.h"
|
||||||
#include "cheatSystem.h"
|
#include "cheatSystem.h"
|
||||||
|
#include "utilsGTK.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "desmume.h"
|
#include "desmume.h"
|
||||||
|
|
||||||
|
@ -31,10 +31,13 @@
|
||||||
#define GPOINTER_TO_INT(p) ((gint) (glong) (p))
|
#define GPOINTER_TO_INT(p) ((gint) (glong) (p))
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
COLUMN_INDEX,
|
||||||
|
COLUMN_TYPE,
|
||||||
COLUMN_ENABLED,
|
COLUMN_ENABLED,
|
||||||
COLUMN_SIZE,
|
COLUMN_SIZE,
|
||||||
COLUMN_HI,
|
COLUMN_HI,
|
||||||
COLUMN_LO,
|
COLUMN_LO,
|
||||||
|
COLUMN_AR,
|
||||||
COLUMN_DESC,
|
COLUMN_DESC,
|
||||||
NUM_COL
|
NUM_COL
|
||||||
};
|
};
|
||||||
|
@ -56,10 +59,13 @@ static struct {
|
||||||
gint type;
|
gint type;
|
||||||
gint column;
|
gint column;
|
||||||
} columnTable[]={
|
} columnTable[]={
|
||||||
|
{ "Index", TYPE_STRING, COLUMN_INDEX},
|
||||||
|
{ "Type", TYPE_STRING, COLUMN_TYPE},
|
||||||
{ "Enabled", TYPE_TOGGLE, COLUMN_ENABLED},
|
{ "Enabled", TYPE_TOGGLE, COLUMN_ENABLED},
|
||||||
{ "Size", TYPE_COMBO, COLUMN_SIZE},
|
{ "Size", TYPE_COMBO, COLUMN_SIZE},
|
||||||
{ "Offset", TYPE_STRING, COLUMN_HI},
|
{ "Address", TYPE_STRING, COLUMN_HI},
|
||||||
{ "Value", TYPE_STRING, COLUMN_LO},
|
{ "Value", TYPE_STRING, COLUMN_LO},
|
||||||
|
{ "AR Code", TYPE_STRING, COLUMN_AR},
|
||||||
{ "Description", TYPE_STRING, COLUMN_DESC}
|
{ "Description", TYPE_STRING, COLUMN_DESC}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,7 +73,7 @@ static GtkWidget *win = NULL;
|
||||||
static BOOL shouldBeRunning = FALSE;
|
static BOOL shouldBeRunning = FALSE;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------
|
||||||
// SEARCH
|
// CHEATS MENU
|
||||||
// ---------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -75,28 +81,28 @@ enabled_toggled(GtkCellRendererToggle * cell,
|
||||||
gchar * path_str, gpointer data)
|
gchar * path_str, gpointer data)
|
||||||
{
|
{
|
||||||
GtkTreeModel *model = (GtkTreeModel *) data;
|
GtkTreeModel *model = (GtkTreeModel *) data;
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter, f_iter;
|
||||||
GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
|
GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
|
||||||
gboolean guiEnabled;
|
gboolean guiEnabled;
|
||||||
|
|
||||||
gtk_tree_model_get_iter(model, &iter, path);
|
gtk_tree_model_get_iter(model, &f_iter, path);
|
||||||
gtk_tree_model_get(model, &iter, COLUMN_ENABLED, &guiEnabled, -1);
|
gtk_tree_model_filter_convert_iter_to_child_iter(
|
||||||
|
GTK_TREE_MODEL_FILTER(model), &iter, &f_iter);
|
||||||
|
GtkTreeModel *store =
|
||||||
|
gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
|
||||||
|
|
||||||
|
gtk_tree_model_get(store, &iter, COLUMN_ENABLED, &guiEnabled, -1);
|
||||||
|
|
||||||
guiEnabled ^= 1;
|
guiEnabled ^= 1;
|
||||||
const bool cheatEnabled = (guiEnabled) ? true : false;
|
const bool cheatEnabled = (guiEnabled) ? true : false;
|
||||||
CHEATS_LIST tempCheatItem;
|
CHEATS_LIST tempCheatItem;
|
||||||
u32 ii;
|
u32 ii;
|
||||||
GtkTreePath *path1;
|
|
||||||
|
|
||||||
path1 = gtk_tree_model_get_path (model, &iter);
|
gtk_tree_model_get(store, &iter, COLUMN_INDEX, &ii, -1);
|
||||||
ii = gtk_tree_path_get_indices (path)[0];
|
|
||||||
|
|
||||||
cheats->copyItemFromIndex(ii, tempCheatItem);
|
cheats->toggle(cheatEnabled, ii);
|
||||||
|
|
||||||
cheats->update(tempCheatItem.size, tempCheatItem.code[0][0], tempCheatItem.code[0][1], tempCheatItem.description,
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, COLUMN_ENABLED, guiEnabled, -1);
|
||||||
cheatEnabled, ii);
|
|
||||||
|
|
||||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLUMN_ENABLED, guiEnabled, -1);
|
|
||||||
|
|
||||||
gtk_tree_path_free(path);
|
gtk_tree_path_free(path);
|
||||||
}
|
}
|
||||||
|
@ -107,51 +113,74 @@ static void cheat_list_modify_cheat(GtkCellRendererText * cell,
|
||||||
{
|
{
|
||||||
GtkTreeModel *model = (GtkTreeModel *) data;
|
GtkTreeModel *model = (GtkTreeModel *) data;
|
||||||
GtkTreePath *path = gtk_tree_path_new_from_string(path_string);
|
GtkTreePath *path = gtk_tree_path_new_from_string(path_string);
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter, f_iter;
|
||||||
|
|
||||||
gint column =
|
gint column =
|
||||||
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column"));
|
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column"));
|
||||||
|
|
||||||
gtk_tree_model_get_iter(model, &iter, path);
|
gtk_tree_model_get_iter(model, &f_iter, path);
|
||||||
|
gtk_tree_path_free(path);
|
||||||
|
|
||||||
|
gtk_tree_model_filter_convert_iter_to_child_iter(
|
||||||
|
GTK_TREE_MODEL_FILTER(model), &iter, &f_iter);
|
||||||
|
GtkTreeModel *store =
|
||||||
|
gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
|
||||||
|
|
||||||
{
|
{
|
||||||
u32 ii;
|
u32 ii;
|
||||||
GtkTreePath *path1;
|
|
||||||
CHEATS_LIST cheat;
|
CHEATS_LIST cheat;
|
||||||
|
|
||||||
path1 = gtk_tree_model_get_path (model, &iter);
|
gtk_tree_model_get(store, &iter, COLUMN_INDEX, &ii, -1);
|
||||||
ii = gtk_tree_path_get_indices (path)[0];
|
|
||||||
|
|
||||||
cheats->copyItemFromIndex(ii, cheat);
|
cheats->copyItemFromIndex(ii, cheat);
|
||||||
|
|
||||||
gtk_tree_path_free (path1);
|
|
||||||
|
|
||||||
if (column == COLUMN_LO || column == COLUMN_HI
|
if (column == COLUMN_LO || column == COLUMN_HI
|
||||||
|| column == COLUMN_SIZE) {
|
|| column == COLUMN_SIZE) {
|
||||||
u32 v = atoi(new_text);
|
u32 v = 0;
|
||||||
|
u32 data;
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case COLUMN_SIZE:
|
case COLUMN_SIZE:
|
||||||
cheats->update(v-1, cheat.code[0][0], cheat.code[0][1],
|
v = atoi(new_text);
|
||||||
|
// If the size is reduced, the data is currently contains may be
|
||||||
|
// out of range, so cap it at its maximum value.
|
||||||
|
data = std::min(0xFFFFFFFF >> (24 - ((v - 1) << 3)),
|
||||||
|
cheat.code[0][1]);
|
||||||
|
cheats->update(v-1, cheat.code[0][0], data,
|
||||||
cheat.description, cheat.enabled, ii);
|
cheat.description, cheat.enabled, ii);
|
||||||
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, COLUMN_LO,
|
||||||
|
data, -1);
|
||||||
break;
|
break;
|
||||||
case COLUMN_HI:
|
case COLUMN_HI:
|
||||||
|
sscanf(new_text, "%x", &v);
|
||||||
|
v &= 0x0FFFFFFF;
|
||||||
cheats->update(cheat.size, v, cheat.code[0][1], cheat.description,
|
cheats->update(cheat.size, v, cheat.code[0][1], cheat.description,
|
||||||
cheat.enabled, ii);
|
cheat.enabled, ii);
|
||||||
break;
|
break;
|
||||||
case COLUMN_LO:
|
case COLUMN_LO:
|
||||||
|
v = atoi(new_text);
|
||||||
|
v = std::min(0xFFFFFFFF >> (24 - (cheat.size << 3)), v);
|
||||||
cheats->update(cheat.size, cheat.code[0][0], v, cheat.description,
|
cheats->update(cheat.size, cheat.code[0][0], v, cheat.description,
|
||||||
cheat.enabled, ii);
|
cheat.enabled, ii);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, column,
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, column, v, -1);
|
||||||
atoi(new_text), -1);
|
|
||||||
} else if (column == COLUMN_DESC){
|
} else if (column == COLUMN_DESC){
|
||||||
cheats->update(cheat.size, cheat.code[0][0], cheat.code[0][1],
|
cheats->setDescription(new_text, ii);
|
||||||
g_strdup(new_text), cheat.enabled, ii);
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, column,
|
||||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, column,
|
new_text, -1);
|
||||||
g_strdup(new_text), -1);
|
} else if (column == COLUMN_AR) {
|
||||||
|
// Safety: CHEATS::update_AR, though it takes `code` as not const,
|
||||||
|
// only performs a non-null check and passes `code` to
|
||||||
|
// CHEATS::XXCodeFromString as const, therefore new_text (should)
|
||||||
|
// never be modified
|
||||||
|
bool isValid =
|
||||||
|
cheats->update_AR(const_cast<char *>(new_text),
|
||||||
|
cheat.description, cheat.enabled, ii);
|
||||||
|
if (isValid) {
|
||||||
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, column,
|
||||||
|
new_text, -1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,17 +189,29 @@ static void cheat_list_remove_cheat(GtkWidget * widget, gpointer data)
|
||||||
GtkTreeView *tree = (GtkTreeView *) data;
|
GtkTreeView *tree = (GtkTreeView *) data;
|
||||||
GtkTreeSelection *selection = gtk_tree_view_get_selection (tree);
|
GtkTreeSelection *selection = gtk_tree_view_get_selection (tree);
|
||||||
GtkTreeModel *model = gtk_tree_view_get_model (tree);
|
GtkTreeModel *model = gtk_tree_view_get_model (tree);
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter, f_iter;
|
||||||
|
|
||||||
if (gtk_tree_selection_get_selected (selection, NULL, &iter)){
|
if (gtk_tree_selection_get_selected (selection, NULL, &f_iter)){
|
||||||
u32 ii;
|
u32 ii;
|
||||||
|
gboolean valid;
|
||||||
GtkTreePath *path;
|
GtkTreePath *path;
|
||||||
|
|
||||||
path = gtk_tree_model_get_path (model, &iter);
|
path = gtk_tree_model_get_path (model, &f_iter);
|
||||||
ii = gtk_tree_path_get_indices (path)[0];
|
gtk_tree_model_filter_convert_iter_to_child_iter(
|
||||||
|
GTK_TREE_MODEL_FILTER(model), &iter, &f_iter);
|
||||||
|
GtkTreeModel *store =
|
||||||
|
gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
|
||||||
|
|
||||||
gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
|
gtk_tree_model_get(store, &iter, COLUMN_INDEX, &ii, -1);
|
||||||
|
|
||||||
|
valid = gtk_list_store_remove(GTK_LIST_STORE(store), &iter);
|
||||||
cheats->remove(ii);
|
cheats->remove(ii);
|
||||||
|
while (valid) {
|
||||||
|
gtk_list_store_set(GTK_LIST_STORE(store), &iter, COLUMN_INDEX, ii,
|
||||||
|
-1);
|
||||||
|
ii++;
|
||||||
|
valid = gtk_tree_model_iter_next(store, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
gtk_tree_path_free (path);
|
gtk_tree_path_free (path);
|
||||||
}
|
}
|
||||||
|
@ -181,17 +222,42 @@ static void cheat_list_add_cheat(GtkWidget * widget, gpointer data)
|
||||||
#define NEW_DESC "New cheat"
|
#define NEW_DESC "New cheat"
|
||||||
GtkListStore *store = (GtkListStore *) data;
|
GtkListStore *store = (GtkListStore *) data;
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
cheats->add(1, 0, 0, g_strdup(NEW_DESC), false);
|
// Safety: CHEATS::add only uses `description` to call CHEATS::update, which
|
||||||
|
// only uses it to call CHEATS::setDescription, which takes `description` as
|
||||||
|
// const.
|
||||||
|
cheats->add(0, 0, 0, const_cast<char *>(NEW_DESC), false);
|
||||||
gtk_list_store_append(store, &iter);
|
gtk_list_store_append(store, &iter);
|
||||||
gtk_list_store_set(store, &iter,
|
gtk_list_store_set(store, &iter,
|
||||||
|
COLUMN_INDEX, cheats->getListSize() - 1,
|
||||||
|
COLUMN_TYPE, 0,
|
||||||
COLUMN_ENABLED, FALSE,
|
COLUMN_ENABLED, FALSE,
|
||||||
COLUMN_SIZE, 1,
|
COLUMN_SIZE, 1,
|
||||||
COLUMN_HI, 0,
|
COLUMN_HI, 0,
|
||||||
COLUMN_LO, 0, COLUMN_DESC, NEW_DESC, -1);
|
COLUMN_LO, 0, COLUMN_DESC, NEW_DESC, -1);
|
||||||
|
|
||||||
#undef NEW_DESC
|
#undef NEW_DESC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void cheat_list_add_cheat_AR(GtkWidget *widget, gpointer data)
|
||||||
|
{
|
||||||
|
#define NEW_DESC "New cheat"
|
||||||
|
#define NEW_AR "00000000 00000000"
|
||||||
|
GtkListStore *store = (GtkListStore *) data;
|
||||||
|
GtkTreeIter iter;
|
||||||
|
// Safety: CHEATS::add_AR only uses `code` to call , `description` to call
|
||||||
|
// CHEATS::setDescription, which takes the variable as const.
|
||||||
|
cheats->add_AR(const_cast<char *>(NEW_AR), const_cast<char *>(NEW_DESC),
|
||||||
|
false);
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter,
|
||||||
|
COLUMN_INDEX, cheats->getListSize() - 1,
|
||||||
|
COLUMN_TYPE, 1,
|
||||||
|
COLUMN_ENABLED, FALSE,
|
||||||
|
COLUMN_AR, NEW_AR,
|
||||||
|
COLUMN_DESC, NEW_DESC, -1);
|
||||||
|
#undef NEW_DESC
|
||||||
|
#undef NEW_AR
|
||||||
|
}
|
||||||
|
|
||||||
static GtkTreeModel * create_numbers_model (void)
|
static GtkTreeModel * create_numbers_model (void)
|
||||||
{
|
{
|
||||||
#define N_NUMBERS 4
|
#define N_NUMBERS 4
|
||||||
|
@ -222,9 +288,21 @@ static GtkTreeModel * create_numbers_model (void)
|
||||||
#undef N_NUMBERS
|
#undef N_NUMBERS
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store)
|
static void cheat_list_address_to_hex(GtkTreeViewColumn *column,
|
||||||
|
GtkCellRenderer *renderer,
|
||||||
|
GtkTreeModel *model, GtkTreeIter *iter,
|
||||||
|
gpointer data)
|
||||||
{
|
{
|
||||||
|
gint addr;
|
||||||
|
gtk_tree_model_get(model, iter, COLUMN_HI, &addr, -1);
|
||||||
|
gchar *hex_addr = g_strdup_printf("0x0%07X", addr);
|
||||||
|
g_object_set(renderer, "text", hex_addr, NULL);
|
||||||
|
g_free(hex_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store,
|
||||||
|
u8 cheat_type)
|
||||||
|
{
|
||||||
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
|
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
|
||||||
static GtkTreeModel * size_model;
|
static GtkTreeModel * size_model;
|
||||||
|
|
||||||
|
@ -240,10 +318,13 @@ static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store)
|
||||||
attrib = "active";
|
attrib = "active";
|
||||||
break;
|
break;
|
||||||
case TYPE_STRING:
|
case TYPE_STRING:
|
||||||
|
if (cheat_type == CHEAT_TYPE_INTERNAL)
|
||||||
renderer = gtk_cell_renderer_text_new();
|
renderer = gtk_cell_renderer_text_new();
|
||||||
|
else if (cheat_type == CHEAT_TYPE_AR)
|
||||||
|
renderer = desmume_cell_renderer_ndtext_new();
|
||||||
g_object_set(renderer, "editable", TRUE, NULL);
|
g_object_set(renderer, "editable", TRUE, NULL);
|
||||||
g_signal_connect(renderer, "edited",
|
g_signal_connect(renderer, "edited",
|
||||||
G_CALLBACK(cheat_list_modify_cheat), store);
|
G_CALLBACK(cheat_list_modify_cheat), model);
|
||||||
attrib = "text";
|
attrib = "text";
|
||||||
break;
|
break;
|
||||||
case TYPE_COMBO:
|
case TYPE_COMBO:
|
||||||
|
@ -257,20 +338,29 @@ static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store)
|
||||||
NULL);
|
NULL);
|
||||||
g_object_unref(size_model);
|
g_object_unref(size_model);
|
||||||
g_signal_connect(renderer, "edited",
|
g_signal_connect(renderer, "edited",
|
||||||
G_CALLBACK(cheat_list_modify_cheat), store);
|
G_CALLBACK(cheat_list_modify_cheat), model);
|
||||||
attrib = "text";
|
attrib = "text";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
gint c = columnTable[ii].column;
|
||||||
column =
|
column =
|
||||||
gtk_tree_view_column_new_with_attributes(columnTable[ii].
|
gtk_tree_view_column_new_with_attributes(columnTable[ii].
|
||||||
caption, renderer,
|
caption, renderer,
|
||||||
attrib, columnTable[ii].column,
|
attrib, c,
|
||||||
NULL);
|
NULL);
|
||||||
|
if (c == COLUMN_HI && cheat_type == CHEAT_TYPE_INTERNAL) {
|
||||||
|
gtk_tree_view_column_set_cell_data_func(
|
||||||
|
column, renderer, cheat_list_address_to_hex, NULL, NULL);
|
||||||
|
}
|
||||||
|
if (c == COLUMN_ENABLED || c == COLUMN_DESC ||
|
||||||
|
((c == COLUMN_SIZE || c == COLUMN_HI || c == COLUMN_LO) &&
|
||||||
|
cheat_type == CHEAT_TYPE_INTERNAL) ||
|
||||||
|
(c == COLUMN_AR && cheat_type == CHEAT_TYPE_AR)) {
|
||||||
g_object_set_data(G_OBJECT(renderer), "column",
|
g_object_set_data(G_OBJECT(renderer), "column",
|
||||||
GINT_TO_POINTER(columnTable[ii].column));
|
GINT_TO_POINTER(columnTable[ii].column));
|
||||||
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cheatListEnd()
|
static void cheatListEnd()
|
||||||
|
@ -282,8 +372,11 @@ static void cheatListEnd()
|
||||||
|
|
||||||
static GtkListStore *cheat_list_populate()
|
static GtkListStore *cheat_list_populate()
|
||||||
{
|
{
|
||||||
GtkListStore *store = gtk_list_store_new (5, G_TYPE_BOOLEAN,
|
// COLUMN_INDEX, COLUMN_TYPE, COLUMN_ENABLED, COLUMN_SIZE,
|
||||||
G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
|
// COLUMN_HI, COLUMN_LO, COLUMN_AR, COLUMN_DESC
|
||||||
|
GtkListStore *store = gtk_list_store_new (
|
||||||
|
8, G_TYPE_INT, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_INT,
|
||||||
|
G_TYPE_INT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
|
||||||
|
|
||||||
CHEATS_LIST cheat;
|
CHEATS_LIST cheat;
|
||||||
u32 chsize = cheats->getListSize();
|
u32 chsize = cheats->getListSize();
|
||||||
|
@ -291,45 +384,113 @@ static GtkListStore *cheat_list_populate()
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
cheats->copyItemFromIndex(ii, cheat);
|
cheats->copyItemFromIndex(ii, cheat);
|
||||||
gtk_list_store_append(store, &iter);
|
gtk_list_store_append(store, &iter);
|
||||||
gtk_list_store_set(store, &iter,
|
if (cheat.type == CHEAT_TYPE_INTERNAL) {
|
||||||
|
gtk_list_store_set(store, &iter, COLUMN_INDEX, ii,
|
||||||
|
COLUMN_TYPE, cheat.type,
|
||||||
COLUMN_ENABLED, cheat.enabled,
|
COLUMN_ENABLED, cheat.enabled,
|
||||||
COLUMN_SIZE, cheat.size+1,
|
COLUMN_SIZE, cheat.size + 1,
|
||||||
COLUMN_HI, cheat.code[0][0],
|
COLUMN_HI, cheat.code[0][0],
|
||||||
COLUMN_LO, cheat.code[0][1],
|
COLUMN_LO, cheat.code[0][1],
|
||||||
COLUMN_DESC, cheat.description,
|
COLUMN_DESC, cheat.description, -1);
|
||||||
-1);
|
} else if (cheat.type == CHEAT_TYPE_AR) {
|
||||||
|
u32 cheat_len = cheat.num;
|
||||||
|
char *cheat_str = (char *) malloc(18 * cheat_len);
|
||||||
|
cheat_str[0] = '\0';
|
||||||
|
|
||||||
|
// Safety: "%08X" is 8 bytes (x2), " " and "\n" are 1 each for 18
|
||||||
|
// bytes each strdup_printf called cheat_len times for the size of
|
||||||
|
// the malloc. g_strlcat emulates BSD's strlcat, so on the last
|
||||||
|
// iteration, a NUL-terminator is writted instead of the last
|
||||||
|
// trailing newline.
|
||||||
|
for (u32 jj = 0; jj < cheat_len; jj++) {
|
||||||
|
gchar *tmp = g_strdup_printf("%08X %08X\n", cheat.code[jj][0],
|
||||||
|
cheat.code[jj][1]);
|
||||||
|
g_strlcat(cheat_str, tmp, 18 * cheat_len);
|
||||||
|
g_free(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_list_store_set(store, &iter, COLUMN_INDEX, ii,
|
||||||
|
COLUMN_TYPE, cheat.type,
|
||||||
|
COLUMN_ENABLED, cheat.enabled,
|
||||||
|
COLUMN_AR, cheat_str,
|
||||||
|
COLUMN_DESC, cheat.description, -1);
|
||||||
|
free(cheat_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkWidget *cheat_list_create_ui()
|
static gboolean cheat_list_is_raw(GtkTreeModel *model, GtkTreeIter *iter,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
gint type = CHEAT_TYPE_EMPTY;
|
||||||
|
gtk_tree_model_get(model, iter, COLUMN_TYPE, &type, -1);
|
||||||
|
return type == CHEAT_TYPE_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean cheat_list_is_ar(GtkTreeModel *model, GtkTreeIter *iter,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
gint type = CHEAT_TYPE_EMPTY;
|
||||||
|
gtk_tree_model_get(model, iter, COLUMN_TYPE, &type, -1);
|
||||||
|
return type == CHEAT_TYPE_AR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cheat_list_create_ui()
|
||||||
{
|
{
|
||||||
GtkListStore *store = cheat_list_populate();
|
GtkListStore *store = cheat_list_populate();
|
||||||
GtkWidget *tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
|
GtkTreeModel *filter_raw =
|
||||||
|
gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL);
|
||||||
|
gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filter_raw),
|
||||||
|
cheat_list_is_raw, NULL, NULL);
|
||||||
|
GtkWidget *tree_raw = gtk_tree_view_new_with_model(filter_raw);
|
||||||
|
|
||||||
GtkWidget *vbox = gtk_vbox_new(FALSE, 1);
|
GtkWidget *vbox = gtk_vbox_new(FALSE, 1);
|
||||||
GtkWidget *hbbox = gtk_hbutton_box_new();
|
GtkWidget *hbbox = gtk_hbutton_box_new();
|
||||||
GtkWidget *button;
|
GtkWidget *button;
|
||||||
|
|
||||||
gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(tree));
|
gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(tree_raw));
|
||||||
gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(hbbox));
|
gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(hbbox));
|
||||||
gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(vbox));
|
gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(vbox));
|
||||||
|
|
||||||
button = gtk_button_new_with_label("add cheat");
|
button = gtk_button_new_with_label("Add internal cheat");
|
||||||
g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_add_cheat), store);
|
g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_add_cheat), store);
|
||||||
gtk_container_add(GTK_CONTAINER(hbbox),button);
|
gtk_container_add(GTK_CONTAINER(hbbox),button);
|
||||||
|
|
||||||
button = gtk_button_new_with_label("Remove cheat");
|
button = gtk_button_new_with_label("Remove internal cheat");
|
||||||
g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_remove_cheat), tree);
|
g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_remove_cheat),
|
||||||
|
GTK_TREE_VIEW(tree_raw));
|
||||||
gtk_container_add(GTK_CONTAINER(hbbox),button);
|
gtk_container_add(GTK_CONTAINER(hbbox),button);
|
||||||
|
|
||||||
cheat_list_add_columns(GTK_TREE_VIEW(tree), store);
|
GtkTreeModel *filter_ar =
|
||||||
|
gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL);
|
||||||
|
gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filter_ar),
|
||||||
|
cheat_list_is_ar, NULL, NULL);
|
||||||
|
GtkWidget *tree_ar = gtk_tree_view_new_with_model(filter_ar);
|
||||||
|
|
||||||
|
hbbox = gtk_hbutton_box_new();
|
||||||
|
gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(tree_ar));
|
||||||
|
gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(hbbox));
|
||||||
|
|
||||||
|
button = gtk_button_new_with_label("Add Action Replay cheat");
|
||||||
|
gtk_container_add(GTK_CONTAINER(hbbox), button);
|
||||||
|
g_signal_connect(button, "clicked", G_CALLBACK(cheat_list_add_cheat_AR),
|
||||||
|
store);
|
||||||
|
|
||||||
|
button = gtk_button_new_with_label("Remove Action Replay cheat");
|
||||||
|
g_signal_connect(button, "clicked", G_CALLBACK(cheat_list_remove_cheat),
|
||||||
|
GTK_TREE_VIEW(tree_ar));
|
||||||
|
gtk_container_add(GTK_CONTAINER(hbbox), button);
|
||||||
|
|
||||||
|
cheat_list_add_columns(GTK_TREE_VIEW(tree_raw), store, CHEAT_TYPE_INTERNAL);
|
||||||
|
cheat_list_add_columns(GTK_TREE_VIEW(tree_ar), store, CHEAT_TYPE_AR);
|
||||||
|
|
||||||
/* Setup the selection handler */
|
/* Setup the selection handler */
|
||||||
GtkTreeSelection *select;
|
GtkTreeSelection *select;
|
||||||
select = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
|
select = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_raw));
|
||||||
|
gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
|
||||||
|
select = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_ar));
|
||||||
gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
|
gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheatList ()
|
void CheatList ()
|
||||||
|
|
|
@ -13,6 +13,7 @@ desmume_src = [
|
||||||
'desmume.cpp',
|
'desmume.cpp',
|
||||||
'dToolsList.cpp',
|
'dToolsList.cpp',
|
||||||
'tools/ioregsView.cpp',
|
'tools/ioregsView.cpp',
|
||||||
|
'utilsGTK.cpp',
|
||||||
'cheatsGTK.cpp',
|
'cheatsGTK.cpp',
|
||||||
'main.cpp',
|
'main.cpp',
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,355 @@
|
||||||
|
/* utilsGTK.cpp - this file is part of DeSmuME
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 DeSmuME Team
|
||||||
|
*
|
||||||
|
* This file is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This file is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "utilsGTK.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <gdk/gdkkeysyms.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
A C++ implementation of a GtkCellRendererText subclass which handles
|
||||||
|
newline-delimited text and allows for editing that text with the ability
|
||||||
|
for users to add newlines, based off of a GPLv2+ Python implementation here:
|
||||||
|
https://gitlab.gnome.org/GNOME/gtk/-/issues/175#note_487323
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
DESMUME_ENTRY_ND:
|
||||||
|
An object similar to an Entry, but which allows for newlines to be
|
||||||
|
inserted by holding Shift, Ctrl, or Alt along with pressing Enter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct _DesmumeEntryNdPrivate
|
||||||
|
{
|
||||||
|
GtkWidget *scroll;
|
||||||
|
GtkWidget *editor;
|
||||||
|
gboolean editing_canceled;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
PROP_EDITING_CANCELED = 1,
|
||||||
|
ENTRY_ND_NUM_PROP,
|
||||||
|
} DesmumeEntryNdProperty;
|
||||||
|
|
||||||
|
static GParamSpec *entry_nd_properties[ENTRY_ND_NUM_PROP] = {
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Declared here to statisfy the type creation macro, but defined further down.
|
||||||
|
static void desmume_entry_nd_cell_editable_init(GtkCellEditableIface *iface);
|
||||||
|
|
||||||
|
// As defined in GObject 2.38, which is past the last release of GTK2.
|
||||||
|
// https://gitlab.gnome.org/GNOME/glib/-/blob/main/gobject/gtype.h#L2188
|
||||||
|
#ifndef G_ADD_PRIVATE
|
||||||
|
#define G_ADD_PRIVATE(TypeName) \
|
||||||
|
{ \
|
||||||
|
TypeName##_private_offset = g_type_add_instance_private( \
|
||||||
|
g_define_type_id, sizeof(TypeName##Private)); \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_CODE(DesmumeEntryNd, desmume_entry_nd, GTK_TYPE_EVENT_BOX,
|
||||||
|
G_ADD_PRIVATE(DesmumeEntryNd) G_IMPLEMENT_INTERFACE(
|
||||||
|
GTK_TYPE_CELL_EDITABLE,
|
||||||
|
desmume_entry_nd_cell_editable_init))
|
||||||
|
|
||||||
|
static void desmume_entry_nd_set_property(GObject *object, guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(object);
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
switch ((DesmumeEntryNdProperty) property_id) {
|
||||||
|
case PROP_EDITING_CANCELED:
|
||||||
|
if (priv->editing_canceled != g_value_get_boolean(value)) {
|
||||||
|
priv->editing_canceled = g_value_get_boolean(value);
|
||||||
|
g_object_notify(object, "editing-canceled");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_get_property(GObject *object, guint property_id,
|
||||||
|
GValue *value, GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(object);
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
switch ((DesmumeEntryNdProperty) property_id) {
|
||||||
|
case PROP_EDITING_CANCELED:
|
||||||
|
g_value_set_boolean(value, priv->editing_canceled);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define GDK_EVENT_PROPAGATE FALSE
|
||||||
|
#define GDK_EVENT_STOP TRUE
|
||||||
|
static gboolean desmume_entry_nd_key_press(GtkWidget *widget,
|
||||||
|
GdkEventKey *event, gpointer data)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = (DesmumeEntryNd *) data;
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
// Allow the editor to decide how to handle the key event, except key events
|
||||||
|
// which its parent TextView needs to handle itself
|
||||||
|
gboolean doPropagate = GDK_EVENT_PROPAGATE;
|
||||||
|
guint kv = event->keyval;
|
||||||
|
guint mod = event->state;
|
||||||
|
|
||||||
|
if ((kv == GDK_Return || kv == GDK_KP_Enter || kv == GDK_ISO_Enter) &&
|
||||||
|
!(mod & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK))) {
|
||||||
|
// Enter + Ctrl, Shift, or Mod1 (commonly Alt), enter a newline in the
|
||||||
|
// editor, but otherwise act as confirm for the TextView
|
||||||
|
priv->editing_canceled = FALSE;
|
||||||
|
doPropagate = GDK_EVENT_STOP;
|
||||||
|
gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry_nd));
|
||||||
|
} else if (kv == GDK_Escape) {
|
||||||
|
priv->editing_canceled = TRUE;
|
||||||
|
doPropagate = GDK_EVENT_STOP;
|
||||||
|
gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry_nd));
|
||||||
|
}
|
||||||
|
return doPropagate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean desmume_entry_nd_button_press(GtkWidget *widget,
|
||||||
|
GdkEventButton *event,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
GtkTextView *editor = (GtkTextView *) widget;
|
||||||
|
GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS(editor);
|
||||||
|
klass->button_press_event(widget, event);
|
||||||
|
// We have explicitly described how to handle mouse button events, so do not
|
||||||
|
// propagate.
|
||||||
|
return GDK_EVENT_STOP;
|
||||||
|
}
|
||||||
|
#undef GDK_EVENT_PROPAGATE
|
||||||
|
#undef GDK_EVENT_STOP
|
||||||
|
|
||||||
|
static void desmume_entry_nd_dispose(GObject *object)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(object);
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
// Recursively destroys contained objects, so destroys the editor as well
|
||||||
|
gtk_widget_destroy(priv->scroll);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS(desmume_entry_nd_parent_class)->dispose(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_finalize(GObject *object)
|
||||||
|
{
|
||||||
|
G_OBJECT_CLASS(desmume_entry_nd_parent_class)->finalize(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_size_request(GtkWidget *widget,
|
||||||
|
GtkRequisition *req)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(widget);
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
gtk_widget_size_request(GTK_WIDGET(priv->scroll), req);
|
||||||
|
GtkRequisition *temp_req = gtk_requisition_copy(req);
|
||||||
|
gtk_widget_size_request(GTK_WIDGET(priv->editor), req);
|
||||||
|
req->width += temp_req->width;
|
||||||
|
req->height += temp_req->height;
|
||||||
|
gtk_requisition_free(temp_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_class_init(DesmumeEntryNdClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
||||||
|
object_class->set_property = desmume_entry_nd_set_property;
|
||||||
|
object_class->get_property = desmume_entry_nd_get_property;
|
||||||
|
object_class->dispose = desmume_entry_nd_dispose;
|
||||||
|
object_class->finalize = desmume_entry_nd_finalize;
|
||||||
|
|
||||||
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
|
||||||
|
widget_class->size_request = desmume_entry_nd_size_request;
|
||||||
|
|
||||||
|
entry_nd_properties[PROP_EDITING_CANCELED] =
|
||||||
|
g_param_spec_boolean("editing-canceled", "Editing Canceled",
|
||||||
|
"The edit was canceled", FALSE, G_PARAM_READWRITE);
|
||||||
|
g_object_class_install_properties(object_class, ENTRY_ND_NUM_PROP,
|
||||||
|
entry_nd_properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_init(DesmumeEntryNd *entry_nd)
|
||||||
|
{
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
priv->scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(priv->scroll),
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||||
|
|
||||||
|
priv->editor = gtk_text_view_new();
|
||||||
|
gtk_text_view_set_editable(GTK_TEXT_VIEW(priv->editor), TRUE);
|
||||||
|
gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(priv->editor), FALSE);
|
||||||
|
|
||||||
|
g_signal_connect(priv->editor, "key-press-event",
|
||||||
|
G_CALLBACK(desmume_entry_nd_key_press), entry_nd);
|
||||||
|
g_signal_connect(priv->editor, "button-press-event",
|
||||||
|
G_CALLBACK(desmume_entry_nd_button_press), NULL);
|
||||||
|
|
||||||
|
gtk_container_add(GTK_CONTAINER(priv->scroll), priv->editor);
|
||||||
|
gtk_container_add(GTK_CONTAINER(entry_nd), priv->scroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_start_editing(GtkCellEditable *cell_editable,
|
||||||
|
GdkEvent *event)
|
||||||
|
{
|
||||||
|
DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(cell_editable);
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
gtk_widget_show_all(GTK_WIDGET(entry_nd));
|
||||||
|
gtk_widget_grab_focus(GTK_WIDGET(priv->editor));
|
||||||
|
|
||||||
|
// Highlight the entirety of the editor's text
|
||||||
|
GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->editor));
|
||||||
|
GtkTextIter start, end;
|
||||||
|
gtk_text_buffer_get_bounds(buf, &start, &end);
|
||||||
|
gtk_text_buffer_select_range(buf, &start, &end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gchar *desmume_entry_nd_get_text(DesmumeEntryNd *entry_nd)
|
||||||
|
{
|
||||||
|
DesmumeEntryNdPrivate *priv =
|
||||||
|
(DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->editor));
|
||||||
|
GtkTextIter start, end;
|
||||||
|
gtk_text_buffer_get_bounds(buf, &start, &end);
|
||||||
|
|
||||||
|
return gtk_text_buffer_get_text(buf, &start, &end, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_set_text(DesmumeEntryNd *entry_nd,
|
||||||
|
const gchar *text)
|
||||||
|
{
|
||||||
|
DesmumeEntryNdPrivate *priv;
|
||||||
|
priv = (DesmumeEntryNdPrivate *) desmume_entry_nd_get_instance_private(
|
||||||
|
entry_nd);
|
||||||
|
|
||||||
|
GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->editor));
|
||||||
|
gtk_text_buffer_set_text(buf, text, strlen(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_entry_nd_cell_editable_init(GtkCellEditableIface *iface)
|
||||||
|
{
|
||||||
|
iface->start_editing = desmume_entry_nd_start_editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *desmume_entry_nd_new()
|
||||||
|
{
|
||||||
|
return (GtkWidget *) g_object_new(DESMUME_TYPE_ENTRY_ND, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
DESMUME_CELL_RENDERER_ND_TEXT:
|
||||||
|
A subclass of GtkCellRendererText which creates our DesmumeEntryNd instead
|
||||||
|
of a GtkEntry, which allows a cell in a TreeView to accepts newlines.
|
||||||
|
*/
|
||||||
|
|
||||||
|
G_DEFINE_TYPE(DesmumeCellRendererNdtext, desmume_cell_renderer_ndtext,
|
||||||
|
GTK_TYPE_CELL_RENDERER_TEXT)
|
||||||
|
|
||||||
|
static void desmume_cell_renderer_ndtext_editing_done(GtkCellEditable *entry_nd,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
gboolean canceled;
|
||||||
|
g_object_get(entry_nd, "editing-canceled", &canceled, NULL);
|
||||||
|
if (!canceled) {
|
||||||
|
const gchar *path =
|
||||||
|
(gchar *) g_object_get_data(G_OBJECT(entry_nd), "full-text");
|
||||||
|
gchar *new_text = desmume_entry_nd_get_text(DESMUME_ENTRY_ND(entry_nd));
|
||||||
|
|
||||||
|
guint signal_id =
|
||||||
|
g_signal_lookup("edited", DESMUME_TYPE_CELL_RENDERER_NDTEXT);
|
||||||
|
g_signal_emit(data, signal_id, 0, path, new_text);
|
||||||
|
g_free(new_text);
|
||||||
|
}
|
||||||
|
gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(entry_nd));
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkCellEditable *desmume_cell_renderer_ndtext_start_editing(
|
||||||
|
GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget,
|
||||||
|
const gchar *path, GdkRectangle *background_area, GdkRectangle *cell_area,
|
||||||
|
GtkCellRendererState flags)
|
||||||
|
{
|
||||||
|
DesmumeCellRendererNdtext *ndtext = DESMUME_CELL_RENDERER_NDTEXT(cell);
|
||||||
|
gboolean editable;
|
||||||
|
g_object_get(G_OBJECT(ndtext), "editable", &editable, NULL);
|
||||||
|
if (!editable)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
gchar *text;
|
||||||
|
g_object_get(G_OBJECT(ndtext), "text", &text, NULL);
|
||||||
|
|
||||||
|
GtkWidget *entry_nd = desmume_entry_nd_new();
|
||||||
|
|
||||||
|
if (text != NULL) {
|
||||||
|
desmume_entry_nd_set_text(DESMUME_ENTRY_ND(entry_nd), text);
|
||||||
|
g_free(text);
|
||||||
|
}
|
||||||
|
g_object_set_data_full(G_OBJECT(entry_nd), "full-text", g_strdup(path),
|
||||||
|
g_free);
|
||||||
|
|
||||||
|
g_signal_connect(entry_nd, "editing-done",
|
||||||
|
G_CALLBACK(desmume_cell_renderer_ndtext_editing_done),
|
||||||
|
ndtext);
|
||||||
|
return GTK_CELL_EDITABLE(entry_nd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
desmume_cell_renderer_ndtext_class_init(DesmumeCellRendererNdtextClass *klass)
|
||||||
|
{
|
||||||
|
GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass);
|
||||||
|
cell_class->start_editing = desmume_cell_renderer_ndtext_start_editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void desmume_cell_renderer_ndtext_init(DesmumeCellRendererNdtext *ndtext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkCellRenderer *desmume_cell_renderer_ndtext_new()
|
||||||
|
{
|
||||||
|
return GTK_CELL_RENDERER(
|
||||||
|
g_object_new(DESMUME_TYPE_CELL_RENDERER_NDTEXT, NULL));
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/* utilsGTK.h - this file is part of DeSmuME
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 DeSmuME Team
|
||||||
|
*
|
||||||
|
* This file is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This file is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __UTILS_GTK_H__
|
||||||
|
#define __UTILS_GTK_H__
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define DESMUME_TYPE_ENTRY_ND (desmume_entry_nd_get_type())
|
||||||
|
#define DESMUME_ENTRY_ND(obj) \
|
||||||
|
(G_TYPE_CHECK_INSTANCE_CAST((obj), DESMUME_TYPE_ENTRY_ND, DesmumeEntryNd))
|
||||||
|
#define DESMUME_ENTRY_ND_CLASS(klass) \
|
||||||
|
(G_TYPE_CHECK_CLASS_CAST((klass), DESMUME_TYPE_ENTRY_ND, DesmumeEntryNdClass))
|
||||||
|
#define DESMUME_IS_ENTRY_ND(obj) \
|
||||||
|
(G_TYPE_CHECK_INSTANCE_TYPE((obj), DESMUME_TYPE_ENTRY_ND))
|
||||||
|
#define DESMUME_IS_ENTRY_ND_CLASS(klass) \
|
||||||
|
(G_TYPE_CHECK_CLASS_TYPE((klass), DESMUME_TYPE_ENTRY_ND))
|
||||||
|
#define DESMUME_ENTRY_ND_GET_CLASS(obj) \
|
||||||
|
(G_TYPE_INSTANCE_GET_CLASS((obj), DESMUME_TYPE_ENTRY_ND, DesmumeEntryNdClass))
|
||||||
|
|
||||||
|
typedef struct _DesmumeEntryNd DesmumeEntryNd;
|
||||||
|
typedef struct _DesmumeEntryNdClass DesmumeEntryNdClass;
|
||||||
|
typedef struct _DesmumeEntryNdPrivate DesmumeEntryNdPrivate;
|
||||||
|
|
||||||
|
struct _DesmumeEntryNd
|
||||||
|
{
|
||||||
|
GtkEventBox parent;
|
||||||
|
DesmumeEntryNdPrivate *priv;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _DesmumeEntryNdClass
|
||||||
|
{
|
||||||
|
GtkEventBoxClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
GType desmume_entry_nd_get_type(void) G_GNUC_CONST;
|
||||||
|
GtkEventBox *entry_nd_new(void);
|
||||||
|
|
||||||
|
#define DESMUME_TYPE_CELL_RENDERER_NDTEXT \
|
||||||
|
(desmume_cell_renderer_ndtext_get_type())
|
||||||
|
#define DESMUME_CELL_RENDERER_NDTEXT(obj) \
|
||||||
|
(G_TYPE_CHECK_INSTANCE_CAST((obj), DESMUME_TYPE_CELL_RENDERER_NDTEXT, \
|
||||||
|
DesmumeCellRendererNdtext))
|
||||||
|
#define DESMUME_CELL_RENDERER_NDTEXT_CLASS(klass) \
|
||||||
|
(G_TYPE_CHECK_CLASS_CAST( \
|
||||||
|
(klass), DESMUME_TYPE_CELL_RENDERER_NDTEXT DesmumeCellRendererNdtextClass))
|
||||||
|
#define DESMUME_IS_CELL_RENDERER_NDTEXT(obj) \
|
||||||
|
(G_TYPE_CHECK_INSTANCE_TYPE((obj), DESMUME_TYPE_CELL_RENDERER_NDTEXT))
|
||||||
|
#define DESMUME_IS_CELL_RENDERER_NDTEXT_CLASS(klass) \
|
||||||
|
(G_TYPE_CHECK_CLASS_TYPE((klass), DESMUME_TYPE_CELL_RENDERER_NDTEXT))
|
||||||
|
#define DESMUME_CELL_RENDERER_NDTEXT_GET_CLASS(obj) \
|
||||||
|
(G_TYPE_INSTANCE_GET_CLASS(obj), DESMUME_TYPE_CELL_RENDERER_NDTEXT, \
|
||||||
|
DesmumeCellRendererNdtextClass)
|
||||||
|
|
||||||
|
typedef struct _DesmumeCellRendererNdtext DesmumeCellRendererNdtext;
|
||||||
|
typedef struct _DesmumeCellRendererNdtextClass DesmumeCellRendererNdtextClass;
|
||||||
|
|
||||||
|
struct _DesmumeCellRendererNdtext
|
||||||
|
{
|
||||||
|
GtkCellRendererText parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _DesmumeCellRendererNdtextClass
|
||||||
|
{
|
||||||
|
GtkCellRendererTextClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
GType desmume_cell_renderer_ndtext_get_type(void) G_GNUC_CONST;
|
||||||
|
GtkCellRenderer *desmume_cell_renderer_ndtext_new(void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif /*__UTILS_GTK_H__*/
|
Loading…
Reference in New Issue