David Capello 19e8083256 + Added Editor::space_pressed to move the scroll with the space key.
+ Renamed Editor::cursor_eyedropper to Editor::alt_pressed.
+ Added statusbar_show_tip.
+ Added AppHooks.
+ Renamed color_button_* to colorbutton_*
+ Renamed GfxObj*Layer::parent to Layer*Layer::parent_layer.
+ Added Palette gfxobj and refactored a lot of code to this new Palette
  instead of Allegro's RGB/PALETTE.
+ Now jfile.c uses jxml.c.
+ New signature for callbacks in 'hook_signal' of modules/gui.c:
  bool hook(JWidget, void *);
- Removed colsel and minipal from dialogs.
+ Fixed mouse bounds in sliders.
2008-03-22 18:43:56 +00:00

863 lines
20 KiB
C

/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2008 David A. Capello
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include <assert.h>
#include <allegro.h>
#include <allegro/internal/aintern.h>
#ifdef ALLEGRO_WINDOWS
#include <winalleg.h>
#endif
#include "jinete/jinete.h"
#include "jinete/jintern.h"
#include "commands/commands.h"
#include "console/console.h"
#include "core/app.h"
#include "core/cfg.h"
#include "core/core.h"
#include "core/dirs.h"
#include "dialogs/options.h"
#include "dialogs/filmedit.h"
#include "intl/msgids.h"
#include "modules/editors.h"
#include "modules/gfx.h"
#include "modules/gui.h"
#include "modules/palettes.h"
#include "modules/rootmenu.h"
#include "modules/sprites.h"
#include "raster/sprite.h"
#include "script/script.h"
#include "util/recscr.h"
#include "widgets/editor.h"
#include "widgets/statebar.h"
#define REBUILD_RECENT_LIST 2
#define REFRESH_FULL_SCREEN 4
/**************************************************************/
#ifdef ALLEGRO_WINDOWS
# define DEF_SCALE 2
#else
# define DEF_SCALE 1
#endif
static struct
{
int width;
int height;
int scale;
} try_resolutions[] = { { 1024, 768, DEF_SCALE },
{ 800, 600, DEF_SCALE },
{ 640, 480, DEF_SCALE },
{ 320, 240, 1 },
{ 320, 200, 1 },
{ 0, 0, 0 } };
static int try_depths[] = { 32, 24, 16, 15, 8 };
/**************************************************************/
struct Monitor
{
/* returns true when the job is done and the monitor can be removed */
void (*proc)(void *);
void (*free)(void *);
void *data;
bool lock : 1;
bool deleted : 1;
};
static JWidget manager = NULL;
static int monitor_timer = -1;
static JList monitors;
static bool ji_screen_created = FALSE;
static volatile int next_idle_flags = 0;
static JList icon_buttons;
/* default GUI screen configuration */
static bool double_buffering;
static int screen_scaling;
static Monitor *monitor_new(void (*proc)(void *),
void (*free)(void *), void *data);
static void monitor_free(Monitor *monitor);
/* load & save graphics configuration */
static void load_gui_config(int *w, int *h, int *bpp, bool *fullscreen);
static void save_gui_config(void);
static bool button_with_icon_msg_proc(JWidget widget, JMessage msg);
static bool manager_msg_proc(JWidget widget, JMessage msg);
static void regen_theme_and_fixup_icons(void *data);
/**
* Used by set_display_switch_callback(SWITCH_IN, ...).
*/
static void display_switch_in_callback()
{
next_idle_flags |= REFRESH_FULL_SCREEN;
}
END_OF_STATIC_FUNCTION(display_switch_in_callback);
/**
* Initializes GUI.
*/
int init_module_gui(void)
{
int min_possible_dsk_res = 0;
int c, w, h, bpp, autodetect;
bool fullscreen;
/* install timer related stuff */
if (install_timer() < 0) {
user_printf(_("Error installing timer handler\n"));
return -1;
}
/* install the mouse */
if (install_mouse() < 0) {
user_printf(_("Error installing mouse handler\n"));
return -1;
}
/* install the keyboard */
if (install_keyboard() < 0) {
user_printf(_("Error installing keyboard handler\n"));
return -1;
}
/* disable Ctrl+Shift+End in non-DOS */
#if !defined(ALLEGRO_DOS)
three_finger_flag = FALSE;
#endif
three_finger_flag = TRUE; /* TODO remove this line */
/* set the graphics mode... */
load_gui_config(&w, &h, &bpp, &fullscreen);
autodetect = fullscreen ? GFX_AUTODETECT_FULLSCREEN:
GFX_AUTODETECT_WINDOWED;
/* default resolution */
if (!w || !h) {
bool has_desktop = FALSE;
int dsk_w, dsk_h;
has_desktop = get_desktop_resolution(&dsk_w, &dsk_h) == 0;
/* we must extract some space for the windows borders */
dsk_w -= 16;
dsk_h -= 32;
/* try to get desktop resolution */
if (has_desktop) {
for (c=0; try_resolutions[c].width; ++c) {
if (try_resolutions[c].width <= dsk_w &&
try_resolutions[c].height <= dsk_h) {
min_possible_dsk_res = c;
fullscreen = FALSE;
w = try_resolutions[c].width;
h = try_resolutions[c].height;
screen_scaling = try_resolutions[c].scale;
break;
}
}
}
/* full screen */
else {
fullscreen = TRUE;
w = 320;
h = 200;
screen_scaling = 1;
}
}
/* default color depth */
if (!bpp) {
bpp = desktop_color_depth();
if (!bpp)
bpp = 8;
}
for (;;) {
/* original */
set_color_depth(bpp);
if (set_gfx_mode(autodetect, w, h, 0, 0) == 0)
break;
for (c=min_possible_dsk_res; try_resolutions[c].width; ++c) {
if (set_gfx_mode(autodetect,
try_resolutions[c].width,
try_resolutions[c].height, 0, 0) == 0) {
screen_scaling = try_resolutions[c].scale;
goto gfx_done;
}
}
if (bpp == 8) {
user_printf(_("Error setting graphics mode\n%s\n"
"Try \"ase -res WIDTHxHEIGHTxBPP\"\n"), allegro_error);
return -1;
}
else {
for (c=0; try_depths[c]; ++c) {
if (bpp == try_depths[c]) {
bpp = try_depths[c+1];
break;
}
}
}
}
gfx_done:;
monitors = jlist_new();
/* window title */
set_window_title("Allegro Sprite Editor v" VERSION);
/* create the default-manager */
manager = jmanager_new();
jwidget_add_hook(manager, JI_WIDGET, manager_msg_proc, NULL);
/* setup the standard jinete theme for widgets */
ji_set_standard_theme();
/* set hook to translate strings */
ji_set_translation_hook((void *)msgids_get);
/* configure ji_screen */
gui_setup_screen();
/* add a hook to display-switch so when the user returns to the
screen it's completelly refreshed/redrawn */
LOCK_VARIABLE(next_idle_flags);
LOCK_FUNCTION(display_switch_in_callback);
set_display_switch_callback(SWITCH_IN, display_switch_in_callback);
/* set graphics options for next time */
save_gui_config();
/* load the font */
reload_default_font();
/* hook for palette change to regenerate the theme */
app_add_hook(APP_PALETTE_CHANGE, regen_theme_and_fixup_icons, NULL);
/* icon buttons */
icon_buttons = jlist_new();
/* setup mouse */
_setup_mouse_speed();
return 0;
}
void exit_module_gui(void)
{
JLink link;
/* destroy monitors */
JI_LIST_FOR_EACH(monitors, link) {
monitor_free(link->data);
}
jlist_free(monitors);
monitors = NULL;
if (double_buffering) {
BITMAP *old_bmp = ji_screen;
ji_set_screen(screen);
if (ji_screen_created)
destroy_bitmap(old_bmp);
ji_screen_created = FALSE;
}
jlist_free(icon_buttons);
icon_buttons = NULL;
jmanager_free(manager);
remove_keyboard();
remove_mouse();
remove_timer();
}
static Monitor *monitor_new(void (*proc)(void *),
void (*free)(void *), void *data)
{
Monitor *monitor = jnew(Monitor, 1);
if (!monitor)
return NULL;
monitor->proc = proc;
monitor->free = free;
monitor->data = data;
monitor->lock = FALSE;
monitor->deleted = FALSE;
return monitor;
}
static void monitor_free(Monitor *monitor)
{
if (monitor->free)
(*monitor->free)(monitor->data);
jfree(monitor);
}
static void load_gui_config(int *w, int *h, int *bpp, bool *fullscreen)
{
*w = get_config_int("GfxMode", "Width", 0);
*h = get_config_int("GfxMode", "Height", 0);
*bpp = get_config_int("GfxMode", "Depth", 0);
*fullscreen = get_config_bool("GfxMode", "FullScreen", FALSE);
screen_scaling = get_config_int("GfxMode", "Scale", 1);
screen_scaling = MID(1, screen_scaling, 4);
}
static void save_gui_config(void)
{
set_config_int("GfxMode", "Width", SCREEN_W);
set_config_int("GfxMode", "Height", SCREEN_H);
set_config_int("GfxMode", "Depth", bitmap_color_depth(screen));
set_config_bool("GfxMode", "FullScreen", gfx_driver->windowed ? FALSE: TRUE);
set_config_int("GfxMode", "Scale", screen_scaling);
}
int get_screen_scaling(void)
{
return screen_scaling;
}
void set_screen_scaling(int scaling)
{
screen_scaling = scaling;
}
void update_screen_for_sprite(Sprite *sprite)
{
if (!(ase_mode & MODE_GUI))
return;
/* without sprite */
if (!sprite) {
/* well, change to the default palette */
if (set_current_palette(NULL, FALSE)) {
/* if the palette changes, refresh the whole screen */
jmanager_refresh_screen();
}
}
/* with a sprite */
else {
/* select the palette of the sprite */
if (set_current_palette(sprite_get_palette(sprite, sprite->frame), FALSE)) {
/* if the palette changes, refresh the whole screen */
jmanager_refresh_screen();
}
else {
/* if it's the same palette update only the editors with the sprite */
update_editors_with_sprite(sprite);
}
}
statusbar_set_text(app_get_statusbar(), -1, "");
}
void gui_run(void)
{
jmanager_run(manager);
}
void gui_feedback(void)
{
/* menu stuff */
if (next_idle_flags & REBUILD_RECENT_LIST) {
if (app_realloc_recent_list())
next_idle_flags ^= REBUILD_RECENT_LIST;
}
if (next_idle_flags & REFRESH_FULL_SCREEN) {
next_idle_flags ^= REFRESH_FULL_SCREEN;
update_screen_for_sprite(current_sprite);
}
/* record file if is necessary */
rec_screen_poll();
/* double buffering? */
if (double_buffering) {
jmouse_draw_cursor();
if (ji_dirty_region) {
ji_flip_dirty_region();
}
else {
if (JI_SCREEN_W == SCREEN_W && JI_SCREEN_H == SCREEN_H) {
blit(ji_screen, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
}
else {
stretch_blit(ji_screen, screen,
0, 0, ji_screen->w, ji_screen->h,
0, 0, SCREEN_W, SCREEN_H);
}
}
}
}
/**
* Sets the ji_screen variable.
*
* This routine should be called everytime you changes the graphics
* mode.
*/
void gui_setup_screen(void)
{
/* double buffering is required when screen scaling is used */
double_buffering = (screen_scaling > 1);
/* is double buffering active */
if (double_buffering) {
BITMAP *old_bmp = ji_screen;
ji_set_screen(create_bitmap(SCREEN_W / screen_scaling,
SCREEN_H / screen_scaling));
if (ji_screen_created)
destroy_bitmap(old_bmp);
ji_screen_created = TRUE;
}
else {
ji_set_screen(screen);
ji_screen_created = FALSE;
}
reload_default_font();
/* set the configuration */
save_gui_config();
}
void reload_default_font(void)
{
JTheme theme = ji_get_theme();
const char *user_font;
DIRS *dirs, *dir;
char buf[512];
/* no font for now */
if (theme->default_font && theme->default_font != font)
destroy_font(theme->default_font);
theme->default_font = NULL;
/* directories */
dirs = dirs_new();
user_font = get_config_string("Options", "UserFont", "");
if ((user_font) && (*user_font))
dirs_add_path(dirs, user_font);
usprintf(buf, "fonts/ase%d.pcx", GUISCALE);
dirs_cat_dirs(dirs, filename_in_datadir(buf));
/* try to load the font */
for (dir=dirs; dir; dir=dir->next) {
theme->default_font = ji_font_load(dir->path);
if (theme->default_font) {
if (ji_font_is_scalable(theme->default_font))
ji_font_set_size(theme->default_font, 8*GUISCALE);
break;
}
}
dirs_free(dirs);
/* default font: the Allegro one */
if (!theme->default_font)
theme->default_font = font;
/* set all widgets fonts */
_ji_set_font_of_all_widgets(theme->default_font);
}
void load_window_pos(JWidget window, const char *section)
{
JRect pos, orig_pos;
/* default position */
orig_pos = jwidget_get_rect(window);
pos = jrect_new_copy(orig_pos);
/* load configurated position */
get_config_rect(section, "WindowPos", pos);
pos->x2 = pos->x1 + MID(jrect_w(orig_pos), jrect_w(pos), JI_SCREEN_W);
pos->y2 = pos->y1 + MID(jrect_h(orig_pos), jrect_h(pos), JI_SCREEN_H);
jrect_moveto(pos,
MID(0, pos->x1, JI_SCREEN_W-jrect_w(pos)),
MID(0, pos->y1, JI_SCREEN_H-jrect_h(pos)));
jwidget_set_rect(window, pos);
jrect_free(pos);
jrect_free(orig_pos);
}
void save_window_pos(JWidget window, const char *section)
{
set_config_rect(section, "WindowPos", window->rc);
}
JWidget load_widget(const char *filename, const char *name)
{
JWidget widget;
DIRS *it, *dirs;
char buf[512];
bool found = FALSE;
dirs = dirs_new();
usprintf(buf, "jids/%s", filename);
dirs_add_path(dirs, filename);
dirs_cat_dirs(dirs, filename_in_datadir(buf));
for (it=dirs; it; it=it->next) {
if (exists(it->path)) {
ustrcpy(buf, it->path);
found = TRUE;
break;
}
}
dirs_free(dirs);
if (!found) {
console_printf(_("File not found: \"%s\"\n"), filename);
return NULL;
}
widget = ji_load_widget(buf, name);
if (!widget)
console_printf(_("Error loading widget: \"%s\"\n"), name);
return widget;
}
void rebuild_recent_list(void)
{
next_idle_flags |= REBUILD_RECENT_LIST;
}
/**********************************************************************/
/* hook signals */
typedef struct HookData {
int signal_num;
bool (*signal_handler)(JWidget widget, void *data);
void *data;
} HookData;
static int hook_type(void)
{
static int type = 0;
if (!type)
type = ji_register_widget_type();
return type;
}
static bool hook_handler(JWidget widget, JMessage msg)
{
switch (msg->type) {
case JM_DESTROY:
jfree(jwidget_get_data(widget, hook_type()));
break;
case JM_SIGNAL: {
HookData *hook_data = jwidget_get_data(widget, hook_type());
if (hook_data->signal_num == msg->signal.num)
return (*hook_data->signal_handler)(widget, hook_data->data);
break;
}
}
return FALSE;
}
/**
* @warning You can't use this function for the same widget two times.
*/
void hook_signal(JWidget widget,
int signal_num,
bool (*signal_handler)(JWidget widget, void *data),
void *data)
{
HookData *hook_data = jnew(HookData, 1);
hook_data->signal_num = signal_num;
hook_data->signal_handler = signal_handler;
hook_data->data = data;
jwidget_add_hook(widget, hook_type(), hook_handler, hook_data);
}
/**
* Utility routine to get various widgets by name.
*
* @code
* if (!get_widgets(wnd,
* "name1", &widget1,
* "name2", &widget2,
* "name3", &widget3,
* NULL)) {
* ...
* }
* @endcode
*/
bool get_widgets(JWidget window, ...)
{
JWidget *widget;
char *name;
va_list ap;
va_start(ap, window);
while ((name = va_arg(ap, char *))) {
widget = va_arg(ap, JWidget *);
if (!widget)
break;
*widget = jwidget_find_name(window, name);
if (!*widget) {
console_printf(_("Widget %s not found.\n"), name);
return FALSE;
}
}
va_end(ap);
return TRUE;
}
/**********************************************************************/
/* Icon in buttons */
/* adds a button in the list of "icon_buttons" to restore the icon
when the palette changes, the "user_data[3]" is used to save the
"gfx_id" (the same ID that is used in "get_gfx()" from
"modules/gfx.c"), also this routine adds a hook to
JI_SIGNAL_DESTROY to remove the button from the "icon_buttons"
list when the widget is free */
void add_gfxicon_to_button(JWidget button, int gfx_id, int icon_align)
{
button->user_data[3] = (void *)gfx_id;
jwidget_add_hook(button, JI_WIDGET, button_with_icon_msg_proc, NULL);
ji_generic_button_set_icon(button, get_gfx(gfx_id));
ji_generic_button_set_icon_align(button, icon_align);
jlist_append(icon_buttons, button);
}
void set_gfxicon_in_button(JWidget button, int gfx_id)
{
button->user_data[3] = (void *)gfx_id;
ji_generic_button_set_icon(button, get_gfx(gfx_id));
jwidget_dirty(button);
}
static bool button_with_icon_msg_proc(JWidget widget, JMessage msg)
{
if (msg->type == JM_DESTROY)
jlist_remove(icon_buttons, widget);
return FALSE;
}
/**********************************************************************/
/* Button style (convert radio or check buttons and draw it like
normal buttons) */
JWidget radio_button_new(int radio_group, int b1, int b2, int b3, int b4)
{
JWidget widget = ji_generic_button_new(NULL, JI_RADIO, JI_BUTTON);
if (widget) {
jradio_set_group(widget, radio_group);
jbutton_set_bevel(widget, b1, b2, b3, b4);
}
return widget;
}
JWidget check_button_new(const char *text, int b1, int b2, int b3, int b4)
{
JWidget widget = ji_generic_button_new(text, JI_CHECK, JI_BUTTON);
if (widget) {
jbutton_set_bevel(widget, b1, b2, b3, b4);
}
return widget;
}
/**
* Adds a routine to be called each 100 milliseconds to monitor
* whatever you want. It's mainly used to monitor the progress of a
* file-operation (see @ref fop_operate)
*/
Monitor *add_gui_monitor(void (*proc)(void *),
void (*free)(void *), void *data)
{
Monitor *monitor = monitor_new(proc, free, data);
jlist_append(monitors, monitor);
if (monitor_timer < 0)
monitor_timer = jmanager_add_timer(manager, 100);
jmanager_start_timer(monitor_timer);
return monitor;
}
/**
* Removes a previously added monitor.
*/
void remove_gui_monitor(Monitor *monitor)
{
JLink link = jlist_find(monitors, monitor);
assert(link != NULL);
if (!monitor->lock)
monitor_free(monitor);
else
monitor->deleted = TRUE;
jlist_delete_link(monitors, link);
if (jlist_empty(monitors))
jmanager_stop_timer(monitor_timer);
}
/**********************************************************************/
/* manager event handler */
static bool manager_msg_proc(JWidget widget, JMessage msg)
{
switch (msg->type) {
case JM_QUEUEPROCESSING:
gui_feedback();
break;
case JM_TIMER:
if (msg->timer.timer_id == monitor_timer) {
JLink link, next;
JI_LIST_FOR_EACH_SAFE(monitors, link, next) {
Monitor *monitor = link->data;
/* is the monitor not lock? */
if (!monitor->lock) {
/* call the monitor procedure */
monitor->lock = TRUE;
(*monitor->proc)(monitor->data);
monitor->lock = FALSE;
if (monitor->deleted)
monitor_free(monitor);
}
}
/* is monitors empty? we can stop the timer so */
if (jlist_empty(monitors))
jmanager_stop_timer(monitor_timer);
}
break;
case JM_KEYPRESSED: {
Command *command = command_get_by_key(msg);
if (!command)
break;
/* the screen shot is available in everywhere */
if (strcmp(command->name, CMD_SCREEN_SHOT) == 0) {
if (command_is_enabled(command, NULL)) {
command_execute(command, NULL);
return TRUE;
}
}
/* all other keys are only available in the main-window */
else {
JWidget child;
JLink link;
JI_LIST_FOR_EACH(widget->children, link) {
child = link->data;
/* there are a foreground window executing? */
if (jwindow_is_foreground(child)) {
break;
}
/* is it the desktop and the top-window= */
else if (jwindow_is_desktop(child) && child == app_get_top_window()) {
/* ok, so we can execute the command represented by the
pressed-key in the message... */
if (command_is_enabled(command, NULL)) {
command_execute(command, NULL);
return TRUE;
}
break;
}
}
}
break;
}
}
return FALSE;
}
/**********************************************************************/
/* graphics */
static void regen_theme_and_fixup_icons(void *data)
{
JWidget button;
JLink link;
/* regenerate the theme */
ji_regen_theme();
/* fixup the icons */
JI_LIST_FOR_EACH(icon_buttons, link) {
button = link->data;
ji_generic_button_set_icon(button, get_gfx((int)button->user_data[3]));
}
}