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