bsnes/higan/phoenix/gtk/widget/hex-edit.cpp

273 lines
7.9 KiB
C++
Executable File

namespace phoenix {
static bool HexEdit_keyPress(GtkWidget *widget, GdkEventKey *event, HexEdit *self) {
return self->p.keyPress(event->keyval);
}
static bool HexEdit_scroll(GtkRange *range, GtkScrollType scroll, gdouble value, HexEdit *self) {
self->p.scroll((unsigned)value);
return false;
}
bool pHexEdit::focused() {
return GTK_WIDGET_HAS_FOCUS(subWidget);
}
void pHexEdit::setColumns(unsigned columns) {
setScroll();
update();
}
void pHexEdit::setLength(unsigned length) {
setScroll();
update();
}
void pHexEdit::setOffset(unsigned offset) {
setScroll();
updateScroll();
update();
}
void pHexEdit::setRows(unsigned rows) {
setScroll();
update();
}
void pHexEdit::update() {
if(!hexEdit.onRead) {
gtk_text_buffer_set_text(textBuffer, "", -1);
return;
}
unsigned position = cursorPosition();
string output;
unsigned offset = hexEdit.state.offset;
for(unsigned row = 0; row < hexEdit.state.rows; row++) {
output.append(hex<8>(offset));
output.append(" ");
string hexdata;
string ansidata = " ";
for(unsigned column = 0; column < hexEdit.state.columns; column++) {
if(offset < hexEdit.state.length) {
uint8_t data = hexEdit.onRead(offset++);
hexdata.append(hex<2>(data));
hexdata.append(" ");
char buffer[2] = { data >= 0x20 && data <= 0x7e ? (char)data : '.', 0 };
ansidata.append(buffer);
} else {
hexdata.append(" ");
ansidata.append(" ");
}
}
output.append(hexdata);
output.append(ansidata);
if(offset >= hexEdit.state.length) break;
if(row != hexEdit.state.rows - 1) output.append("\n");
}
gtk_text_buffer_set_text(textBuffer, output, -1);
if(position == 0) position = 10; //start at first position where hex values can be entered
setCursorPosition(position);
}
void pHexEdit::constructor() {
gtkWidget = gtk_hbox_new(false, 0);
container = gtk_scrolled_window_new(0, 0);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(container), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(container), GTK_SHADOW_ETCHED_IN);
subWidget = gtk_text_view_new();
gtk_text_view_set_editable(GTK_TEXT_VIEW(subWidget), false);
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), GTK_WRAP_NONE);
gtk_container_add(GTK_CONTAINER(container), subWidget);
g_signal_connect(G_OBJECT(subWidget), "key-press-event", G_CALLBACK(HexEdit_keyPress), (gpointer)&hexEdit);
scrollBar = gtk_vscrollbar_new((GtkAdjustment*)0);
gtk_range_set_range(GTK_RANGE(scrollBar), 0, 255);
gtk_range_set_increments(GTK_RANGE(scrollBar), 1, 16);
gtk_widget_set_sensitive(scrollBar, false);
g_signal_connect(G_OBJECT(scrollBar), "change-value", G_CALLBACK(HexEdit_scroll), (gpointer)&hexEdit);
gtk_box_pack_start(GTK_BOX(gtkWidget), container, true, true, 0);
gtk_box_pack_start(GTK_BOX(gtkWidget), scrollBar, false, false, 1);
textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(subWidget));
textCursor = gtk_text_buffer_get_mark(textBuffer, "insert");
gtk_widget_show(scrollBar);
gtk_widget_show(subWidget);
gtk_widget_show(container);
setColumns(hexEdit.state.columns);
setRows(hexEdit.state.rows);
setLength(hexEdit.state.length);
setOffset(hexEdit.state.offset);
update();
}
void pHexEdit::destructor() {
gtk_widget_destroy(scrollBar);
gtk_widget_destroy(subWidget);
gtk_widget_destroy(container);
gtk_widget_destroy(gtkWidget);
}
void pHexEdit::orphan() {
destructor();
constructor();
}
unsigned pHexEdit::cursorPosition() {
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark(textBuffer, &iter, textCursor);
return gtk_text_iter_get_offset(&iter);
}
bool pHexEdit::keyPress(unsigned scancode) {
if(!hexEdit.onRead) return false;
unsigned position = cursorPosition();
unsigned lineWidth = 10 + (hexEdit.state.columns * 3) + 1 + hexEdit.state.columns + 1;
unsigned cursorY = position / lineWidth;
unsigned cursorX = position % lineWidth;
if(scancode == GDK_Home) {
setCursorPosition(cursorY * lineWidth + 10);
return true;
}
if(scancode == GDK_End) {
setCursorPosition(cursorY * lineWidth + 10 + (hexEdit.state.columns * 3 - 1));
return true;
}
if(scancode == GDK_Up) {
if(cursorY != 0) return false;
signed newOffset = hexEdit.state.offset - hexEdit.state.columns;
if(newOffset >= 0) {
hexEdit.setOffset(newOffset);
update();
}
return true;
}
if(scancode == GDK_Down) {
if(cursorY != hexEdit.state.rows - 1) return false;
signed newOffset = hexEdit.state.offset + hexEdit.state.columns;
if(newOffset + hexEdit.state.columns * hexEdit.state.rows - (hexEdit.state.columns - 1) <= hexEdit.state.length) {
hexEdit.setOffset(newOffset);
update();
}
return true;
}
if(scancode == GDK_Page_Up) {
signed newOffset = hexEdit.state.offset - hexEdit.state.columns * hexEdit.state.rows;
if(newOffset >= 0) {
hexEdit.setOffset(newOffset);
} else {
hexEdit.setOffset(0);
}
update();
return true;
}
if(scancode == GDK_Page_Down) {
signed newOffset = hexEdit.state.offset + hexEdit.state.columns * hexEdit.state.rows;
for(unsigned n = 0; n < hexEdit.state.rows; n++) {
if(newOffset + hexEdit.state.columns * hexEdit.state.rows - (hexEdit.state.columns - 1) <= hexEdit.state.length) {
hexEdit.setOffset(newOffset);
update();
break;
}
newOffset -= hexEdit.state.columns;
}
return true;
}
//convert scancode to hex nibble
if(scancode >= '0' && scancode <= '9') scancode = scancode - '0';
else if(scancode >= 'A' && scancode <= 'F') scancode = scancode - 'A' + 10;
else if(scancode >= 'a' && scancode <= 'f') scancode = scancode - 'a' + 10;
else return false; //not a valid hex value
if(cursorX >= 10) {
//not on an offset
cursorX -= 10;
if((cursorX % 3) != 2) {
//not on a space
bool cursorNibble = (cursorX % 3) == 1; //0 = high, 1 = low
cursorX /= 3;
if(cursorX < hexEdit.state.columns) {
//not in ANSI region
unsigned offset = hexEdit.state.offset + (cursorY * hexEdit.state.columns + cursorX);
if(offset >= hexEdit.state.length) return false; //do not edit past end of data
uint8_t data = hexEdit.onRead(offset);
//write modified value
if(cursorNibble == 1) {
data = (data & 0xf0) | (scancode << 0);
} else {
data = (data & 0x0f) | (scancode << 4);
}
if(hexEdit.onWrite) hexEdit.onWrite(offset, data);
//auto-advance cursor to next nibble/byte
position++;
if(cursorNibble && cursorX != hexEdit.state.columns - 1) position++;
setCursorPosition(position);
//refresh output to reflect modified data
update();
}
}
}
return true;
}
void pHexEdit::scroll(unsigned position) {
unsigned rows = hexEdit.state.length / hexEdit.state.columns;
if(position >= rows) position = rows - 1;
hexEdit.setOffset(position * hexEdit.state.columns);
}
void pHexEdit::setCursorPosition(unsigned position) {
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark(textBuffer, &iter, textCursor);
//GTK+ will throw many errors to the terminal if you set iterator past end of buffer
GtkTextIter endIter;
gtk_text_buffer_get_end_iter(textBuffer, &iter);
unsigned endPosition = gtk_text_iter_get_offset(&iter);
gtk_text_iter_set_offset(&iter, min(position, endPosition));
gtk_text_buffer_place_cursor(textBuffer, &iter);
}
void pHexEdit::setScroll() {
unsigned rows = hexEdit.state.length / hexEdit.state.columns;
if(rows) rows--;
if(rows) {
gtk_range_set_range(GTK_RANGE(scrollBar), 0, rows);
gtk_widget_set_sensitive(scrollBar, true);
} else {
gtk_widget_set_sensitive(scrollBar, false);
}
}
void pHexEdit::updateScroll() {
unsigned row = hexEdit.state.offset / hexEdit.state.columns;
gtk_range_set_value(GTK_RANGE(scrollBar), row);
}
}