/* * TilEm II * * Copyright (c) 2010-2011 Thibault Duponchelle * Copyright (c) 2010-2011 Benjamin Moody * * This program 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 3 of the * License, or (at your option) any later version. * * This program 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. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include "gui.h" #include "files.h" #include "filedlg.h" /* Table for translating skin-file key number (based on actual position, and defined by the VTI/TiEmu file formats) into a scancode. Note that the TILEM_KEY_* constants are named according to the TI-83 keypad layout; other models use different names for the keys, but the same scancodes. */ static const int keycode_map[] = { TILEM_KEY_YEQU, TILEM_KEY_WINDOW, TILEM_KEY_ZOOM, TILEM_KEY_TRACE, TILEM_KEY_GRAPH, TILEM_KEY_2ND, TILEM_KEY_MODE, TILEM_KEY_DEL, TILEM_KEY_LEFT, TILEM_KEY_RIGHT, TILEM_KEY_UP, TILEM_KEY_DOWN, TILEM_KEY_ALPHA, TILEM_KEY_GRAPHVAR, TILEM_KEY_STAT, TILEM_KEY_MATH, TILEM_KEY_MATRIX, TILEM_KEY_PRGM, TILEM_KEY_VARS, TILEM_KEY_CLEAR, TILEM_KEY_RECIP, TILEM_KEY_SIN, TILEM_KEY_COS, TILEM_KEY_TAN, TILEM_KEY_POWER, TILEM_KEY_SQUARE, TILEM_KEY_COMMA, TILEM_KEY_LPAREN, TILEM_KEY_RPAREN, TILEM_KEY_DIV, TILEM_KEY_LOG, TILEM_KEY_7, TILEM_KEY_8, TILEM_KEY_9, TILEM_KEY_MUL, TILEM_KEY_LN, TILEM_KEY_4, TILEM_KEY_5, TILEM_KEY_6, TILEM_KEY_SUB, TILEM_KEY_STORE, TILEM_KEY_1, TILEM_KEY_2, TILEM_KEY_3, TILEM_KEY_ADD, TILEM_KEY_ON, TILEM_KEY_0, TILEM_KEY_DECPNT, TILEM_KEY_CHS, TILEM_KEY_ENTER }; /* Find the keycode for the key (if any) at the given position. If the keys overlap, choose the "nearest" (according to Manhattan distance to the midpoint.) */ static int scan_click(const SKIN_INFOS* skin, double x, double y) { guint ix, iy, nearest = 0, i; int dx, dy, d, best_d = G_MAXINT; if (!skin) return 0; ix = (x * skin->sx + 0.5); iy = (y * skin->sy + 0.5); for (i = 0; i < G_N_ELEMENTS(keycode_map); i++) { if (ix >= skin->keys_pos[i].left && ix < skin->keys_pos[i].right && iy >= skin->keys_pos[i].top && iy < skin->keys_pos[i].bottom) { dx = (skin->keys_pos[i].left + skin->keys_pos[i].right - 2 * ix); dy = (skin->keys_pos[i].top + skin->keys_pos[i].bottom - 2 * iy); d = ABS(dx) + ABS(dy); if (d < best_d) { best_d = d; nearest = keycode_map[i]; } } } return nearest; } /* Retrieve pointer coordinates for an input device. */ static void get_device_pointer(GdkWindow *win, GdkDevice *dev, gdouble *x, gdouble *y, GdkModifierType *mask) { gdouble *axes; int i; axes = g_new(gdouble, dev->num_axes); gdk_device_get_state(dev, win, axes, mask); for (i = 0; i < dev->num_axes; i++) { if (x && dev->axes[i].use == GDK_AXIS_X) *x = axes[i]; else if (y && dev->axes[i].use == GDK_AXIS_Y) *y = axes[i]; } g_free(axes); } /* Show a nice GtkAboutDialog */ void show_about() { GtkWidget *dialog = gtk_about_dialog_new(); gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), "2.0"); gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(dialog), "(c) Benjamin Moody\n(c) Thibault Duponchelle\n(c) Luc Bruant\n"); gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog), "TilEm is a TI Linux Emulator.\n It emulates all current z80 models.\n TI73, TI76, TI81, TI82, TI83(+)(SE), TI84+(SE), TI85 and TI86 ;D"); gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog), "http://lpg.ticalc.org/prj_tilem/"); /* Add the logo */ char* tilem_ban = get_shared_file_path("pixs", "tilem_ban.png", NULL); if(tilem_ban) { GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(tilem_ban, NULL); gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), pixbuf); g_object_unref(pixbuf), pixbuf = NULL; } gtk_dialog_run(GTK_DIALOG (dialog)); gtk_widget_destroy(dialog); } void launch_debugger(TilemEmulatorWindow *ewin) { if (!ewin->emu->dbg) ewin->emu->dbg = tilem_debugger_new(ewin->emu); tilem_debugger_show(ewin->emu->dbg); } /* Press a key, ensuring that at most one key is "pressed" at a time due to this function (if pointer moves or is released, we don't want the old key held down.) FIXME: on multi-pointer displays, allow each input device to act separately */ static void press_mouse_key(TilemEmulatorWindow* ewin, int key) { if (ewin->mouse_key == key) return; tilem_calc_emulator_release_key(ewin->emu, ewin->mouse_key); tilem_calc_emulator_press_key(ewin->emu, key); ewin->mouse_key = key; } /* Mouse button pressed */ gboolean mouse_press_event(G_GNUC_UNUSED GtkWidget* w, GdkEventButton *event, gpointer data) { TilemEmulatorWindow* ewin = data; int key; key = scan_click(ewin->skin, event->x, event->y); if (event->button == 1) { /* button 1: press key until button is released or pointer moves away */ press_mouse_key(ewin, key); return TRUE; } else if (event->button == 2) { /* button 2: hold key down permanently */ tilem_calc_emulator_press_key(ewin->emu, key); return TRUE; } else if (event->button == 3) { /* button 3: popup menu */ gtk_menu_popup(GTK_MENU(ewin->popup_menu), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } else return FALSE; } /* Mouse pointer moved */ gboolean pointer_motion_event(G_GNUC_UNUSED GtkWidget* w, GdkEventMotion *event, gpointer data) { TilemEmulatorWindow* ewin = data; int key; if (event->is_hint) get_device_pointer(event->window, event->device, &event->x, &event->y, &event->state); if (event->state & GDK_BUTTON1_MASK) key = scan_click(ewin->skin, event->x, event->y); else key = 0; press_mouse_key(ewin, key); return FALSE; } /* Mouse button released */ gboolean mouse_release_event(G_GNUC_UNUSED GtkWidget* w, GdkEventButton *event, gpointer data) { TilemEmulatorWindow* ewin = data; if (event->button == 1) press_mouse_key(ewin, 0); return FALSE; } /* Find key binding for the given keysym and modifiers */ static TilemKeyBinding* find_key_binding_for_keysym(TilemCalcEmulator* emu, guint keyval, GdkModifierType mods) { int i; for (i = 0; i < emu->nkeybindings; i++) if (keyval == emu->keybindings[i].keysym && mods == emu->keybindings[i].modifiers) return &emu->keybindings[i]; return NULL; } /* Find key binding matching the given event */ static TilemKeyBinding* find_key_binding(TilemCalcEmulator* emu, GdkEventKey* event) { GdkDisplay *dpy; GdkKeymap *km; guint keyval; GdkModifierType consumed, mods; TilemKeyBinding *kb; dpy = gdk_drawable_get_display(event->window); km = gdk_keymap_get_for_display(dpy); /* determine the relevant set of modifiers */ gdk_keymap_translate_keyboard_state(km, event->hardware_keycode, event->state, event->group, &keyval, NULL, NULL, &consumed); mods = (event->state & ~consumed & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)); if (event->state & GDK_LOCK_MASK && (kb = find_key_binding_for_keysym(emu, keyval, mods | GDK_LOCK_MASK))) { return kb; } return find_key_binding_for_keysym(emu, keyval, mods); } /* Key-press event */ gboolean key_press_event(G_GNUC_UNUSED GtkWidget* w, GdkEventKey* event, gpointer data) { TilemEmulatorWindow *ewin = data; TilemKeyBinding *kb; int i, key; unsigned int hwkey; /* Ignore repeating keys */ for (i = 0; i < 64; i++) if (ewin->keypress_keycodes[i] == event->hardware_keycode) return TRUE; if (ewin->sequence_keycode == event->hardware_keycode) return TRUE; if (!(kb = find_key_binding(ewin->emu, event))) return FALSE; hwkey = event->hardware_keycode; if (kb->nscancodes == 1) { /* if queue is empty, just press the key; otherwise add it to the queue */ key = kb->scancodes[0]; if (tilem_calc_emulator_press_or_queue(ewin->emu, key)) ewin->sequence_keycode = hwkey; else ewin->keypress_keycodes[key] = hwkey; } else { tilem_calc_emulator_queue_keys(ewin->emu, kb->scancodes, kb->nscancodes); ewin->sequence_keycode = hwkey; } return TRUE; } /* Key-release event */ gboolean key_release_event(G_GNUC_UNUSED GtkWidget* w, GdkEventKey* event, gpointer data) { TilemEmulatorWindow *ewin = data; int i; /* Check if the key that was just released was one that activated a calculator keypress. (Do not try to look up event->keyval; modifiers may have changed since the key was pressed.) */ for (i = 0; i < 64; i++) { if (ewin->keypress_keycodes[i] == event->hardware_keycode) { tilem_calc_emulator_release_key(ewin->emu, i); ewin->keypress_keycodes[i] = 0; } } if (ewin->sequence_keycode == event->hardware_keycode) { tilem_calc_emulator_release_queued_key(ewin->emu); ewin->sequence_keycode = 0; } return FALSE; } static void place_menu(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data) { GtkWidget *w = data; GdkWindow *win; GdkScreen *screen; int n; win = gtk_widget_get_window(w); gdk_window_get_origin(win, x, y); screen = gdk_drawable_get_screen(win); n = gdk_screen_get_monitor_at_point(screen, *x, *y); gtk_menu_set_monitor(menu, n); *push_in = FALSE; } /* Pop up menu on main window */ gboolean popup_menu_event(GtkWidget* w, gpointer data) { TilemEmulatorWindow *ewin = data; gtk_menu_popup(GTK_MENU(ewin->popup_menu), NULL, NULL, &place_menu, w, 0, gtk_get_current_event_time()); return TRUE; } /* Callback function for the drag and drop event */ void drag_data_received(G_GNUC_UNUSED GtkWidget *win, G_GNUC_UNUSED GdkDragContext *dc, G_GNUC_UNUSED gint x, G_GNUC_UNUSED gint y, GtkSelectionData *seldata, G_GNUC_UNUSED guint info, G_GNUC_UNUSED guint t, gpointer data) { TilemEmulatorWindow *ewin = data; gchar **uris, **filenames; gint i, j, n; uris = gtk_selection_data_get_uris(seldata); if (!uris) return; n = g_strv_length(uris); filenames = g_new0(gchar *, n + 1); for (i = j = 0; i < n; i++) { filenames[j] = g_filename_from_uri(uris[i], NULL, NULL); if (filenames[j]) j++; } filenames[j] = NULL; load_files(ewin, filenames); g_strfreev(filenames); }