1 /*
2 * TilEm II
3 *
4 * Copyright (c) 2010-2011 Thibault Duponchelle
5 * Copyright (c) 2010-2011 Benjamin Moody
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <gtk/gtk.h>
29 #include <ticalcs.h>
30 #include <tilem.h>
31 #include <scancodes.h>
32
33 #include "gui.h"
34 #include "files.h"
35 #include "filedlg.h"
36
37 /* Table for translating skin-file key number (based on actual
38 position, and defined by the VTI/TiEmu file formats) into a
39 scancode. Note that the TILEM_KEY_* constants are named according
40 to the TI-83 keypad layout; other models use different names for
41 the keys, but the same scancodes. */
42 static const int keycode_map[] =
43 { TILEM_KEY_YEQU,
44 TILEM_KEY_WINDOW,
45 TILEM_KEY_ZOOM,
46 TILEM_KEY_TRACE,
47 TILEM_KEY_GRAPH,
48
49 TILEM_KEY_2ND,
50 TILEM_KEY_MODE,
51 TILEM_KEY_DEL,
52 TILEM_KEY_LEFT,
53 TILEM_KEY_RIGHT,
54 TILEM_KEY_UP,
55 TILEM_KEY_DOWN,
56 TILEM_KEY_ALPHA,
57 TILEM_KEY_GRAPHVAR,
58 TILEM_KEY_STAT,
59
60 TILEM_KEY_MATH,
61 TILEM_KEY_MATRIX,
62 TILEM_KEY_PRGM,
63 TILEM_KEY_VARS,
64 TILEM_KEY_CLEAR,
65
66 TILEM_KEY_RECIP,
67 TILEM_KEY_SIN,
68 TILEM_KEY_COS,
69 TILEM_KEY_TAN,
70 TILEM_KEY_POWER,
71
72 TILEM_KEY_SQUARE,
73 TILEM_KEY_COMMA,
74 TILEM_KEY_LPAREN,
75 TILEM_KEY_RPAREN,
76 TILEM_KEY_DIV,
77
78 TILEM_KEY_LOG,
79 TILEM_KEY_7,
80 TILEM_KEY_8,
81 TILEM_KEY_9,
82 TILEM_KEY_MUL,
83
84 TILEM_KEY_LN,
85 TILEM_KEY_4,
86 TILEM_KEY_5,
87 TILEM_KEY_6,
88 TILEM_KEY_SUB,
89
90 TILEM_KEY_STORE,
91 TILEM_KEY_1,
92 TILEM_KEY_2,
93 TILEM_KEY_3,
94 TILEM_KEY_ADD,
95
96 TILEM_KEY_ON,
97 TILEM_KEY_0,
98 TILEM_KEY_DECPNT,
99 TILEM_KEY_CHS,
100 TILEM_KEY_ENTER };
101
102 /* Find the keycode for the key (if any) at the given position. If
103 the keys overlap, choose the "nearest" (according to Manhattan
104 distance to the midpoint.) */
scan_click(const SKIN_INFOS * skin,double x,double y)105 static int scan_click(const SKIN_INFOS* skin, double x, double y)
106 {
107 guint ix, iy, nearest = 0, i;
108 int dx, dy, d, best_d = G_MAXINT;
109
110 if (!skin)
111 return 0;
112
113 ix = (x * skin->sx + 0.5);
114 iy = (y * skin->sy + 0.5);
115
116 for (i = 0; i < G_N_ELEMENTS(keycode_map); i++) {
117 if (ix >= skin->keys_pos[i].left
118 && ix < skin->keys_pos[i].right
119 && iy >= skin->keys_pos[i].top
120 && iy < skin->keys_pos[i].bottom) {
121 dx = (skin->keys_pos[i].left + skin->keys_pos[i].right
122 - 2 * ix);
123 dy = (skin->keys_pos[i].top + skin->keys_pos[i].bottom
124 - 2 * iy);
125 d = ABS(dx) + ABS(dy);
126
127 if (d < best_d) {
128 best_d = d;
129 nearest = keycode_map[i];
130 }
131 }
132 }
133
134 return nearest;
135 }
136
137 /* Retrieve pointer coordinates for an input device. */
get_device_pointer(GdkWindow * win,GdkDevice * dev,gdouble * x,gdouble * y,GdkModifierType * mask)138 static void get_device_pointer(GdkWindow *win, GdkDevice *dev,
139 gdouble *x, gdouble *y, GdkModifierType *mask)
140 {
141 gdouble *axes;
142 int i;
143
144 axes = g_new(gdouble, dev->num_axes);
145 gdk_device_get_state(dev, win, axes, mask);
146
147 for (i = 0; i < dev->num_axes; i++) {
148 if (x && dev->axes[i].use == GDK_AXIS_X)
149 *x = axes[i];
150 else if (y && dev->axes[i].use == GDK_AXIS_Y)
151 *y = axes[i];
152 }
153
154 g_free(axes);
155 }
156
157 /* Show a nice GtkAboutDialog */
show_about()158 void show_about()
159 {
160 GtkWidget *dialog = gtk_about_dialog_new();
161 gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), "2.0");
162 gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(dialog), "(c) Benjamin Moody\n(c) Thibault Duponchelle\n(c) Luc Bruant\n");
163 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");
164 gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog), "http://lpg.ticalc.org/prj_tilem/");
165
166 /* Add the logo */
167 char* tilem_ban = get_shared_file_path("pixs", "tilem_ban.png", NULL);
168 if(tilem_ban) {
169 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(tilem_ban, NULL);
170 gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), pixbuf);
171 g_object_unref(pixbuf), pixbuf = NULL;
172 }
173
174 gtk_dialog_run(GTK_DIALOG (dialog));
175 gtk_widget_destroy(dialog);
176
177 }
178
179
launch_debugger(TilemEmulatorWindow * ewin)180 void launch_debugger(TilemEmulatorWindow *ewin)
181 {
182 if (!ewin->emu->dbg)
183 ewin->emu->dbg = tilem_debugger_new(ewin->emu);
184 tilem_debugger_show(ewin->emu->dbg);
185 }
186
187 /* Press a key, ensuring that at most one key is "pressed" at a time
188 due to this function (if pointer moves or is released, we don't
189 want the old key held down.)
190
191 FIXME: on multi-pointer displays, allow each input device to act
192 separately */
press_mouse_key(TilemEmulatorWindow * ewin,int key)193 static void press_mouse_key(TilemEmulatorWindow* ewin, int key)
194 {
195 if (ewin->mouse_key == key)
196 return;
197
198 tilem_calc_emulator_release_key(ewin->emu, ewin->mouse_key);
199 tilem_calc_emulator_press_key(ewin->emu, key);
200 ewin->mouse_key = key;
201 }
202
203 /* Mouse button pressed */
mouse_press_event(G_GNUC_UNUSED GtkWidget * w,GdkEventButton * event,gpointer data)204 gboolean mouse_press_event(G_GNUC_UNUSED GtkWidget* w, GdkEventButton *event,
205 gpointer data)
206 {
207 TilemEmulatorWindow* ewin = data;
208 int key;
209
210 key = scan_click(ewin->skin, event->x, event->y);
211
212 if (event->button == 1) {
213 /* button 1: press key until button is released or
214 pointer moves away */
215 press_mouse_key(ewin, key);
216 return TRUE;
217 }
218 else if (event->button == 2) {
219 /* button 2: hold key down permanently */
220 tilem_calc_emulator_press_key(ewin->emu, key);
221 return TRUE;
222 }
223 else if (event->button == 3) {
224 /* button 3: popup menu */
225 gtk_menu_popup(GTK_MENU(ewin->popup_menu),
226 NULL, NULL, NULL, NULL,
227 event->button, event->time);
228 return TRUE;
229 }
230 else
231 return FALSE;
232 }
233
234 /* Mouse pointer moved */
pointer_motion_event(G_GNUC_UNUSED GtkWidget * w,GdkEventMotion * event,gpointer data)235 gboolean pointer_motion_event(G_GNUC_UNUSED GtkWidget* w, GdkEventMotion *event,
236 gpointer data)
237 {
238 TilemEmulatorWindow* ewin = data;
239 int key;
240
241 if (event->is_hint)
242 get_device_pointer(event->window, event->device,
243 &event->x, &event->y, &event->state);
244
245 if (event->state & GDK_BUTTON1_MASK)
246 key = scan_click(ewin->skin, event->x, event->y);
247 else
248 key = 0;
249
250 press_mouse_key(ewin, key);
251
252 return FALSE;
253 }
254
255 /* Mouse button released */
mouse_release_event(G_GNUC_UNUSED GtkWidget * w,GdkEventButton * event,gpointer data)256 gboolean mouse_release_event(G_GNUC_UNUSED GtkWidget* w, GdkEventButton *event,
257 gpointer data)
258 {
259 TilemEmulatorWindow* ewin = data;
260
261 if (event->button == 1)
262 press_mouse_key(ewin, 0);
263
264 return FALSE;
265 }
266
267 /* Find key binding for the given keysym and modifiers */
find_key_binding_for_keysym(TilemCalcEmulator * emu,guint keyval,GdkModifierType mods)268 static TilemKeyBinding* find_key_binding_for_keysym(TilemCalcEmulator* emu,
269 guint keyval,
270 GdkModifierType mods)
271 {
272 int i;
273
274 for (i = 0; i < emu->nkeybindings; i++)
275 if (keyval == emu->keybindings[i].keysym
276 && mods == emu->keybindings[i].modifiers)
277 return &emu->keybindings[i];
278
279 return NULL;
280 }
281
282 /* Find key binding matching the given event */
find_key_binding(TilemCalcEmulator * emu,GdkEventKey * event)283 static TilemKeyBinding* find_key_binding(TilemCalcEmulator* emu,
284 GdkEventKey* event)
285 {
286 GdkDisplay *dpy;
287 GdkKeymap *km;
288 guint keyval;
289 GdkModifierType consumed, mods;
290 TilemKeyBinding *kb;
291
292 dpy = gdk_drawable_get_display(event->window);
293 km = gdk_keymap_get_for_display(dpy);
294
295 /* determine the relevant set of modifiers */
296
297 gdk_keymap_translate_keyboard_state(km, event->hardware_keycode,
298 event->state, event->group,
299 &keyval, NULL, NULL, &consumed);
300
301 mods = (event->state & ~consumed
302 & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK));
303
304 if (event->state & GDK_LOCK_MASK
305 && (kb = find_key_binding_for_keysym(emu, keyval,
306 mods | GDK_LOCK_MASK))) {
307 return kb;
308 }
309
310 return find_key_binding_for_keysym(emu, keyval, mods);
311 }
312
313 /* Key-press event */
key_press_event(G_GNUC_UNUSED GtkWidget * w,GdkEventKey * event,gpointer data)314 gboolean key_press_event(G_GNUC_UNUSED GtkWidget* w, GdkEventKey* event,
315 gpointer data)
316 {
317 TilemEmulatorWindow *ewin = data;
318 TilemKeyBinding *kb;
319 int i, key;
320 unsigned int hwkey;
321
322 /* Ignore repeating keys */
323 for (i = 0; i < 64; i++)
324 if (ewin->keypress_keycodes[i] == event->hardware_keycode)
325 return TRUE;
326 if (ewin->sequence_keycode == event->hardware_keycode)
327 return TRUE;
328
329 if (!(kb = find_key_binding(ewin->emu, event)))
330 return FALSE;
331
332 hwkey = event->hardware_keycode;
333
334 if (kb->nscancodes == 1) {
335 /* if queue is empty, just press the key; otherwise
336 add it to the queue */
337 key = kb->scancodes[0];
338 if (tilem_calc_emulator_press_or_queue(ewin->emu, key))
339 ewin->sequence_keycode = hwkey;
340 else
341 ewin->keypress_keycodes[key] = hwkey;
342 }
343 else {
344 tilem_calc_emulator_queue_keys(ewin->emu, kb->scancodes,
345 kb->nscancodes);
346 ewin->sequence_keycode = hwkey;
347 }
348
349 return TRUE;
350 }
351
352 /* Key-release event */
key_release_event(G_GNUC_UNUSED GtkWidget * w,GdkEventKey * event,gpointer data)353 gboolean key_release_event(G_GNUC_UNUSED GtkWidget* w, GdkEventKey* event,
354 gpointer data)
355 {
356 TilemEmulatorWindow *ewin = data;
357 int i;
358
359 /* Check if the key that was just released was one that
360 activated a calculator keypress. (Do not try to look up
361 event->keyval; modifiers may have changed since the key was
362 pressed.) */
363 for (i = 0; i < 64; i++) {
364 if (ewin->keypress_keycodes[i] == event->hardware_keycode) {
365 tilem_calc_emulator_release_key(ewin->emu, i);
366 ewin->keypress_keycodes[i] = 0;
367 }
368 }
369
370 if (ewin->sequence_keycode == event->hardware_keycode) {
371 tilem_calc_emulator_release_queued_key(ewin->emu);
372 ewin->sequence_keycode = 0;
373 }
374
375 return FALSE;
376 }
377
place_menu(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer data)378 static void place_menu(GtkMenu *menu, gint *x, gint *y,
379 gboolean *push_in, gpointer data)
380 {
381 GtkWidget *w = data;
382 GdkWindow *win;
383 GdkScreen *screen;
384 int n;
385
386 win = gtk_widget_get_window(w);
387 gdk_window_get_origin(win, x, y);
388
389 screen = gdk_drawable_get_screen(win);
390 n = gdk_screen_get_monitor_at_point(screen, *x, *y);
391 gtk_menu_set_monitor(menu, n);
392
393 *push_in = FALSE;
394 }
395
396 /* Pop up menu on main window */
popup_menu_event(GtkWidget * w,gpointer data)397 gboolean popup_menu_event(GtkWidget* w, gpointer data)
398 {
399 TilemEmulatorWindow *ewin = data;
400
401 gtk_menu_popup(GTK_MENU(ewin->popup_menu),
402 NULL, NULL, &place_menu, w,
403 0, gtk_get_current_event_time());
404
405 return TRUE;
406 }
407
408 /* Callback function for the drag and drop event */
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)409 void drag_data_received(G_GNUC_UNUSED GtkWidget *win,
410 G_GNUC_UNUSED GdkDragContext *dc,
411 G_GNUC_UNUSED gint x, G_GNUC_UNUSED gint y,
412 GtkSelectionData *seldata,
413 G_GNUC_UNUSED guint info, G_GNUC_UNUSED guint t,
414 gpointer data)
415 {
416 TilemEmulatorWindow *ewin = data;
417 gchar **uris, **filenames;
418 gint i, j, n;
419
420 uris = gtk_selection_data_get_uris(seldata);
421 if (!uris)
422 return;
423
424 n = g_strv_length(uris);
425 filenames = g_new0(gchar *, n + 1);
426
427 for (i = j = 0; i < n; i++) {
428 filenames[j] = g_filename_from_uri(uris[i], NULL, NULL);
429 if (filenames[j])
430 j++;
431 }
432 filenames[j] = NULL;
433
434 load_files(ewin, filenames);
435 g_strfreev(filenames);
436 }
437