1 /*
2  * This is a plug-in for GIMP.
3  *
4  * Generates clickable image maps.
5  *
6  * Copyright (C) 1998-2006 Maurits Rijk  m.rijk@chello.nl
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "config.h"
23 
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include <glib/gstdio.h>
29 
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h> /* for keyboard values */
32 
33 #include <libgimp/gimp.h>
34 #include <libgimp/gimpui.h>
35 
36 #include "imap_about.h"
37 #include "imap_circle.h"
38 #include "imap_commands.h"
39 #include "imap_default_dialog.h"
40 #include "imap_edit_area_info.h"
41 #include "imap_file.h"
42 #include "imap_main.h"
43 #include "imap_menu.h"
44 #include "imap_misc.h"
45 #include "imap_object.h"
46 #include "imap_polygon.h"
47 #include "imap_preview.h"
48 #include "imap_rectangle.h"
49 #include "imap_selection.h"
50 #include "imap_settings.h"
51 #include "imap_source.h"
52 #include "imap_statusbar.h"
53 #include "imap_stock.h"
54 #include "imap_string.h"
55 
56 #include "libgimp/stdplugins-intl.h"
57 
58 
59 #define MAX_ZOOM_FACTOR 8
60 #define ZOOMED(x) (_zoom_factor * (x))
61 #define GET_REAL_COORD(x) ((x) / _zoom_factor)
62 
63 static gint             zoom_in         (void);
64 static gint             zoom_out        (void);
65 
66 
67 /* Global variables */
68 static MapInfo_t   _map_info;
69 static PreferencesData_t _preferences = {CSIM, TRUE, FALSE, TRUE, TRUE, FALSE,
70 FALSE, TRUE, DEFAULT_UNDO_LEVELS, DEFAULT_MRU_SIZE};
71 static MRU_t *_mru;
72 
73 static gint32        _drawable_id;
74 static GdkCursorType _cursor = GDK_TOP_LEFT_ARROW;
75 static gboolean     _show_url = TRUE;
76 static gchar       *_filename = NULL;
77 static gchar       *_image_name;
78 static gint        _image_width;
79 static gint        _image_height;
80 static GtkWidget   *_dlg;
81 static Preview_t   *_preview;
82 static Selection_t *_selection;
83 static StatusBar_t *_statusbar;
84 static ObjectList_t *_shapes;
85 static gint         _zoom_factor = 1;
86 static gboolean (*_button_press_func)(GtkWidget*, GdkEventButton*, gpointer);
87 static gpointer _button_press_param;
88 
89 /* Declare local functions. */
90 static void  query  (void);
91 static void  run    (const gchar      *name,
92                      gint              nparams,
93                      const GimpParam  *param,
94                      gint             *nreturn_vals,
95                      GimpParam       **return_vals);
96 static gint  dialog (gint32            drawable_id);
97 
98 const GimpPlugInInfo PLUG_IN_INFO = {
99    NULL,                        /* init_proc */
100    NULL,                        /* quit_proc */
101    query,                       /* query_proc */
102    run,                         /* run_proc */
103 };
104 
105 static int run_flag = 0;
106 
107 
MAIN()108 MAIN ()
109 
110 static void query(void)
111 {
112    static const GimpParamDef args[] = {
113       {GIMP_PDB_INT32,    "run-mode", "The run mode { RUN-INTERACTIVE (0) }"},
114       {GIMP_PDB_IMAGE,    "image",    "Input image (unused)"},
115       {GIMP_PDB_DRAWABLE, "drawable", "Input drawable"},
116    };
117    static const GimpParamDef *return_vals = NULL;
118    static int nreturn_vals = 0;
119 
120    gimp_install_procedure(PLUG_IN_PROC,
121                           N_("Create a clickable imagemap"),
122                           "",
123                           "Maurits Rijk",
124                           "Maurits Rijk",
125                           "1998-2005",
126                           N_("_Image Map..."),
127                           "RGB*, GRAY*, INDEXED*",
128                           GIMP_PLUGIN,
129                           G_N_ELEMENTS (args), nreturn_vals,
130                           args, return_vals);
131 
132    gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Web");
133 }
134 
135 static void
run(const gchar * name,gint n_params,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)136 run (const gchar      *name,
137      gint              n_params,
138      const GimpParam  *param,
139      gint             *nreturn_vals,
140      GimpParam       **return_vals)
141 {
142    static GimpParam values[1];
143    GimpRunMode run_mode;
144    gint32      drawable_id;
145    GimpPDBStatusType status = GIMP_PDB_SUCCESS;
146 
147    INIT_I18N ();
148    gegl_init (NULL, NULL);
149 
150    *nreturn_vals = 1;
151    *return_vals = values;
152 
153    run_mode    = param[0].data.d_int32;
154    drawable_id = param[2].data.d_drawable;
155 
156    _drawable_id = drawable_id;
157    _image_name = gimp_image_get_name(param[1].data.d_image);
158    _image_width = gimp_image_width(param[1].data.d_image);
159    _image_height = gimp_image_height(param[1].data.d_image);
160 
161    _map_info.color = gimp_drawable_is_rgb(drawable_id);
162 
163    if (run_mode == GIMP_RUN_INTERACTIVE) {
164       if (!dialog(drawable_id)) {
165          /* The dialog was closed, or something similarly evil happened. */
166          status = GIMP_PDB_EXECUTION_ERROR;
167       }
168    }
169 
170    values[0].type = GIMP_PDB_STATUS;
171    values[0].data.d_status = status;
172 }
173 
174 GtkWidget*
get_dialog(void)175 get_dialog(void)
176 {
177   return _dlg;
178 }
179 
180 MRU_t*
get_mru(void)181 get_mru(void)
182 {
183    if (!_mru)
184       _mru = mru_create();
185    return _mru;
186 }
187 
188 MapInfo_t*
get_map_info(void)189 get_map_info(void)
190 {
191    return &_map_info;
192 }
193 
194 PreferencesData_t*
get_preferences(void)195 get_preferences(void)
196 {
197    return &_preferences;
198 }
199 
200 static void
init_preferences(void)201 init_preferences(void)
202 {
203    ColorSelData_t *colors = &_preferences.colors;
204 
205    colors->normal_fg.red = 0;
206    colors->normal_fg.green = 0xFFFF;
207    colors->normal_fg.blue = 0;
208 
209    colors->normal_bg.red = 0;
210    colors->normal_bg.green = 0;
211    colors->normal_bg.blue = 0xFFFF;
212 
213    colors->selected_fg.red = 0xFFFF;
214    colors->selected_fg.green = 0;
215    colors->selected_fg.blue = 0;
216 
217    colors->selected_bg.red = 0;
218    colors->selected_bg.green = 0;
219    colors->selected_bg.blue = 0xFFFF;
220 
221    colors->interactive_fg.red = 0xFFFF;
222    colors->interactive_fg.green = 0;
223    colors->interactive_fg.blue = 0xFFFF;
224 
225    colors->interactive_bg.red = 0xFFFF;
226    colors->interactive_bg.green = 0xFFFF;
227    colors->interactive_bg.blue = 0;
228 
229    preferences_load(&_preferences);
230 
231    mru_set_size(_mru, _preferences.mru_size);
232    command_list_set_undo_level(_preferences.undo_levels);
233 }
234 
235 gint
get_image_width(void)236 get_image_width(void)
237 {
238    return _image_width;
239 }
240 
241 gint
get_image_height(void)242 get_image_height(void)
243 {
244    return _image_height;
245 }
246 
247 void
set_busy_cursor(void)248 set_busy_cursor(void)
249 {
250    preview_set_cursor(_preview, GDK_WATCH);
251 }
252 
253 void
remove_busy_cursor(void)254 remove_busy_cursor(void)
255 {
256   gdk_window_set_cursor(gtk_widget_get_window (_dlg), NULL);
257 }
258 
259 static gint
zoom_in(void)260 zoom_in(void)
261 {
262    if (_zoom_factor < MAX_ZOOM_FACTOR) {
263       set_zoom(_zoom_factor + 1);
264       menu_set_zoom(_zoom_factor);
265    }
266    return _zoom_factor;
267 }
268 
269 static gint
zoom_out(void)270 zoom_out(void)
271 {
272    if (_zoom_factor > 1) {
273       set_zoom(_zoom_factor - 1);
274       menu_set_zoom(_zoom_factor);
275    }
276    return _zoom_factor;
277 }
278 
279 void
set_zoom(gint zoom_factor)280 set_zoom(gint zoom_factor)
281 {
282    set_busy_cursor();
283    _zoom_factor = zoom_factor;
284    preview_zoom(_preview, zoom_factor);
285    statusbar_set_zoom(_statusbar, zoom_factor);
286    remove_busy_cursor();
287 }
288 
289 gint
get_real_coord(gint coord)290 get_real_coord(gint coord)
291 {
292    return GET_REAL_COORD(coord);
293 }
294 
295 void
draw_line(cairo_t * cr,gint x1,gint y1,gint x2,gint y2)296 draw_line(cairo_t *cr, gint x1, gint y1, gint x2, gint y2)
297 {
298    cairo_move_to (cr, ZOOMED (x1) + .5, ZOOMED (y1) + .5);
299    cairo_line_to (cr, ZOOMED (x2) + .5, ZOOMED (y2) + .5);
300    cairo_stroke (cr);
301 }
302 
303 void
draw_rectangle(cairo_t * cr,gboolean filled,gint x,gint y,gint width,gint height)304 draw_rectangle(cairo_t *cr, gboolean filled, gint x, gint y,
305                gint width, gint height)
306 {
307    cairo_rectangle (cr, ZOOMED (x) + (filled ? 0. : .5),
308                         ZOOMED (y) + (filled ? 0. : .5),
309                     ZOOMED (width), ZOOMED (height));
310    if (filled)
311       cairo_fill (cr);
312    else
313       cairo_stroke (cr);
314 }
315 
316 void
draw_circle(cairo_t * cr,gint x,gint y,gint r)317 draw_circle(cairo_t *cr, gint x, gint y, gint r)
318 {
319    cairo_arc (cr, ZOOMED (x), ZOOMED (y), ZOOMED (r), 0., 2 * G_PI);
320    cairo_stroke (cr);
321 }
322 
323 void
draw_polygon(cairo_t * cr,GList * list)324 draw_polygon(cairo_t *cr, GList *list)
325 {
326    GList     *p;
327 
328    for (p = list; p; p = p->next) {
329       GdkPoint *src = (GdkPoint*) p->data;
330       cairo_line_to (cr, ZOOMED (src->x) + .5, ZOOMED (src->y) + .5);
331    }
332    cairo_close_path (cr);
333    cairo_stroke (cr);
334 }
335 
336 void
set_preview_color(GtkRadioAction * action,GtkRadioAction * current,gpointer user_data)337 set_preview_color (GtkRadioAction *action, GtkRadioAction *current,
338                    gpointer user_data)
339 {
340   _map_info.show_gray = (gtk_radio_action_get_current_value (current) == 1);
341    set_zoom(_zoom_factor);
342 }
343 
344 void
preview_redraw(void)345 preview_redraw(void)
346 {
347   gtk_widget_queue_draw(_preview->preview);
348 }
349 
350 void
set_zoom_factor(GtkRadioAction * action,GtkRadioAction * current,gpointer user_data)351 set_zoom_factor (GtkRadioAction *action, GtkRadioAction *current,
352                  gpointer user_data)
353 {
354   gint factor = gtk_radio_action_get_current_value (current);
355   set_zoom (factor + 1);
356 }
357 
358 const gchar *
get_image_name(void)359 get_image_name(void)
360 {
361    return _image_name;
362 }
363 
364 const char*
get_filename(void)365 get_filename(void)
366 {
367    return _filename;
368 }
369 
370 static gboolean
arrow_on_button_press(GtkWidget * widget,GdkEventButton * event,gpointer data)371 arrow_on_button_press (GtkWidget      *widget,
372                        GdkEventButton *event,
373                        gpointer        data)
374 {
375   if (gdk_event_triggers_context_menu ((GdkEvent *) event))
376     {
377       do_popup_menu (event);
378     }
379   else if (event->button == 1)
380     {
381       if (event->type == GDK_2BUTTON_PRESS)
382         edit_shape((gint) event->x, (gint) event->y);
383       else
384         select_shape(widget, event);
385     }
386 
387   return FALSE;
388 }
389 
390 static void
set_arrow_func(void)391 set_arrow_func(void)
392 {
393    _button_press_func = arrow_on_button_press;
394    _cursor = GDK_TOP_LEFT_ARROW;
395 }
396 
397 static void
set_object_func(gboolean (* func)(GtkWidget *,GdkEventButton *,gpointer),gpointer param)398 set_object_func(gboolean (*func)(GtkWidget*, GdkEventButton*,
399                                  gpointer), gpointer param)
400 {
401    _button_press_func = func;
402    _button_press_param = param;
403    _cursor = GDK_CROSSHAIR;
404 }
405 
406 void
set_func(GtkRadioAction * action,GtkRadioAction * current,gpointer user_data)407 set_func(GtkRadioAction *action, GtkRadioAction *current,
408          gpointer user_data)
409 {
410   gint value = gtk_radio_action_get_current_value (current);
411   switch (value)
412     {
413     case 0:
414       set_arrow_func();
415       break;
416     case 1:
417       set_object_func(object_on_button_press, get_rectangle_factory);
418       break;
419     case 2:
420       set_object_func(object_on_button_press, get_circle_factory);
421       break;
422     case 3:
423       set_object_func(object_on_button_press, get_polygon_factory);
424       break;
425     default:
426       break;
427     }
428 }
429 
430 void
add_shape(Object_t * obj)431 add_shape(Object_t *obj)
432 {
433    object_list_append(_shapes, obj);
434 }
435 
436 ObjectList_t*
get_shapes(void)437 get_shapes(void)
438 {
439    return _shapes;
440 }
441 
442 void
update_shape(Object_t * obj)443 update_shape(Object_t *obj)
444 {
445    object_list_update(_shapes, obj);
446 }
447 
448 void
do_edit_selected_shape(void)449 do_edit_selected_shape(void)
450 {
451    object_list_edit_selected(_shapes);
452 }
453 
454 void
do_popup_menu(GdkEventButton * event)455 do_popup_menu(GdkEventButton *event)
456 {
457    gint x = GET_REAL_COORD((gint) event->x);
458    gint y = GET_REAL_COORD((gint) event->y);
459    Object_t *obj = object_list_find(_shapes, x, y);
460    if (obj) {
461       obj->class->do_popup(obj, event);
462    } else {
463       do_main_popup_menu(event);
464    }
465 }
466 
467 static void
set_all_sensitivities(void)468 set_all_sensitivities(void)
469 {
470    gint count = object_list_nr_selected(_shapes);
471    menu_shapes_selected(count);
472 }
473 
474 static void
main_set_title(const char * filename)475 main_set_title(const char *filename)
476 {
477    char *title, *p;
478 
479    g_strreplace(&_filename, filename);
480    p = filename ? g_filename_display_basename (filename) : (gchar *) _("<Untitled>");
481    title = g_strdup_printf("%s - Image Map", p);
482    if (filename)
483      g_free (p);
484    gtk_window_set_title(GTK_WINDOW(_dlg), title);
485    g_free(title);
486 }
487 
488 void
main_set_dimension(gint width,gint height)489 main_set_dimension(gint width, gint height)
490 {
491    statusbar_set_dimension(_statusbar,
492                            width / _zoom_factor, height / _zoom_factor);
493 }
494 
495 void
main_clear_dimension(void)496 main_clear_dimension(void)
497 {
498    statusbar_clear_dimension(_statusbar);
499 }
500 
501 void
show_url(void)502 show_url(void)
503 {
504    _show_url = TRUE;
505 }
506 
507 void
hide_url(void)508 hide_url(void)
509 {
510    _show_url = FALSE;
511    statusbar_clear_status(_statusbar);
512 }
513 
514 void
select_shape(GtkWidget * widget,GdkEventButton * event)515 select_shape(GtkWidget *widget, GdkEventButton *event)
516 {
517    Object_t *obj;
518    gint x = GET_REAL_COORD((gint) event->x);
519    gint y = GET_REAL_COORD((gint) event->y);
520    MoveSashFunc_t sash_func;
521 
522    obj = object_list_near_sash(_shapes, x, y, &sash_func);
523    if (obj) {                   /* Start resizing */
524       Command_t *command = move_sash_command_new(widget, obj, x, y, sash_func);
525       command_execute(command);
526    } else {
527       Command_t *command;
528 
529       obj = object_list_find(_shapes, x, y);
530       if (obj) {
531          if (event->state & GDK_SHIFT_MASK) {
532             if (obj->selected)
533                command = unselect_command_new(obj);
534             else
535                command = select_command_new(obj);
536          } else {               /* No Shift key pressed */
537             if (obj->selected) {
538                command = unselect_all_command_new(_shapes, obj);
539             } else {
540                Command_t *sub_command;
541 
542                command = subcommand_start(NULL);
543                sub_command = unselect_all_command_new(_shapes, NULL);
544                command_add_subcommand(command, sub_command);
545                sub_command = select_command_new(obj);
546                command_add_subcommand(command, sub_command);
547                command_set_name(command, sub_command->name);
548                subcommand_end();
549             }
550          }
551          command_execute(command);
552 
553          command = move_command_new(_preview, obj, x, y);
554          command_execute(command);
555       } else { /* Start selection rectangle */
556          command = select_region_command_new(widget, _shapes, x, y);
557          command_execute(command);
558       }
559    }
560 }
561 
562 void
edit_shape(gint x,gint y)563 edit_shape(gint x, gint y)
564 {
565    Object_t *obj;
566 
567    x = GET_REAL_COORD(x);
568    y = GET_REAL_COORD(y);
569 
570    obj = object_list_find(_shapes, x, y);
571    if (obj) {
572       object_select(obj);
573       object_edit(obj, TRUE);
574    }
575 }
576 
577 void
do_zoom_in(void)578 do_zoom_in(void)
579 {
580    gint factor = zoom_in();
581    menu_set_zoom_sensitivity(factor);
582 }
583 
584 void
do_zoom_out(void)585 do_zoom_out(void)
586 {
587    gint factor = zoom_out();
588    menu_set_zoom_sensitivity(factor);
589 }
590 
591 void
draw_shapes(cairo_t * cr)592 draw_shapes(cairo_t *cr)
593 {
594    object_list_draw(_shapes, cr);
595 }
596 
597 static void
clear_map_info(void)598 clear_map_info(void)
599 {
600    const gchar *author = g_get_real_name();
601 
602    if (!*author)
603       author = g_get_user_name();
604    g_strreplace(&_map_info.image_name, _image_name);
605    g_strreplace(&_map_info.title, "map");
606    g_strreplace(&_map_info.author, author);
607    g_strreplace(&_map_info.default_url, "");
608    g_strreplace(&_map_info.description, "");
609 
610    _map_info.map_format = _preferences.default_map_type;
611    _map_info.show_gray = FALSE;
612 }
613 
614 static void
do_data_changed_dialog(void (* continue_cb)(gpointer),gpointer param)615 do_data_changed_dialog(void (*continue_cb)(gpointer), gpointer param)
616 {
617    GtkWidget *dialog = gtk_message_dialog_new
618      (NULL,
619       GTK_DIALOG_DESTROY_WITH_PARENT,
620       GTK_MESSAGE_QUESTION,
621       GTK_BUTTONS_YES_NO,
622       _("Some data has been changed!"));
623    gtk_message_dialog_format_secondary_text
624      (GTK_MESSAGE_DIALOG (dialog),
625       _("Do you really want to discard your changes?"));
626 
627    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES)
628      continue_cb (param);
629 
630    gtk_widget_destroy (dialog);
631 }
632 
633 static void
check_if_changed(void (* func)(gpointer),gpointer param)634 check_if_changed(void (*func)(gpointer), gpointer param)
635 {
636    if (object_list_get_changed (_shapes))
637      do_data_changed_dialog (func, param);
638    else
639      func (param);
640 }
641 
642 static void
close_current(void)643 close_current(void)
644 {
645    selection_freeze(_selection);
646    object_list_remove_all(_shapes);
647    selection_thaw(_selection);
648    clear_map_info();
649    main_set_title(NULL);
650    set_all_sensitivities();
651    preview_redraw();
652    object_list_clear_changed(_shapes);
653    command_list_remove_all();
654 }
655 
656 static void
really_close(gpointer data)657 really_close(gpointer data)
658 {
659    close_current();
660 }
661 
662 void
do_close(void)663 do_close(void)
664 {
665    check_if_changed(really_close, NULL);
666 }
667 
668 static void
really_quit(gpointer data)669 really_quit(gpointer data)
670 {
671    preferences_save(&_preferences);
672    run_flag = 1;
673    gtk_widget_destroy(_dlg);
674 }
675 
676 void
do_quit(void)677 do_quit(void)
678 {
679    check_if_changed(really_quit, NULL);
680 }
681 
682 void
do_undo(void)683 do_undo(void)
684 {
685    selection_freeze(_selection);
686    last_command_undo();
687    selection_thaw(_selection);
688    preview_redraw();
689 }
690 
691 void
do_redo(void)692 do_redo(void)
693 {
694    selection_freeze(_selection);
695    last_command_redo();
696    selection_thaw(_selection);
697    preview_redraw();
698 }
699 
700 void
save(void)701 save(void)
702 {
703    if (_filename)
704       save_as(_filename);
705    else
706       do_file_save_as_dialog();
707 }
708 
709 static void
write_cern_comment(gpointer param,OutputFunc_t output)710 write_cern_comment(gpointer param, OutputFunc_t output)
711 {
712    output(param, "rect (4096,4096) (4096,4096) imap:#$");
713 }
714 
715 static void
save_as_cern(gpointer param,OutputFunc_t output)716 save_as_cern(gpointer param, OutputFunc_t output)
717 {
718    char *p;
719    gchar *description;
720    gchar *next_token;
721 
722    write_cern_comment(param, output);
723    output(param, "-:Image map file created by GIMP Image Map plug-in\n");
724    write_cern_comment(param, output);
725    output(param, "-:GIMP Image Map plug-in by Maurits Rijk\n");
726    write_cern_comment(param, output);
727    output(param, "-:Please do not edit lines starting with \"#$\"\n");
728    write_cern_comment(param, output);
729    output(param, "VERSION:2.3\n");
730    write_cern_comment(param, output);
731    output(param, "TITLE:%s\n", _map_info.title);
732    write_cern_comment(param, output);
733    output(param, "AUTHOR:%s\n", _map_info.author);
734    write_cern_comment(param, output);
735    output(param, "FORMAT:cern\n");
736 
737    description = g_strdup(_map_info.description);
738    next_token = description;
739    for (p = strtok (next_token, "\n"); p; p = strtok(NULL, "\n")) {
740       write_cern_comment(param, output);
741       output(param, "DESCRIPTION:%s\n", p);
742    }
743    g_free(description);
744 
745    if (*_map_info.default_url)
746       output(param, "default %s\n", _map_info.default_url);
747    object_list_write_cern(_shapes, param, output);
748 }
749 
750 static void
save_as_csim(gpointer param,OutputFunc_t output)751 save_as_csim(gpointer param, OutputFunc_t output)
752 {
753    char *p;
754    gchar *description;
755 
756    output(param, "<img src=\"%s\" width=\"%d\" height=\"%d\" border=\"0\" "
757           "usemap=\"#%s\" />\n\n", _map_info.image_name,
758           _image_width, _image_height, _map_info.title);
759    output(param, "<map name=\"%s\">\n", _map_info.title);
760    output(param,
761           "<!-- #$-:Image map file created by GIMP Image Map plug-in -->\n");
762    output(param, "<!-- #$-:GIMP Image Map plug-in by Maurits Rijk -->\n");
763    output(param,
764           "<!-- #$-:Please do not edit lines starting with \"#$\" -->\n");
765    output(param, "<!-- #$VERSION:2.3 -->\n");
766    output(param, "<!-- #$AUTHOR:%s -->\n", _map_info.author);
767 
768    description = g_strdup(_map_info.description);
769    for (p = strtok(description, "\n"); p; p = strtok(NULL, "\n"))
770       output(param, "<!-- #$DESCRIPTION:%s -->\n", p);
771    g_free(description);
772 
773    object_list_write_csim(_shapes, param, output);
774    if (*_map_info.default_url)
775       output(param, "<area shape=\"default\" href=\"%s\" />\n",
776              _map_info.default_url);
777    output(param, "</map>\n");
778 }
779 
780 static void
save_as_ncsa(gpointer param,OutputFunc_t output)781 save_as_ncsa(gpointer param, OutputFunc_t output)
782 {
783    char *p;
784    gchar *description;
785 
786    output(param, "#$-:Image map file created by GIMP Image Map plug-in\n");
787    output(param, "#$-:GIMP Image Map plug-in by Maurits Rijk\n");
788    output(param, "#$-:Please do not edit lines starting with \"#$\"\n");
789    output(param, "#$VERSION:2.3\n");
790    output(param, "#$TITLE:%s\n", _map_info.title);
791    output(param, "#$AUTHOR:%s\n", _map_info.author);
792    output(param, "#$FORMAT:ncsa\n");
793 
794    description = g_strdup(_map_info.description);
795    for (p = strtok(description, "\n"); p; p = strtok(NULL, "\n"))
796       output(param, "#$DESCRIPTION:%s\n", p);
797    g_free(description);
798 
799    if (*_map_info.default_url)
800       output(param, "default %s\n", _map_info.default_url);
801    object_list_write_ncsa(_shapes, param, output);
802 }
803 
804 static void   save_to_file (gpointer    param,
805                             const char *format,
806                             ...) G_GNUC_PRINTF(2,3);
807 
808 static void
save_to_file(gpointer param,const char * format,...)809 save_to_file(gpointer param, const char* format, ...)
810 {
811    va_list ap;
812 
813    va_start(ap, format);
814    vfprintf((FILE*)param, format, ap);
815    va_end(ap);
816 }
817 
818 void
dump_output(gpointer param,OutputFunc_t output)819 dump_output(gpointer param, OutputFunc_t output)
820 {
821    if (_map_info.map_format == NCSA)
822       save_as_ncsa(param, output);
823    else if (_map_info.map_format == CERN)
824       save_as_cern(param, output);
825    else if (_map_info.map_format == CSIM)
826       save_as_csim(param, output);
827 }
828 
829 void
save_as(const gchar * filename)830 save_as(const gchar *filename)
831 {
832    FILE *out = g_fopen(filename, "w");
833    if (out) {
834       dump_output(out, save_to_file);
835       fclose(out);
836 
837       statusbar_set_status(_statusbar, _("File \"%s\" saved."), filename);
838       main_set_title(filename);
839       object_list_clear_changed(_shapes);
840    } else {
841       do_file_error_dialog( _("Couldn't save file:"), filename);
842    }
843 }
844 
845 static void
do_image_size_changed_dialog(void)846 do_image_size_changed_dialog(void)
847 {
848    GtkWidget *dialog = gtk_message_dialog_new_with_markup
849      (NULL,
850       GTK_DIALOG_DESTROY_WITH_PARENT,
851       GTK_MESSAGE_QUESTION,
852       GTK_BUTTONS_YES_NO,
853       "<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s",
854       _("Image size has changed."),
855       _("Resize area's?"));
856 
857    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES)
858      {
859        gint per_x = _image_width * 100 / _map_info.old_image_width;
860        gint per_y = _image_height * 100 / _map_info.old_image_height;
861        object_list_resize(_shapes, per_x, per_y);
862      }
863 
864    preview_redraw();
865    gtk_widget_destroy (dialog);
866 }
867 
868 static void
really_load(gpointer data)869 really_load(gpointer data)
870 {
871    gchar *filename = (gchar*) data;
872    close_current();
873 
874    selection_freeze(_selection);
875    _map_info.old_image_width = _image_width;
876    _map_info.old_image_height = _image_height;
877    if (load_csim(filename)) {
878       _map_info.map_format = CSIM;
879       if (_image_width != _map_info.old_image_width ||
880           _image_height != _map_info.old_image_height) {
881          do_image_size_changed_dialog();
882       }
883    } else if (load_ncsa(filename)) {
884       _map_info.map_format = NCSA;
885    } else if (load_cern(filename)) {
886       _map_info.map_format = CERN;
887    } else {
888       do_file_error_dialog( _("Couldn't read file:"), filename);
889       selection_thaw(_selection);
890       close_current();
891       return;
892    }
893    mru_set_first(_mru, filename);
894    menu_build_mru_items(_mru);
895 
896    selection_thaw(_selection);
897    main_set_title(filename);
898    object_list_clear_changed(_shapes);
899    preview_redraw();
900 }
901 
902 void
load(const gchar * filename)903 load(const gchar *filename)
904 {
905    static gchar *tmp_filename;
906    g_strreplace(&tmp_filename, filename);
907    check_if_changed(really_load, (gpointer) tmp_filename);
908 }
909 
910 void
toggle_area_list(void)911 toggle_area_list(void)
912 {
913    selection_toggle_visibility(_selection);
914 }
915 
916 static gboolean
close_callback(GtkWidget * widget,gpointer data)917 close_callback(GtkWidget *widget, gpointer data)
918 {
919    do_quit();
920    return TRUE;
921 }
922 
923 static gboolean
preview_move(GtkWidget * widget,GdkEventMotion * event)924 preview_move(GtkWidget *widget, GdkEventMotion *event)
925 {
926    gint x = GET_REAL_COORD((gint) event->x);
927    gint y = GET_REAL_COORD((gint) event->y);
928    static Object_t *prev_obj = NULL;
929    Object_t *obj = object_list_find(_shapes, x, y);
930 
931    statusbar_set_xy(_statusbar, x, y);
932    if (obj != prev_obj) {
933       prev_obj = obj;
934       if (obj && _show_url) {
935          statusbar_set_status(_statusbar, _("URL: %s"), obj->url);
936       } else {
937          statusbar_clear_status(_statusbar);
938       }
939    }
940 #ifdef _NOT_READY_YET_
941    if (!obj) {
942       if (grid_near_x(x)) {
943          preview_set_cursor(_preview, GDK_SB_H_DOUBLE_ARROW);
944       } else if (grid_near_y(y)) {
945          preview_set_cursor(_preview, GDK_SB_V_DOUBLE_ARROW);
946       } else {
947          preview_set_cursor(_preview, _cursor);
948       }
949    }
950 #endif
951    return FALSE;
952 }
953 
954 static void
preview_enter(GtkWidget * widget,GdkEventCrossing * event)955 preview_enter(GtkWidget *widget, GdkEventCrossing *event)
956 {
957    preview_set_cursor(_preview, _cursor);
958 }
959 
960 static void
preview_leave(GtkWidget * widget,GdkEventCrossing * event)961 preview_leave(GtkWidget *widget, GdkEventCrossing *event)
962 {
963   gdk_window_set_cursor(gtk_widget_get_window (_dlg), NULL);
964   statusbar_clear_xy(_statusbar);
965 }
966 
967 static gboolean
button_press(GtkWidget * widget,GdkEventButton * event,gpointer data)968 button_press(GtkWidget* widget, GdkEventButton* event, gpointer data)
969 {
970   if (_button_press_func)
971     return _button_press_func(widget, event, _button_press_param);
972 
973   return FALSE;
974 }
975 
976 /* A few global vars for key movement */
977 
978 static guint _timeout;
979 static guint _keyval;
980 static gint _dx, _dy;
981 
982 static void
move_sash_selected_objects(gint dx,gint dy,gboolean fast)983 move_sash_selected_objects(gint dx, gint dy, gboolean fast)
984 {
985    if (fast) {
986       dx *= 5;
987       dy *= 5;
988    }
989 
990    object_list_move_sash_selected(_shapes, dx, dy);
991 
992    preview_redraw ();
993 }
994 
995 static void
move_selected_objects(gint dx,gint dy,gboolean fast)996 move_selected_objects(gint dx, gint dy, gboolean fast)
997 {
998    if (fast) {
999       dx *= 5;
1000       dy *= 5;
1001    }
1002    _dx += dx;
1003    _dy += dy;
1004 
1005    object_list_move_selected(_shapes, dx, dy);
1006 
1007    preview_redraw ();
1008 }
1009 
1010 static gboolean
key_timeout_cb(gpointer data)1011 key_timeout_cb(gpointer data)
1012 {
1013    switch (_keyval) {
1014    case GDK_KEY_Left:
1015    case GDK_KEY_Right:
1016    case GDK_KEY_Up:
1017    case GDK_KEY_Down:
1018       command_list_add(move_selected_command_new(_shapes, _dx, _dy));
1019       _dx = _dy = 0;
1020       break;
1021    }
1022    preview_redraw();
1023 
1024    _timeout = 0;
1025    return FALSE;
1026 }
1027 
1028 static gboolean
key_press_cb(GtkWidget * widget,GdkEventKey * event)1029 key_press_cb(GtkWidget *widget, GdkEventKey *event)
1030 {
1031    gboolean handled = FALSE;
1032    gboolean shift = event->state & GDK_SHIFT_MASK;
1033    gboolean ctrl = event->state & GDK_CONTROL_MASK;
1034    Command_t *command;
1035 
1036    if (_timeout)
1037       g_source_remove(_timeout);
1038    _timeout = 0;
1039 
1040    switch (event->keyval) {
1041    case GDK_KEY_Left:
1042       if (ctrl)
1043          move_sash_selected_objects(-1, 0, shift);
1044       else
1045          move_selected_objects(-1, 0, shift);
1046       handled = TRUE;
1047       break;
1048    case GDK_KEY_Right:
1049       if (ctrl)
1050          move_sash_selected_objects(1, 0, shift);
1051       else
1052          move_selected_objects(1, 0, shift);
1053       handled = TRUE;
1054       break;
1055    case GDK_KEY_Up:
1056       if (ctrl)
1057          move_sash_selected_objects(0, -1, shift);
1058       else
1059          move_selected_objects(0, -1, shift);
1060       handled = TRUE;
1061       break;
1062    case GDK_KEY_Down:
1063       if (ctrl)
1064          move_sash_selected_objects(0, 1, shift);
1065       else
1066          move_selected_objects(0, 1, shift);
1067       handled = TRUE;
1068       break;
1069    case GDK_KEY_Tab:
1070       if (shift)
1071          command = select_prev_command_new(_shapes);
1072       else
1073          command = select_next_command_new(_shapes);
1074       command_execute(command);
1075       handled = TRUE;
1076       break;
1077    }
1078    if (handled)
1079       g_signal_stop_emission_by_name(widget, "key-press-event");
1080 
1081    return handled;
1082 }
1083 
1084 static gboolean
key_release_cb(GtkWidget * widget,GdkEventKey * event)1085 key_release_cb(GtkWidget *widget, GdkEventKey *event)
1086 {
1087    _keyval = event->keyval;
1088    _timeout = g_timeout_add(250, key_timeout_cb, NULL);
1089    return FALSE;
1090 }
1091 
1092 static void
geometry_changed(Object_t * obj,gpointer data)1093 geometry_changed(Object_t *obj, gpointer data)
1094 {
1095    preview_redraw();
1096 }
1097 
1098 static void
data_changed(Object_t * obj,gpointer data)1099 data_changed(Object_t *obj, gpointer data)
1100 {
1101    preview_redraw();
1102    set_all_sensitivities();
1103 }
1104 
1105 static void
data_selected(Object_t * obj,gpointer data)1106 data_selected(Object_t *obj, gpointer data)
1107 {
1108    set_all_sensitivities();
1109 }
1110 
1111 void
imap_help(void)1112 imap_help (void)
1113 {
1114   gimp_standard_help_func ("plug-in-imagemap", NULL);
1115 }
1116 
1117 void
do_cut(void)1118 do_cut (void)
1119 {
1120   command_execute (cut_command_new (_shapes));
1121 }
1122 
1123 void
do_copy(void)1124 do_copy (void)
1125 {
1126   command_execute (copy_command_new (_shapes));
1127 }
1128 
1129 void
do_paste(void)1130 do_paste (void)
1131 {
1132   command_execute (paste_command_new (_shapes));
1133 }
1134 
1135 void
do_select_all(void)1136 do_select_all(void)
1137 {
1138   command_execute (select_all_command_new (_shapes));
1139 }
1140 
1141 void
do_deselect_all(void)1142 do_deselect_all(void)
1143 {
1144   command_execute (unselect_all_command_new (_shapes, NULL));
1145 }
1146 
1147 void
do_clear(void)1148 do_clear(void)
1149 {
1150   command_execute (clear_command_new(_shapes));
1151 }
1152 
1153 void
do_move_up(void)1154 do_move_up(void)
1155 {
1156   /* Fix me!
1157    Command_t *command = object_up_command_new(_current_obj->list,
1158                                               _current_obj);
1159    command_execute(command);
1160   */
1161 }
1162 
1163 void
do_move_down(void)1164 do_move_down(void)
1165 {
1166   /* Fix me!
1167    Command_t *command = object_down_command_new(_current_obj->list,
1168                                                 _current_obj);
1169    command_execute(command);
1170   */
1171 }
1172 
1173 void
do_move_to_front(void)1174 do_move_to_front(void)
1175 {
1176   command_execute(move_to_front_command_new(_shapes));
1177 }
1178 
1179 void
do_send_to_back(void)1180 do_send_to_back(void)
1181 {
1182   command_execute(send_to_back_command_new(_shapes));
1183 }
1184 
1185 void
do_use_gimp_guides_dialog(void)1186 do_use_gimp_guides_dialog(void)
1187 {
1188   command_execute (gimp_guides_command_new (_shapes, _drawable_id));
1189 }
1190 
1191 void
do_create_guides_dialog(void)1192 do_create_guides_dialog(void)
1193 {
1194   command_execute (guides_command_new (_shapes));
1195 }
1196 
1197 static Command_t*
factory_move_up(void)1198 factory_move_up(void)
1199 {
1200    return move_up_command_new(_shapes);
1201 }
1202 
1203 static Command_t*
factory_move_down(void)1204 factory_move_down(void)
1205 {
1206    return move_down_command_new(_shapes);
1207 }
1208 
1209 static gint
dialog(gint32 drawable_id)1210 dialog(gint32 drawable_id)
1211 {
1212    GtkWidget    *dlg;
1213    GtkWidget    *hbox;
1214    GtkWidget    *main_vbox;
1215    GtkWidget    *tools;
1216 
1217    gimp_ui_init (PLUG_IN_BINARY, TRUE);
1218 
1219    set_arrow_func ();
1220 
1221    _shapes = make_object_list();
1222 
1223    _dlg = dlg = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1224    gtk_window_set_resizable(GTK_WINDOW(dlg), TRUE);
1225 
1226    main_set_title(NULL);
1227    gimp_help_connect (dlg, gimp_standard_help_func, PLUG_IN_PROC, NULL);
1228 
1229    gtk_window_set_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
1230 
1231    gimp_window_set_transient (GTK_WINDOW (dlg));
1232 
1233    g_signal_connect (dlg, "delete-event",
1234                      G_CALLBACK (close_callback), NULL);
1235    g_signal_connect (dlg, "key-press-event",
1236                      G_CALLBACK (key_press_cb), NULL);
1237    g_signal_connect (dlg, "key-release-event",
1238                      G_CALLBACK (key_release_cb), NULL);
1239 
1240    g_signal_connect (dlg, "destroy",
1241                      G_CALLBACK (gtk_main_quit),
1242                      NULL);
1243 
1244    main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1245    gtk_container_add (GTK_CONTAINER (dlg), main_vbox);
1246    gtk_widget_show (main_vbox);
1247 
1248    init_stock_icons();
1249 
1250    /* Create menu */
1251    make_menu(main_vbox, dlg);
1252 
1253    /* Create toolbar */
1254    make_toolbar(main_vbox, dlg);
1255 
1256    /*  Dialog area  */
1257    hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 1);
1258    gtk_box_pack_start (GTK_BOX (main_vbox), hbox, TRUE, TRUE, 0);
1259    gtk_widget_show(hbox);
1260 
1261    tools = make_tools(dlg);
1262    /* selection_set_edit_command(tools, factory_edit); */
1263    gtk_box_pack_start(GTK_BOX(hbox), tools, FALSE, FALSE, 0);
1264 
1265    _preview = make_preview(drawable_id);
1266 
1267    g_signal_connect(_preview->preview, "motion-notify-event",
1268                     G_CALLBACK(preview_move), NULL);
1269    g_signal_connect(_preview->preview, "enter-notify-event",
1270                     G_CALLBACK(preview_enter), NULL);
1271    g_signal_connect(_preview->preview, "leave-notify-event",
1272                     G_CALLBACK(preview_leave), NULL);
1273    g_signal_connect(_preview->preview, "button-press-event",
1274                     G_CALLBACK(button_press), NULL);
1275    gtk_box_pack_start (GTK_BOX (hbox), _preview->window, TRUE, TRUE, 0);
1276 
1277    object_list_add_geometry_cb(_shapes, geometry_changed, NULL);
1278    object_list_add_update_cb(_shapes, data_changed, NULL);
1279    object_list_add_add_cb(_shapes, data_changed, NULL);
1280    object_list_add_remove_cb(_shapes, data_changed, NULL);
1281    object_list_add_move_cb(_shapes, data_changed, NULL);
1282    object_list_add_select_cb(_shapes, data_selected, NULL);
1283 
1284    /* Selection */
1285    _selection = make_selection(_shapes);
1286    selection_set_move_up_command(_selection, factory_move_up);
1287    selection_set_move_down_command(_selection, factory_move_down);
1288    gtk_box_pack_start(GTK_BOX(hbox), _selection->container, FALSE, FALSE, 0);
1289 
1290    _statusbar = make_statusbar(main_vbox, dlg);
1291    statusbar_set_zoom(_statusbar, 1);
1292 
1293    gtk_widget_show(dlg);
1294 
1295    _mru = mru_create();
1296    init_preferences();
1297 
1298    clear_map_info();
1299 
1300    if (!mru_empty(_mru))
1301       menu_build_mru_items(_mru);
1302 
1303    gtk_main();
1304 
1305    return run_flag;
1306 }
1307