/* Hey EMACS -*- linux-c -*- */ /* TiEmu - Tiemu Is an EMUlator * * Copyright (c) 2000-2001, Thomas Corvazier, Romain Lievin * Copyright (c) 2001-2003, Romain Lievin * Copyright (c) 2003, Julien Blache * Copyright (c) 2004, Romain Liévin * Copyright (c) 2005, Romain Liévin * Copyright (c) 2005, Julien Blache * Copyright (c) 2006-2010, Kevin Kofler * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H # include #endif /* */ #ifdef __WIN32__ #include #endif #include #include #include #include #include #if !GTK_CHECK_VERSION(2,18,0) #define gtk_widget_get_state(wid) (GTK_WIDGET_STATE((wid))) #endif #include "intl.h" #include "paths.h" #include "skinops.h" #include "support.h" #include "ti68k_int.h" #include "struct.h" #include "tie_error.h" #include "calc.h" #include "dbg_all.h" #include "screenshot.h" #include "keymap.h" #include "logging.h" #include "gscales.h" #define ALLOW_RESIZE_WIN32 GtkWidget *main_wnd = NULL; gboolean explicit_destroy = 0; GtkWidget *area = NULL; SKIN_INFOS skin_infos = { 0 }; extern GdkPixbuf* lcd_mem; extern GdkPixbuf* lcd; extern GdkPixmap* pixmap; extern Pc2TiKey* kbd_keymap; extern const char* skn_keymap; extern const char sknKey92[]; extern const char sknKey89[]; extern uint32_t* lcd_bytmap; extern LCD_INFOS li; extern float sf; // scaling factor extern LCD_RECT ls; extern LCD_RECT lr; extern SKN_RECT sr; extern WND_RECT wr; static guint tid = -1; extern int shot_cnt; extern int skip_cnt; // part 1: set scale factor static void set_scale(int view_mode) { if(view_mode == VIEW_NORMAL) { options.scale = sf = 1.0; } else if(view_mode == VIEW_LARGE) { options.scale = sf = 2.0; } else if(view_mode == VIEW_FULL) { GdkScreen* screen = gdk_screen_get_default(); gint sw = gdk_screen_get_width(screen); gint sh = gdk_screen_get_height(screen); sf = (float)sw / lr.w; sf = (float)sh / lr.h; //printf("%i %i %f\n", sw, lr.w, sf); //printf("%i %i %f\n", sh, lr.h, sf); options.scale = sf = (float)1.0; // restricted to 3.0, too CPU intensive ! } } // part 2: compute sizes static void set_infos(void) // set window & lcd sizes { // LCD rectangle (source: skin) ls.x = (int)(sf * skin_infos.lcd_pos.left); ls.y = (int)(sf * skin_infos.lcd_pos.top); ls.w = (int)(sf * tihw.lcd_w); ls.h = (int)(sf * tihw.lcd_h); // LCD rectangle (target: window) if(options.skin) { lr.x = ls.x; lr.y = ls.y; } else { lr.x = 0; lr.y = 0; } lr.w = (int)(sf * tihw.lcd_w); lr.h = (int)(sf * tihw.lcd_h); // SKN rectangle sr.x = sr.y = 0; sr.w = (int)(sf * skin_infos.width); sr.h = (int)(sf * skin_infos.height); // WND rectangle (= LCD or SKN depending on w/ or w/o skin) wr.x = wr.y = 0; if(options.skin) { wr.w = sr.w; wr.h = sr.h; } else { wr.w = lr.w; wr.h = lr.h; } #if 0 printf("LCD src: %3i %3i %3i %3i\n", ls.x, ls.y, ls.w, ls.h); printf("LCD dst: %3i %3i %3i %3i\n", lr.x, lr.y, lr.w, lr.h); printf("SKN : %3i %3i %3i %3i\n", sr.x, sr.y, sr.w, sr.h); printf("WND : %3i %3i %3i %3i\n", wr.x, wr.y, wr.w, wr.h); #endif } // part 3: set changes on window static void set_window(int full_redraw) { if(main_wnd->window == NULL) return; // resize window and drawing area if(full_redraw) gtk_window_resize(GTK_WINDOW(main_wnd), wr.w, wr.h); #if defined(__WIN32__) && defined(ALLOW_RESIZE_WIN32) if(!full_redraw) gdk_window_resize(main_wnd->window, wr.w, wr.h); #endif // reallocate backing pixmap if(pixmap != NULL) { // free current backing pixmap g_object_unref(pixmap); pixmap = NULL; // and allocate a new one pixmap = gdk_pixmap_new(main_wnd->window, wr.w, wr.h, -1); if(pixmap == NULL) { gchar *s = g_strdup_printf("unable to create backing pixmap.\n"); tiemu_error(0, s); g_free(s); return; } } } static void set_constraints(void) { // Allows resizing of window with a constant aspect ratio. // This is the right way as used under Linux. // Does not work under Windows thus not enabled. #if !defined(__WIN32__) || !defined(ALLOW_RESIZE_WIN32) if(1) { GdkGeometry geom = { -1 }; GdkWindowHints mask = GDK_HINT_MIN_SIZE | GDK_HINT_ASPECT; double r = (float)wr.w / wr.h; geom.min_width = 100; geom.min_height = 100; geom.min_aspect = r; geom.max_aspect = r; gtk_window_set_geometry_hints(GTK_WINDOW(main_wnd), area, &geom, mask); //printf("set_constraints: %i %i %1.2f\n", wr.w, wr.h, r); } #endif } // Main wnd by loading glade xml file or by executing glade generated code gint display_main_wnd(void) { GladeXML *xml; gchar *title; xml = glade_xml_new (tilp_paths_build_glade("calc-2.glade"), "calc_wnd", PACKAGE); if (!xml) g_error(_("%s: GUI loading failed!\n"), __FILE__); glade_xml_signal_autoconnect(xml); main_wnd = glade_xml_get_widget(xml, "calc_wnd"); area = glade_xml_get_widget(xml, "drawingarea1"); gtk_window_move(GTK_WINDOW(main_wnd), options3.calc.rect.x, options3.calc.rect.y); set_constraints(); gtk_widget_realize(main_wnd); // set drawing area valid // set window title (useful for TIGCC-IDE for instance) // Note: lpWindowName is "TiEmu (%s)" and lpClassName is "gdkWindowToplevel" title = g_strdup_printf("TiEmu (%s)", ti68k_calctype_to_string(tihw.calc_type)); gtk_window_set_title(GTK_WINDOW(main_wnd), title); g_free(title); return 0; } extern void on_exit_without_saving_state1_activate(GtkMenuItem* item, gpointer data); GLADE_CB void on_calc_wnd_destroy (GtkObject *object, gpointer user_data) { // Uninstall LCD refresh (to avoid earlier use of main_wnd by hid_lcd_update) g_source_remove(tid); // When GTK called this signal, the widget has already been destroy // thus set the pointer to a valid value, ie NULL . main_wnd = NULL; if(!explicit_destroy) on_exit_without_saving_state1_activate(NULL, NULL); } extern void redraw_skin(void); GLADE_CB gboolean on_drawingarea1_configure_event (GtkWidget *widget, GdkEventConfigure *event, gpointer user_data) { float factor; // compute scaling factor if(options.skin) factor = (float)event->width / (float)skin_infos.width; else factor = (float)event->width / (float)tihw.lcd_w; #if 0 printf("on_drawingarea1_configure_event: x y w h = %i %i %i %i\n", event->x, event->y, event->width, event->height); printf("on_drawingarea1_configure_event: f = %1.2f\n", factor); #endif // if normal or large view then exits if((factor == 1.0) || (factor == 2.0)) return FALSE; // set scaling factor options.scale = sf = factor; options.view = VIEW_CUSTOM; // compute sizes set_infos(); // and set window size set_window(0); redraw_skin(); return FALSE; } GLADE_CB gboolean on_drawingarea1_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { gdk_draw_pixmap( widget->window, widget->style->fg_gc[gtk_widget_get_state(widget)], pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return FALSE; } static int match_skin(int calc_type) { SKIN_INFOS *sk = &skin_infos; int ok; gchar *skin_name, *s; s = g_strdup(ti68k_calctype_to_string(calc_type)); skin_name = g_ascii_strdown(s, strlen(s)); g_free(s); if(!strcmp(skin_name, "ti92+")) skin_name[4] = '\0'; #ifdef __IPAQ__ s = g_strconcat("ipaq_", skin_name, NULL); g_free(skin_name); skin_name = s; #endif // filename is "", load default skin if(!strcmp(g_basename(options.skin_file), "")) { g_free(options.skin_file); options.skin_file = g_strdup_printf("%s%s.skn", inst_paths.skin_dir, skin_name); g_free(skin_name); return -1; } // load skin header if(skin_read_header(sk, options.skin_file) == -1) { g_free(options.skin_file); options.skin_file = g_strdup_printf("%s%s.skn", inst_paths.skin_dir, skin_name); g_free(skin_name); return -1; } // is skin compatible switch(tihw.calc_type) { case TI92: case TI92p: ok = !strcmp((const char *)sk->calc, SKIN_TI92) || !strcmp((const char *)sk->calc, SKIN_TI92P); break; case TI89: ok = !strcmp((const char *)sk->calc, SKIN_TI89); break; case TI89t: ok = !strcmp((const char *)sk->calc, SKIN_TI89T); break; case V200: ok = !strcmp((const char *)sk->calc, SKIN_V200); break; default: ok = 0; break; } if(!ok) { g_free(options.skin_file); options.skin_file = g_strdup_printf("%s%s.skn", inst_paths.skin_dir, skin_name); //tiemu_error(0, _("skin incompatible with the current calc model. Falling back to default skin.")); g_free(skin_name); return -1; } g_free(skin_name); return 0; } static int match_keymap(int calc_type) { gchar *keys_name, *s; int ct, ok; s = g_strdup(ti68k_calctype_to_string(calc_type)); keys_name = g_ascii_strdown(s, strlen(s)); if(!strcmp(keys_name, "ti92+") || !strcmp(keys_name, "ti89t")) keys_name[4] = '\0'; if(!strcmp(keys_name, "v200plt")) strcpy(keys_name, "ti92"); // filename is "", load default keymap if(!strcmp(g_basename(options.keys_file), "")) { g_free(options.keys_file); options.keys_file = g_strdup_printf("%s%s.map", inst_paths.skin_dir, keys_name); } // load keymap header ct = keymap_read_header(options.keys_file); if(ct == -1) { g_free(options.keys_file); options.keys_file = g_strdup_printf("%s%s.map", inst_paths.skin_dir, keys_name); g_free(keys_name); return -1; } // is keymap compatible switch(tihw.calc_type) { case TI92: case TI92p: case V200: ok = (ct == TI92) || (ct == TI92p) || (ct == V200); break; case TI89: case TI89t: ok = (ct == TI89) || (ct == TI89t); break; default: ok = 0; break; } if(!ok) { g_free(options.keys_file); options.keys_file = g_strdup_printf("%s%s.map", inst_paths.skin_dir, keys_name); //tiemu_error(0, _("keymap incompatible with the current calc model. Falling back to default keymap.")); g_free(keys_name); return -1; } g_free(keys_name); return 0; } G_LOCK_EXTERN(lcd_flag); extern volatile int lcd_flag; extern volatile int debugger; static gint hid_refresh (gpointer data) { if(lcd_flag || (tihw.hw_type >= HW2)) { // TI92+: jackycar, TI89: baballe hid_update_lcd(); G_LOCK(lcd_flag); lcd_flag = 0; G_UNLOCK(lcd_flag); if(tihw.hw_type >= HW2) lcd_hook_hw2(TRUE); } return TRUE; } void compute_convtable(void); void compute_grayscale(void); extern void dnd_init(void); extern void dnd_exit(void); int hid_init(void) { // Found a PC keyboard keymap match_keymap(tihw.calc_type); // Load kbd keymap if(keymap_load(options.keys_file) == -1) { gchar *s = g_strdup_printf("unable to load this keymap: <%s>\n", options.keys_file); tiemu_error(0, s); g_free(s); return -1; } // Found a skin match_skin(tihw.calc_type); // Load skin (2 parts) if(skin_load(&skin_infos, options.skin_file) == -1) { gchar *s = g_strdup_printf("unable to load this skin: <%s>\n", options.skin_file); tiemu_error(0, s); g_free(s); return -1; } // Set skin keymap depending on calculator type switch(tihw.calc_type) { case TI92: case TI92p: case V200: skn_keymap = sknKey92; break; case TI89: case TI89t: skn_keymap = sknKey89; break; default: { gchar *s = g_strdup_printf("no skin found for this calc\n"); tiemu_error(0, s); g_free(s); return -1; } } // Set window/LCD sizes sf = options.scale; set_scale(options.view); set_infos(); // Allocate the TI screen buffer lcd_bytmap = (uint32_t *)malloc(LCDMEM_W * LCDMEM_H); // Allocate the lcd pixbuf lcd_mem = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, LCDMEM_W, LCDMEM_H); if(lcd_mem == NULL) { gchar *s = g_strdup_printf("unable to create LCD pixbuf.\n"); tiemu_error(0, s); g_free(s); return -1; } // Used by TI89 (the LCD view is clipped from memory view) lcd = gdk_pixbuf_new_subpixbuf(lcd_mem, 0, 0, tihw.lcd_w, tihw.lcd_h); // Constants for LCD update (speed-up) li.n_channels = gdk_pixbuf_get_n_channels (lcd_mem); li.width = gdk_pixbuf_get_width (lcd_mem); li.height = gdk_pixbuf_get_height (lcd_mem); li.rowstride = gdk_pixbuf_get_rowstride (lcd_mem); li.pixels = gdk_pixbuf_get_pixels (lcd_mem); // Create main window display_main_wnd(); // Allocate the backing pixmap (used for drawing and refresh) pixmap = gdk_pixmap_new(main_wnd->window, wr.w, wr.h, -1); if(pixmap == NULL) { gchar *s = g_strdup_printf("unable to create backing pixmap.\n"); tiemu_error(0, s); g_free(s); return -1; } // Draw the skin and compute grayscale palette set_window(1); redraw_skin(); compute_grayscale(); // Init the planar/chunky conversion table for LCD compute_convtable(); // Install LCD refresh: 100 FPS (10 ms) tid = g_timeout_add((params.lcd_rate == -1) ? 50 : params.lcd_rate, (GtkFunction)hid_refresh, NULL); explicit_destroy = 0; gtk_widget_show(main_wnd); // show wnd here if(options.view == VIEW_FULL) gdk_window_fullscreen(main_wnd->window); lcd_planes[0] = tihw.lcd_adr; lcd_planebufs[0] = &tihw.ram[tihw.lcd_adr]; ngc = 1; lcd_changed = 1; dnd_init(); return 0; } int hid_exit(void) { // Uninstall LCD refresh g_source_remove(tid); // Release resources if(lcd_mem != NULL) { g_object_unref(lcd_mem); lcd_mem = NULL; g_object_unref(lcd); lcd = NULL; } if(pixmap != NULL) { g_object_unref(pixmap); pixmap = NULL; } // Destroy window if(main_wnd) { explicit_destroy = !0; gtk_widget_destroy(main_wnd); } return 0; } void hid_lcd_rate_set(void) { g_source_remove(tid); tid = g_timeout_add((params.lcd_rate == -1) ? 50 : params.lcd_rate, (GtkFunction)hid_refresh, NULL); } int hid_switch_with_skin(void) { options.skin = 1; set_infos(); set_constraints(); set_window(1); redraw_skin(); return 0; } int hid_switch_without_skin(void) { options.skin = 0; set_infos(); set_constraints(); set_window(1); redraw_skin(); return 0; } int hid_change_skin(const char *filename) { int ret1, ret2; ret1 = hid_exit(); ret2 = hid_init(); return ret1 | ret2; } int hid_switch_fullscreen(void) { if(options.view != VIEW_FULL) { set_scale(options.view = VIEW_FULL); set_infos(); set_window(1); redraw_skin(); gdk_window_fullscreen(main_wnd->window); } return 0; } int hid_switch_normal_view(void) { if(options.view != VIEW_NORMAL) { set_scale(options.view = VIEW_NORMAL); set_infos(); set_window(1); redraw_skin(); gdk_window_unfullscreen(main_wnd->window); } return 0; } int hid_switch_large_view(void) { if(options.view != VIEW_LARGE) { set_scale(options.view = VIEW_LARGE); set_infos(); set_window(1); redraw_skin(); gdk_window_unfullscreen(main_wnd->window); } return 0; } int hid_screenshot_burst(void) { shot_cnt = options2.shots; skip_cnt = options2.skips; return 0; } int hid_screenshot_single(void) { gchar *outfile; gchar *ext = ""; gchar *type = ""; GdkPixbuf *pixbuf = { 0 }; gboolean result = FALSE; GError *error = NULL; switch(options2.format) { case IMG_JPG: ext = "jpg"; type = "jpeg"; break; case IMG_PNG: ext = "png"; type = "png"; break; case IMG_ICO: ext = "ico"; type = "ico"; break; case IMG_EPS: ext = "eps"; type = "eps"; break; case IMG_PDF: ext = "pdf"; type = "pdf"; break; case IMG_BMP: ext = "bmp"; type = "bmp"; break; default: ext = "png"; type = "png"; break; } outfile = g_strdup_printf("%s%s%s%03i.%s", options2.folder, G_DIR_SEPARATOR_S, options2.file, options2.counter, ext); tiemu_info(_("screenshot to %s... "), outfile); if((options2.size == IMG_LCD) && (options2.type == IMG_BW)) { // get pixbuf from TI memory (LCD buffer) pixbuf = hid_copy_lcd(); } else if((options2.size == IMG_LCD) && (options2.type == IMG_COL)) { // get pixbuf from grayscale lcd pixbuf = gdk_pixbuf_copy(lcd); } else if((options2.size == IMG_SKIN) && (options2.type == IMG_COL)) { // get pixbuf from backing pixmap pixbuf = gdk_pixbuf_get_from_drawable(NULL, pixmap, NULL, 0, 0, 0, 0, wr.w, wr.h); } else { tiemu_warning(_("unsupported screenshot options combination, screenshot aborted.")); return 0; } switch (options2.format) { case IMG_EPS: result = tiemu_screen_write_eps(outfile, pixbuf, &error); break; case IMG_PDF: result = tiemu_screen_write_pdf(outfile, pixbuf, &error); break; default: result = gdk_pixbuf_save(pixbuf, outfile, type, &error, NULL); break; } if(options2.clipboard) { GtkClipboard *clipboard; clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_image(clipboard, pixbuf); } if (result == FALSE) { tiemu_warning(_("failed to save pixbuf file: %s: %s"), outfile, error->message); g_error_free(error); } g_object_unref(pixbuf); tiemu_info(_("done!")); options2.counter++; return 0; } GLADE_CB gboolean on_calc_wnd_window_state_event (GtkWidget *widget, GdkEvent *event, gpointer user_data) { return FALSE; }