Mon, 03 Aug 2009 14:09:20 +0100
added P-touch decoder source
1 /*
2 #
3 # File : gmic4gimp.cpp
4 # ( C++ source file )
5 #
6 # Description : G'MIC for GIMP - A plug-in to allow the use
7 # of G'MIC commands in GIMP.
8 # This file is a part of the CImg Library project.
9 # ( http://cimg.sourceforge.net )
10 #
11 # Copyright : David Tschumperle (GREYCstoration API)
12 #
13 # License : CeCILL v2.0
14 # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
15 #
16 # This software is governed by the CeCILL license under French law and
17 # abiding by the rules of distribution of free software. You can use,
18 # modify and/ or redistribute the software under the terms of the CeCILL
19 # license as circulated by CEA, CNRS and INRIA at the following URL
20 # "http://www.cecill.info".
21 #
22 # As a counterpart to the access to the source code and rights to copy,
23 # modify and redistribute granted by the license, users are provided only
24 # with a limited warranty and the software's author, the holder of the
25 # economic rights, and the successive licensors have only limited
26 # liability.
27 #
28 # In this respect, the user's attention is drawn to the risks associated
29 # with loading, using, modifying and/or developing or reproducing the
30 # software by the user in light of its specific status of free software,
31 # that may mean that it is complicated to manipulate, and that also
32 # therefore means that it is reserved for developers and experienced
33 # professionals having in-depth computer knowledge. Users are therefore
34 # encouraged to load and test the software's suitability as regards their
35 # requirements in conditions enabling the security of their systems and/or
36 # data to be ensured and, more generally, to use and operate it in the
37 # same conditions as regards security.
38 #
39 # The fact that you are presently reading this means that you have had
40 # knowledge of the CeCILL license and that you accept its terms.
41 #
42 */
44 // Include necessary header files.
45 //--------------------------------
46 #define cimg_display_type 0
47 #include "gmic.h"
48 #include "gmic4gimp_def.h"
49 #include <pthread.h>
50 #include <locale>
51 #include <gtk/gtk.h>
52 #include <libgimp/gimp.h>
53 #include <libgimp/gimpui.h>
54 using namespace cimg_library;
56 // Define plug-in global variables.
57 //---------------------------------
58 CImgList<char> gmic_entries; // The list of recognized G'MIC menu entries (stored as 'char*' strings).
59 CImgList<char> gmic_commands; // The list of corresponding G'MIC commands to process the image.
60 CImgList<char> gmic_preview_commands; // The list of corresponding G'MIC commands to preview the image.
61 CImgList<char> gmic_arguments; // The list of corresponding needed filter arguments.
62 char *gmic_macros; // The array of customized G'MIC macros.
63 GtkTreeStore *filter_store; // A list of available filter entries (used by the GtkTreeView).
64 bool return_create_dialog; // Return value of the 'create_gui_dialog()' function (set by events handlers).
65 void **event_infos; // Infos that are passed to the GUI callback functions.
66 char *path_home; // The path where configuration files are looked for.
68 // Replace '[]' by '()' in a C-string.
69 //------------------------------------
70 void strparenthesis(char *const s) {
71 for (char *ns = s; *ns; ++ns) if (*ns=='[') *ns = '('; else if (*ns==']') *ns = ')';
72 }
74 // Set/get plug-in global variables in GIMP.
75 //------------------------------------------
76 void set_current_filter(const unsigned int current_filter) {
77 const unsigned int ncurrent_filter = current_filter>gmic_entries.size?0:current_filter;
78 gimp_set_data("gmic_current_filter",&ncurrent_filter,sizeof(unsigned int));
79 }
81 unsigned int get_current_filter() {
82 unsigned int current_filter = 0;
83 gimp_get_data("gmic_current_filter",¤t_filter);
84 if (current_filter>gmic_entries.size) current_filter = 0;
85 return current_filter;
86 }
88 void set_filter_nbparams(const unsigned int filter, const unsigned int nbparams) {
89 char s_tmp[256] = { 0 };
90 std::sprintf(s_tmp,"gmic_filter%u_nbparams",filter);
91 gimp_set_data(s_tmp,&nbparams,sizeof(unsigned int));
92 }
94 unsigned int get_filter_nbparams(const unsigned int filter) {
95 char s_tmp[256] = { 0 };
96 std::sprintf(s_tmp,"gmic_filter%u_nbparams",filter);
97 unsigned int nbparams = 0;
98 gimp_get_data(s_tmp,&nbparams);
99 return nbparams;
100 }
102 void set_filter_parameter(const unsigned int filter, const unsigned int n, const char *const param) {
103 char s_tmp[256] = { 0 };
104 std::sprintf(s_tmp,"gmic_filter%u_parameter%u",filter,n);
105 gimp_set_data(s_tmp,param,cimg::strlen(param)+1);
106 }
108 const char *get_filter_parameter(const unsigned int filter, const unsigned int n) {
109 char s_tmp[256] = { 0 };
110 std::sprintf(s_tmp,"gmic_filter%u_parameter%u",filter,n);
111 static char res[4096] = { 0 };
112 res[0] = 0;
113 gimp_get_data(s_tmp,res);
114 return res;
115 }
117 unsigned int get_verbosity_level() {
118 unsigned int verbosity = 0;
119 gimp_get_data("gmic_verbosity",&verbosity);
120 return verbosity;
121 }
123 void set_verbosity_level(const unsigned int verbosity) {
124 gimp_set_data("gmic_verbosity",&verbosity,sizeof(unsigned int));
125 }
127 // Return G'MIC command line needed to run the selected filter.
128 //--------------------------------------------------------------
129 const char* get_commandline(const bool preview) {
130 const unsigned int
131 verbosity_level = get_verbosity_level(),
132 filter = get_current_filter(),
133 nbparams = get_filter_nbparams(filter);
134 if (!filter) return 0;
136 static CImg<char> res;
138 CImgList<char> lres;
139 switch (verbosity_level) {
140 case 0: lres.insert(CImg<char>("-v- -",5)); break;
141 case 1: lres.insert(CImg<char>("-",1)); break;
142 default: lres.insert(CImg<char>("-v+ -debug -",12));
143 }
145 const unsigned int N = filter - 1;
146 const CImg<char> &command_item = (preview?gmic_preview_commands[N]:gmic_commands[N]);
147 if (command_item) {
148 lres.insert(command_item);
149 if (nbparams) {
150 lres[1].last() = ' ';
151 for (unsigned int p = 0; p<nbparams; ++p) {
152 const char *const param = get_filter_parameter(filter,p);
153 if (param) lres.insert(CImg<char>(param,cimg::strlen(param)+1)).last().last() = ',';
154 }
155 }
156 (res = lres.get_append('x')).last() = 0;
157 }
158 return res.ptr();
159 }
161 // Process image region with G'MIC.
162 //---------------------------------
164 // Define structure to store the arguments needed by the processing thread.
165 struct st_process_thread {
166 pthread_t thread;
167 CImgList<float> images;
168 const char *commandline;
169 unsigned int verbosity_level;
170 pthread_mutex_t is_running;
171 };
173 // Thread that does the image processing part (call the G'MIC library).
174 void *process_thread(void *arg) {
175 st_process_thread &spt = *(st_process_thread*)arg;
176 try {
177 if (spt.verbosity_level>0)
178 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Running G'MIC to process the image, with command : %s\n",spt.commandline);
179 std::setlocale(LC_NUMERIC,"C");
180 gmic(spt.commandline,spt.images,gmic_macros,false);
181 if (spt.verbosity_level>0)
182 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : G'MIC successfully returned !\n");
183 } catch (gmic_exception &e) {
184 if (spt.verbosity_level>0)
185 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Error encountered when running G'MIC :\n*** %s\n",e.message);
186 spt.images.assign();
187 }
188 pthread_mutex_unlock(&spt.is_running);
189 pthread_exit(0);
190 return 0;
191 }
193 // Routine called to process the current GIMP image.
194 void process_image(GimpDrawable *drawable, const char *last_commandline) {
195 const unsigned int filter = get_current_filter();
196 if (!last_commandline && !filter) return;
197 const char *commandline = last_commandline?last_commandline:get_commandline(false);
198 if (!commandline || !cimg::strcmp(commandline,"-v- -nop")) return;
199 gimp_progress_init_printf(" G'MIC Toolbox : %s...",gmic_entries[filter-1].ptr());
201 // Read GIMP image region data and make a CImg<float> instance from it.
202 GimpPixelRgn src_region;
203 gint x1, y1, x2, y2;
204 gimp_drawable_mask_bounds(drawable->drawable_id,&x1,&y1,&x2,&y2); // Get coordinates of the current layer selection.
205 const gint width = x2 - x1, height = y2 - y1, channels = drawable->bpp;
206 gimp_pixel_rgn_init(&src_region,drawable,x1,y1,width,height,false,false);
207 guchar *const src_row = g_new(guchar,width*channels);
208 CImg<float> img(width,height,1,channels);
209 cimg_forY(img,y) {
210 gimp_pixel_rgn_get_row(&src_region,src_row,x1,y1+y,width);
211 const guchar *ptrs = src_row;
212 cimg_forX(img,x) cimg_forV(img,k) img(x,y,k) = (float)*(ptrs++);
213 }
214 g_free(src_row);
216 // Call G'MIC interpreter on the CImg<float> image in a new thread.
217 st_process_thread spt;
218 spt.images.assign(1);
219 img.transfer_to(spt.images[0]);
220 spt.commandline = commandline;
221 spt.verbosity_level = get_verbosity_level();
222 pthread_mutex_init(&spt.is_running,0);
223 pthread_mutex_lock(&spt.is_running);
224 pthread_create(&(spt.thread),0,process_thread,(void*)&spt);
226 // Do a small animation with the progress bar, while waiting for
227 // the termination of the processing thread.
228 while (pthread_mutex_trylock(&spt.is_running)) { gimp_progress_pulse(); cimg::wait(500); }
229 gimp_progress_update(1.0);
230 pthread_join(spt.thread,0);
231 pthread_mutex_unlock(&spt.is_running);
232 pthread_mutex_destroy(&spt.is_running);
234 // Force the resulting images to have all the same 2D GRAY, GRAYA, RGB or RGBA format.
235 if (!spt.images) { gimp_progress_end(); return; }
236 unsigned int max_width = 0, max_height = 0, max_channels = 0;
237 cimglist_for(spt.images,p) {
238 const CImg<float>& img = spt.images[p];
239 if (img.width>max_width) max_width = img.width;
240 if (img.height>max_height) max_height = img.height;
241 if (img.dim>max_channels) max_channels = img.dim;
242 }
243 if (max_channels>4) max_channels = 4;
244 cimglist_apply(spt.images,resize)(-100,-100,1,max_channels);
246 // Transfer the result image back into GIMP.
247 if (spt.images.size==1 && (int)max_width==width && (int)max_height==height && (int)max_channels==channels) {
249 // When the result image has same dimensions than the source :
250 // Replace the selected region of the original GIMP image.
251 CImg<float> &res = spt.images[0];
252 GimpPixelRgn dest_region;
253 guchar *const dest_row = g_new(guchar,res.dimx()*res.dimv());
254 gimp_pixel_rgn_init(&dest_region,drawable,0,0,drawable->width,drawable->height,true,true);
255 cimg_forY(res,y) {
256 guchar *ptrd = dest_row;
257 cimg_forX(res,x) cimg_forV(res,k) *(ptrd++) = (guchar)res(x,y,k);
258 gimp_pixel_rgn_set_row(&dest_region,dest_row,x1,y1+y,width);
259 }
260 g_free(dest_row);
261 spt.images.assign();
262 gimp_drawable_flush(drawable);
263 gimp_drawable_merge_shadow(drawable->drawable_id,true);
264 gimp_drawable_update(drawable->drawable_id,x1,y1,x2-x1,y2-y1);
265 gimp_displays_flush();
266 } else {
268 // When the result image has different dimensions than the source :
269 // Returns a new GIMP image.
270 gint id_img = gimp_image_new(max_width,max_height,max_channels<=2?GIMP_GRAY:GIMP_RGB);
271 gimp_image_undo_group_start(id_img);
273 cimglist_for(spt.images,p) {
274 CImg<float> &res = spt.images[p];
275 gint id_layer = gimp_layer_new(id_img,"image",res.dimx(),res.dimy(),
276 res.dimv()==1?GIMP_GRAY_IMAGE:
277 res.dimv()==2?GIMP_GRAYA_IMAGE:
278 res.dimv()==3?GIMP_RGB_IMAGE:
279 GIMP_RGBA_IMAGE,
280 100.0,GIMP_NORMAL_MODE);
281 gimp_image_add_layer(id_img,id_layer,0);
282 GimpDrawable *ndrawable = gimp_drawable_get(id_layer);
284 GimpPixelRgn dest_region;
285 guchar *const dest_row = g_new(guchar,res.dimx()*res.dimv());
286 gimp_pixel_rgn_init(&dest_region,ndrawable,0,0,ndrawable->width,ndrawable->height,true,true);
287 cimg_forY(res,y) {
288 guchar *ptrd = dest_row;
289 cimg_forX(res,x) cimg_forV(res,k) *(ptrd++) = (guchar)res(x,y,k);
290 gimp_pixel_rgn_set_row(&dest_region,dest_row,0,y,res.dimx());
291 }
292 g_free(dest_row);
293 res.assign();
294 gimp_drawable_flush(ndrawable);
295 gimp_drawable_merge_shadow(ndrawable->drawable_id,true);
296 gimp_drawable_update(ndrawable->drawable_id,0,0,ndrawable->width,ndrawable->height);
297 gimp_drawable_detach(ndrawable);
298 }
299 gimp_display_new(id_img);
300 gimp_image_undo_group_end(id_img);
301 gimp_displays_flush();
302 }
303 gimp_progress_end();
304 }
306 // Process preview with G'MIC.
307 //-----------------------------
308 void process_preview(GimpPreview *preview) {
309 const unsigned int filter = get_current_filter();
310 if (!filter) return;
311 const char *const commandline = get_commandline(true);
312 if (!commandline || !cimg::strcmp(commandline,"-v- -nop")) return;
314 // Read GIMP image preview and make a CImg<float> instance from it.
315 gint width, height, channels;
316 guchar *const ptr0 = gimp_zoom_preview_get_source(GIMP_ZOOM_PREVIEW(preview),&width,&height,&channels), *ptrs = ptr0;
317 CImg<float> img(width,height,1,channels);
318 cimg_forXY(img,x,y) cimg_forV(img,k) img(x,y,k) = (float)*(ptrs++);
320 // Call G'MIC interpreter on the preview image.
321 CImgList<float> gmic_images(1);
322 img.transfer_to(gmic_images[0]);
323 try {
324 if (get_verbosity_level()>0)
325 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Running G'MIC to process the preview, with command : %s\n",commandline);
326 std::setlocale(LC_NUMERIC,"C");
327 gmic(commandline,gmic_images,gmic_macros,false);
328 if (get_verbosity_level()>0)
329 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : G'MIC successfully returned !\n");
330 } catch (gmic_exception &e) {
331 if (get_verbosity_level()>0)
332 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Error encountered when running G'MIC :\n*** %s\n",e.message);
333 gmic_images.assign();
334 }
336 // Get current image preview from the processed data.
337 if (gmic_images.size && gmic_images[0]) {
338 CImg<float>& res = gmic_images[0];
339 if (res.width>res.height) {
340 const unsigned int _nheight = res.height*width/res.width, nheight = _nheight?_nheight:1;
341 res.resize(width,nheight,1,-100,2);
342 } else {
343 const unsigned int _nwidth = res.width*height/res.height, nwidth = _nwidth?_nwidth:1;
344 res.resize(nwidth,height,1,-100,2);
345 }
346 if (res.dimx()!=width || res.dimy()!=height) res.resize(width,height,1,-100,0,0,1);
347 switch (channels) {
348 case 1:
349 switch (res.dim) {
350 case 1: break;
351 case 2: res.channel(0); break;
352 case 3: res.channel(0); break;
353 case 4: res.channel(0); break;
354 default: res.channel(0);
355 } break;
356 case 2:
357 switch (res.dim) {
358 case 1: res.resize(-100,-100,1,2,0).get_shared_channel(1).fill(255); break;
359 case 2: break;
360 case 3: res.channels(0,1).get_shared_channel(1).fill(255); break;
361 case 4: res.get_shared_channel(1) = res.get_shared_channel(3); res.channels(0,1); break;
362 default: res.channels(0,1).get_shared_channel(1).fill(255);
363 } break;
364 case 3:
365 switch (res.dim) {
366 case 1: res.resize(-100,-100,1,3); break;
367 case 2: res.channel(0).resize(-100,-100,1,3); break;
368 case 3: break;
369 case 4: res.channels(0,2); break;
370 default: res.channels(0,2);
371 } break;
372 case 4:
373 switch (res.dim) {
374 case 1: res.resize(-100,-100,1,4).get_shared_channel(3).fill(255); break;
375 case 2:
376 res.resize(-100,-100,1,4,0);
377 res.get_shared_channel(3) = res.get_shared_channel(1);
378 res.get_shared_channel(1) = res.get_shared_channel(0);
379 res.get_shared_channel(2) = res.get_shared_channel(0);
380 break;
381 case 3: res.resize(-100,-100,1,4,0).get_shared_channel(3).fill(255); break;
382 case 4: break;
383 default: res.resize(-100,-100,1,4,0);
384 } break;
385 }
386 guchar *ptrd = ptr0;
387 cimg_forXY(res,x,y) cimg_forV(res,k) *(ptrd++) = (guchar)res(x,y,k);
388 gimp_preview_draw_buffer(preview,ptr0,width*channels);
389 g_free(ptr0);
390 }
391 }
393 // Define event functions for GUI.
394 //--------------------------------
396 // Handle responses to the parameter widgets.
397 void on_float_parameter_changed(GtkAdjustment *scale, gpointer user_data) {
398 const unsigned int arg = *(unsigned int*)user_data;
399 double value = 0;
400 gimp_double_adjustment_update(scale,&value);
401 char s_value[1024] = { 0 };
402 std::sprintf(s_value,"%g",value);
403 set_filter_parameter(get_current_filter(),arg,s_value);
404 return_create_dialog = true;
405 }
407 void on_int_parameter_changed(GtkAdjustment *scale, gpointer user_data) {
408 const unsigned int arg = *(unsigned int*)user_data;
409 int value = 0;
410 gimp_int_adjustment_update(scale,&value);
411 char s_value[1024] = { 0 };
412 std::sprintf(s_value,"%d",value);
413 set_filter_parameter(get_current_filter(),arg,s_value);
414 return_create_dialog = true;
415 }
417 void on_bool_parameter_changed(GtkCheckButton *checkbutton, gpointer user_data) {
418 const unsigned int arg = *(unsigned int*)user_data;
419 int value = 0;
420 g_object_get(checkbutton,"active",&value,NULL);
421 char s_value[1024] = { 0 };
422 std::sprintf(s_value,"%d",value?1:0);
423 set_filter_parameter(get_current_filter(),arg,s_value);
424 return_create_dialog = true;
425 }
427 void on_list_parameter_changed(GtkComboBox *combobox, gpointer user_data) {
428 const unsigned int arg = *(unsigned int*)user_data;
429 int value = 0;
430 g_object_get(combobox,"active",&value,NULL);
431 char s_value[1024] = { 0 };
432 std::sprintf(s_value,"%d",value);
433 set_filter_parameter(get_current_filter(),arg,s_value);
434 return_create_dialog = true;
435 }
437 void on_text_parameter_changed(GtkButton *button, gpointer user_data) {
438 button = 0;
439 const unsigned int arg = *(unsigned int*)user_data;
440 GtkWidget *entry = *((GtkWidget**)user_data+1);
441 const char *s_value = gtk_entry_get_text(GTK_ENTRY(entry));
442 set_filter_parameter(get_current_filter(),arg,s_value);
443 return_create_dialog = true;
444 }
446 void on_file_parameter_changed(GtkFileChooserButton *widget, gpointer user_data){
447 const unsigned int arg = *(unsigned int*)user_data;
448 const char
449 *const filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)),
450 *s_value = filename?filename:"";
451 set_filter_parameter(get_current_filter(),arg,s_value);
452 return_create_dialog = true;
453 }
455 void on_color_parameter_changed(GtkColorButton *widget, gpointer user_data){
456 const unsigned int arg = *(unsigned int*)user_data;
457 GdkColor color;
458 gtk_color_button_get_color(GTK_COLOR_BUTTON(widget),&color);
459 char s_value[1024] = { 0 };
460 if (gtk_color_button_get_use_alpha(GTK_COLOR_BUTTON(widget)))
461 std::sprintf(s_value,"%d,%d,%d,%d",
462 color.red>>8,color.green>>8,color.blue>>8,gtk_color_button_get_alpha(GTK_COLOR_BUTTON(widget))>>8);
463 else std::sprintf(s_value,"%d,%d,%d",
464 color.red>>8,color.green>>8,color.blue>>8);
465 set_filter_parameter(get_current_filter(),arg,s_value);
466 return_create_dialog = true;
467 }
469 // Create parameter GUI for specific chosen filter.
470 //--------------------------------------------------
471 void create_parameters_gui(const bool reset) {
472 const unsigned int filter = get_current_filter();
474 // Remove widget in the current frame if necessary.
475 GtkWidget *frame = 0;
476 gimp_get_data("gmic_gui_frame",&frame);
477 if (frame) {
478 GtkWidget *child = GTK_WIDGET(gtk_bin_get_child(GTK_BIN(frame)));
479 if (child) gtk_container_remove(GTK_CONTAINER(frame),child);
480 }
482 GtkWidget *table = 0;
483 if (!filter) { // No filter selected -> Default message.
484 table = gtk_table_new(1,1,false);
485 gtk_widget_show(table);
486 GtkWidget *label = gtk_label_new(NULL);
487 gtk_label_set_markup(GTK_LABEL(label),"<i>Select a filter...</i>");
488 gtk_widget_show(label);
489 gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
490 (GtkAttachOptions)(GTK_EXPAND),(GtkAttachOptions)(GTK_EXPAND),0,0);
491 gtk_misc_set_alignment (GTK_MISC(label),0,0.5);
492 gtk_frame_set_label(GTK_FRAME(frame),NULL);
493 } else { // Filter selected -> Build parameter table.
494 GtkWidget *preview = 0;
495 gimp_get_data("gmic_gui_preview",&preview);
496 const unsigned int N = filter - 1;
497 char nlabel[4096] = { 0 };
498 std::sprintf(nlabel,"<b> %s : </b>",gmic_entries[N].ptr());
499 GtkWidget *frame_title = gtk_label_new(NULL);
500 gtk_widget_show(frame_title);
501 gtk_label_set_markup(GTK_LABEL(frame_title),nlabel);
502 gtk_frame_set_label_widget(GTK_FRAME(frame),frame_title);
504 char argname[4096] = { 0 }, argtype[4096] = { 0 }, argarg[4096] = { 0 };
505 unsigned int nb_arguments = 0;
506 for (const char *argument = gmic_arguments[N].ptr(); *argument; ) {
507 if (std::sscanf(argument,"%4095[^=]=%4095[^(](%4095[^)]",argname,argtype,&(argarg[0]=0))>=2) {
508 argument += cimg::strlen(argname) + cimg::strlen(argtype) + cimg::strlen(argarg) + 3;
509 if (*argument) ++argument;
510 ++nb_arguments;
511 } else break;
512 }
514 if (!nb_arguments) { // Selected filter has no parameters -> Default message.
515 table = gtk_table_new(1,1,false);
516 gtk_widget_show(table);
517 GtkWidget *label = gtk_label_new(NULL);
518 gtk_label_set_markup(GTK_LABEL(label),"<i>No parameters to set...</i>");
519 gtk_widget_show(label);
520 gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
521 (GtkAttachOptions)(GTK_EXPAND),(GtkAttachOptions)(GTK_EXPAND),0,0);
522 gtk_misc_set_alignment (GTK_MISC(label),0,0.5);
523 } else { // Selected filter has parameters -> Create parameter table.
525 // Create new table for putting parameters inside.
526 table = gtk_table_new(3,nb_arguments,false);
527 gtk_widget_show(table);
528 gtk_table_set_row_spacings(GTK_TABLE(table),6);
529 gtk_table_set_col_spacings(GTK_TABLE(table),6);
530 gtk_container_set_border_width(GTK_CONTAINER(table),8);
532 // Parse arguments list and add recognized one to the table.
533 event_infos = new void*[2*nb_arguments];
534 int current_parameter = 0, current_line = 0;
535 for (const char *argument = gmic_arguments[N].ptr(); *argument; ) {
536 if (std::sscanf(argument,"%4095[^=]=%4095[^(](%4095[^)]",argname,argtype,&(argarg[0]=0))>=2) {
537 argument += cimg::strlen(argname) + cimg::strlen(argtype) + cimg::strlen(argarg) + 3;
538 if (*argument) ++argument;
539 cimg::strclean(argname);
540 cimg::strclean(argtype);
541 const char *const s_value = get_filter_parameter(filter,current_parameter);
543 // Check for a float-valued parameter -> Create GtkAdjustment.
544 bool found_valid_item = false;
545 if (!found_valid_item && !cimg::strcasecmp(argtype,"float")) {
546 float initial_value = 0, min_value = 0, max_value = 100;
547 std::setlocale(LC_NUMERIC,"C");
548 std::sscanf(argarg,"%f%*c%f%*c%f",&initial_value,&min_value,&max_value);
549 if (!reset && std::sscanf(s_value,"%f",&initial_value)) {}
550 GtkObject *scale = gimp_scale_entry_new(GTK_TABLE(table),0,current_line,argname,100,6,
551 (gdouble)initial_value,(gdouble)min_value,(gdouble)max_value,
552 0.1,0.1,2,true,0,0,0,0);
553 event_infos[2*current_parameter] = (void*)current_parameter;
554 event_infos[2*current_parameter+1] = (void*)0;
555 on_float_parameter_changed(GTK_ADJUSTMENT(scale),(void*)(event_infos+2*current_parameter));
556 g_signal_connect(scale,"value_changed",G_CALLBACK(on_float_parameter_changed),
557 (void*)(event_infos+2*current_parameter));
558 g_signal_connect_swapped(scale,"value_changed",G_CALLBACK(gimp_preview_invalidate),preview);
559 found_valid_item = true;
560 ++current_parameter;
561 }
563 // Check for an int-valued parameter -> Create GtkAdjustment.
564 if (!found_valid_item && !cimg::strcasecmp(argtype,"int")) {
565 float initial_value = 0, min_value = 0, max_value = 100;
566 std::setlocale(LC_NUMERIC,"C");
567 std::sscanf(argarg,"%f%*c%f%*c%f",&initial_value,&min_value,&max_value);
568 if (!reset && std::sscanf(s_value,"%f",&initial_value)) {}
569 GtkObject *scale = gimp_scale_entry_new(GTK_TABLE(table),0,current_line,argname,100,6,
570 (gdouble)(int)initial_value,(gdouble)(int)min_value,
571 (gdouble)(int)max_value,
572 1,1,0,true,0,0,0,0);
573 event_infos[2*current_parameter] = (void*)current_parameter;
574 event_infos[2*current_parameter+1] = (void*)0;
575 on_int_parameter_changed(GTK_ADJUSTMENT(scale),(void*)(event_infos+2*current_parameter));
576 g_signal_connect(scale,"value_changed",G_CALLBACK(on_int_parameter_changed),
577 (void*)(event_infos+2*current_parameter));
578 g_signal_connect_swapped(scale,"value_changed",G_CALLBACK(gimp_preview_invalidate),preview);
579 found_valid_item = true;
580 ++current_parameter;
581 }
583 // Check for a bool-valued parameter -> Create GtkCheckButton.
584 if (!found_valid_item && !cimg::strcasecmp(argtype,"bool")) {
585 unsigned int initial_value = 0;
586 std::sscanf(argarg,"%u",&initial_value);
587 if (!reset && std::sscanf(s_value,"%u",&initial_value)) {}
588 GtkWidget *checkbutton = gtk_check_button_new_with_label(argname);
589 gtk_widget_show(checkbutton);
590 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton),initial_value?true:false);
591 gtk_table_attach(GTK_TABLE(table),checkbutton,0,2,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
592 event_infos[2*current_parameter] = (void*)current_parameter;
593 event_infos[2*current_parameter+1] = (void*)0;
594 on_bool_parameter_changed(GTK_CHECK_BUTTON(checkbutton),(void*)(event_infos+2*current_parameter));
595 g_signal_connect(checkbutton,"toggled",G_CALLBACK(on_bool_parameter_changed),
596 (void*)(event_infos+2*current_parameter));
597 g_signal_connect_swapped(checkbutton,"toggled",G_CALLBACK(gimp_preview_invalidate),preview);
598 found_valid_item = true;
599 ++current_parameter;
600 }
602 // Check for a list-valued parameter -> Create GtkComboBox.
603 if (!found_valid_item && !cimg::strcasecmp(argtype,"choice")) {
604 GtkWidget *label = gtk_label_new(argname);
605 gtk_widget_show(label);
606 gtk_table_attach(GTK_TABLE(table),label,0,1,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
607 gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
608 GtkWidget *combobox = gtk_combo_box_new_text();
609 gtk_widget_show(combobox);
610 char s_entry[4096] = { 0 }, end = 0; int err2 = 0;
611 unsigned int initial_value = 0;
612 const char *entries = argarg;
613 if (std::sscanf(entries,"%u",&initial_value)==1) entries+=std::sprintf(s_entry,"%u",initial_value) + 1;
614 while (*entries) {
615 if ((err2 = std::sscanf(entries,"%4095[^,]%c",s_entry,&end))>0) {
616 entries += cimg::strlen(s_entry) + (err2==2?1:0);
617 cimg::strclean(s_entry);
618 strparenthesis(s_entry);
619 gtk_combo_box_append_text(GTK_COMBO_BOX(combobox),s_entry);
620 } else break;
621 }
622 if (!reset && std::sscanf(s_value,"%u",&initial_value)) {}
623 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox),initial_value);
624 gtk_table_attach(GTK_TABLE(table),combobox,1,3,current_line,current_line+1,
625 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),(GtkAttachOptions)(GTK_FILL),0,0);
626 event_infos[2*current_parameter] = (void*)current_parameter;
627 event_infos[2*current_parameter+1] = (void*)0;
628 on_list_parameter_changed(GTK_COMBO_BOX(combobox),(void*)(event_infos+2*current_parameter));
629 g_signal_connect(combobox,"changed",G_CALLBACK(on_list_parameter_changed),
630 (void*)(event_infos+2*current_parameter));
631 g_signal_connect_swapped(combobox,"changed",G_CALLBACK(gimp_preview_invalidate),preview);
632 found_valid_item = true;
633 ++current_parameter;
634 }
636 // Check for a text-valued parameter -> Create GtkEntry.
637 if (!found_valid_item && !cimg::strcasecmp(argtype,"text")) {
638 GtkWidget *label = gtk_label_new(argname);
639 gtk_widget_show(label);
640 gtk_table_attach(GTK_TABLE(table),label,0,1,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
641 gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
642 GtkWidget *entry = gtk_entry_new_with_max_length(4095);
643 gtk_widget_show(entry);
644 cimg::strclean(argarg);
645 if (!reset && *s_value) gtk_entry_set_text(GTK_ENTRY(entry),s_value);
646 else gtk_entry_set_text(GTK_ENTRY(entry),argarg);
647 gtk_table_attach(GTK_TABLE(table),entry,1,2,current_line,current_line+1,
648 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),(GtkAttachOptions)0,0,0);
649 GtkWidget *button = gtk_button_new_with_label("Update");
650 gtk_widget_show(button);
651 gtk_table_attach(GTK_TABLE(table),button,2,3,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
652 event_infos[2*current_parameter] = (void*)current_parameter;
653 event_infos[2*current_parameter+1] = (void*)entry;
654 on_text_parameter_changed(GTK_BUTTON(button),(void*)(event_infos+2*current_parameter));
655 g_signal_connect(button,"clicked",G_CALLBACK(on_text_parameter_changed),
656 (void*)(event_infos+2*current_parameter));
657 g_signal_connect_swapped(button,"clicked",G_CALLBACK(gimp_preview_invalidate),preview);
658 found_valid_item = true;
659 ++current_parameter;
660 }
662 // Check for a filename parameter -> Create GtkFileChooserButton.
663 if (!found_valid_item && !cimg::strcasecmp(argtype,"file")) {
664 GtkWidget *label = gtk_label_new(argname);
665 gtk_widget_show(label);
666 gtk_table_attach(GTK_TABLE(table),label,0,1,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
667 gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
668 GtkWidget *filechooser = gtk_file_chooser_button_new(argname,GTK_FILE_CHOOSER_ACTION_OPEN);
669 gtk_widget_show(filechooser);
670 cimg::strclean(argarg);
671 if (!reset && *s_value) gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(filechooser),s_value);
672 else gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(filechooser),argarg);
673 gtk_table_attach(GTK_TABLE(table),filechooser,1,3,current_line,current_line+1,
674 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),(GtkAttachOptions)0,0,0);
675 event_infos[2*current_parameter] = (void*)current_parameter;
676 event_infos[2*current_parameter+1] = (void*)0;
677 on_file_parameter_changed(GTK_FILE_CHOOSER_BUTTON(filechooser),(void*)(event_infos+2*current_parameter));
678 g_signal_connect(filechooser,"file-set",G_CALLBACK(on_file_parameter_changed),
679 (void*)(event_infos+2*current_parameter));
680 g_signal_connect_swapped(filechooser,"file-set",G_CALLBACK(gimp_preview_invalidate),preview);
681 found_valid_item = true;
682 ++current_parameter;
683 }
685 // Check for a color -> Create GtkColorButton.
686 if (!found_valid_item && !cimg::strcasecmp(argtype,"color")) {
687 GtkWidget *hbox = gtk_hbox_new(false,6);
688 gtk_widget_show(hbox);
689 gtk_table_attach(GTK_TABLE(table),hbox,0,2,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
690 GtkWidget *label = gtk_label_new(argname);
691 gtk_widget_show(label);
692 gtk_box_pack_start(GTK_BOX(hbox),label,false,false,0);
693 GtkWidget *colorchooser = gtk_color_button_new();
694 gtk_widget_show(colorchooser);
695 gtk_color_button_set_title(GTK_COLOR_BUTTON(colorchooser),argname);
696 gtk_box_pack_start(GTK_BOX(hbox),colorchooser,false,false,0);
697 event_infos[2*current_parameter] = (void*)current_parameter;
698 event_infos[2*current_parameter+1] = (void*)0;
699 cimg::strclean(argarg);
700 unsigned int red = 0, green = 0, blue = 0, alpha = 255;
701 const int err = std::sscanf(argarg,"%u%*c%u%*c%u%*c%u",&red,&green,&blue,&alpha);
702 if (!reset && std::sscanf(s_value,"%u%*c%u%*c%u%*c%u",&red,&green,&blue,&alpha)==err) {}
703 GdkColor col;
704 col.pixel = 0; col.red = red<<8; col.green = green<<8; col.blue = blue<<8;
705 gtk_color_button_set_color(GTK_COLOR_BUTTON(colorchooser),&col);
706 if (err==4) {
707 gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(colorchooser),true);
708 gtk_color_button_set_alpha(GTK_COLOR_BUTTON(colorchooser),alpha<<8);
709 } else gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(colorchooser),false);
710 on_color_parameter_changed(GTK_COLOR_BUTTON(colorchooser),(void*)(event_infos+2*current_parameter));
711 g_signal_connect(colorchooser,"color-set",G_CALLBACK(on_color_parameter_changed),
712 (void*)(event_infos+2*current_parameter));
713 g_signal_connect_swapped(colorchooser,"color-set",G_CALLBACK(gimp_preview_invalidate),preview);
714 found_valid_item = true;
715 ++current_parameter;
716 }
718 // Check for a note -> Create GtkLabel.
719 if (!found_valid_item && !cimg::strcasecmp(argtype,"note")) {
720 cimg::strclean(argarg);
721 GtkWidget *label = gtk_label_new(NULL);
722 cimg::strescape(argarg);
723 strparenthesis(argarg);
724 gtk_label_set_markup(GTK_LABEL(label),argarg);
725 gtk_label_set_line_wrap(GTK_LABEL(label),true);
726 gtk_widget_show(label);
727 gtk_table_attach(GTK_TABLE(table),label,0,3,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
728 gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
729 found_valid_item = true;
730 }
732 if (!found_valid_item) {
733 if (get_verbosity_level()>0)
734 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Found invalid parameter type '%s' for argument '%s'.\n",argtype,argname);
735 } else ++current_line;
736 } else break;
737 }
738 set_filter_nbparams(filter,current_parameter);
739 }
740 }
741 gtk_container_add(GTK_CONTAINER(frame),table);
742 }
744 // Called when the selected filter changed (in the combo-box).
745 void on_filter_changed(GtkTreeSelection *selection, gpointer user_data) {
746 user_data = 0;
747 GtkTreeIter iter;
748 GtkTreeModel *model;
749 unsigned int choice = 0;
750 if (gtk_tree_selection_get_selected(selection,&model,&iter))
751 gtk_tree_model_get(model,&iter,0,&choice,-1);
752 set_current_filter(choice);
753 create_parameters_gui(false);
754 return_create_dialog = true;
755 }
757 // Handle responses to the dialog window buttons.
758 void on_verbosity_level_changed(GtkComboBox *combobox, gpointer user_data) {
759 user_data = 0;
760 int value = 0;
761 g_object_get(combobox,"active",&value,NULL);
762 set_verbosity_level(value);
763 }
765 void on_dialog_reset_clicked(GtkButton *widget, gpointer data) {
766 widget = 0; data = 0;
767 create_parameters_gui(true);
768 return_create_dialog = true;
769 }
771 void on_dialog_cancel_clicked(GtkButton *widget, gpointer data) {
772 widget = 0; data = 0;
773 return_create_dialog = false;
774 gtk_main_quit();
775 }
777 void on_dialog_apply_clicked(GtkButton *widget, gpointer data) {
778 widget = 0;
779 GimpDrawable *drawable = (GimpDrawable*)data;
780 process_image(drawable,0);
781 return_create_dialog = false;
782 }
784 void on_dialog_ok_clicked(GtkButton *widget, gpointer data) {
785 widget = 0; data = 0;
786 gtk_main_quit();
787 }
789 void on_update_button_clicked(GtkButton *widget, gpointer data) {
790 widget = 0;
791 GtkWidget *dialog = (GtkWidget*)data;
792 char update_filename[1024] = { 0 }, update_command[1024] = { 0 }, src_filename[1024] = { 0 }, dest_filename[1024] = { 0 };
793 const char
794 *const update_url = "http://www.greyc.ensicaen.fr/~dtschump",
795 *const path_tmp = cimg::temporary_path();
796 std::sprintf(update_filename,"gmic4gimp_def.%d",gmic_version);
797 std::sprintf(src_filename,"%s/%s",path_tmp,update_filename);
798 std::sprintf(dest_filename,"%s/.%s",path_home,update_filename);
799 if (get_verbosity_level()>0) {
800 std::sprintf(update_command,"wget %s/%s -O %s",update_url,update_filename,src_filename);
801 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Running update procedure, with command : %s\n",update_command);
802 } else std::sprintf(update_command,"wget --quiet %s/%s -O %s",update_url,update_filename,src_filename);
803 int status = cimg::system(update_command);
804 status = 0;
805 std::FILE *file_s = std::fopen(src_filename,"r");
806 bool succeed = false;
807 if (file_s) {
808 unsigned int size_s = 0;
809 std::fseek(file_s,0,SEEK_END);
810 size_s = (unsigned int)std::ftell(file_s);
811 std::rewind(file_s);
812 if (size_s) {
813 std::FILE *file_d = std::fopen(dest_filename,"w");
814 char *buffer = new char[size_s], sep = 0;
815 if (file_d &&
816 std::fread(buffer,sizeof(char),size_s,file_s)==size_s &&
817 std::sscanf(buffer,"#@gim%c",&sep)==1 && sep=='p' &&
818 std::fwrite(buffer,sizeof(char),size_s,file_d)==size_s) { succeed = true; std::fclose(file_d); }
819 delete[] buffer;
820 }
821 std::fclose(file_s);
822 }
823 if (!succeed) {
824 GtkWidget *message = gtk_message_dialog_new_with_markup(GTK_WINDOW(dialog),GTK_DIALOG_MODAL,GTK_MESSAGE_ERROR,GTK_BUTTONS_OK,
825 "<b>Filters update failed !</b>\n\n"
826 "A valid version of the update file :\n\n"
827 "<i>%s/%s</i>\n\n"
828 " ...could not be retrieved from the G'MIC server.\n\n"
829 "Please check your Internet connection or\n"
830 "try a manual update instead.",update_url,update_filename);
831 gtk_widget_show(message);
832 gtk_dialog_run(GTK_DIALOG(message));
833 gtk_widget_destroy(message);
834 } else {
835 GtkWidget *message = gtk_message_dialog_new_with_markup(GTK_WINDOW(dialog),GTK_DIALOG_MODAL,GTK_MESSAGE_INFO,GTK_BUTTONS_OK,
836 "<b>Filters update succeed !</b>\n\n"
837 "The G'MIC Toolbox must be restarted now.");
838 gtk_widget_show(message);
839 gtk_dialog_run(GTK_DIALOG(message));
840 gtk_widget_destroy(message);
841 return_create_dialog = false;
842 set_current_filter(0);
843 gtk_main_quit();
844 }
845 }
847 // Create main plug-in dialog window and wait for a response.
848 //-----------------------------------------------------------
849 bool create_dialog_gui(GimpDrawable *drawable) {
851 // Init GUI_specific variables
852 gimp_ui_init("gmic",true);
853 event_infos = 0;
855 // Create main plug-in dialog window.
856 GtkWidget
857 *dialog = gimp_dialog_new("The G'MIC Toolbox","gmic",0,(GtkDialogFlags)0,gimp_standard_help_func,"gmic",NULL),
858 *cancel_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GTK_STOCK_CANCEL,GTK_RESPONSE_CANCEL),
859 *reset_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GIMP_STOCK_RESET,1),
860 *apply_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GTK_STOCK_APPLY,GTK_RESPONSE_APPLY),
861 *ok_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GTK_STOCK_OK,GTK_RESPONSE_OK);
862 gimp_window_set_transient(GTK_WINDOW(dialog));
863 g_signal_connect(dialog,"close",G_CALLBACK(on_dialog_cancel_clicked),0);
864 g_signal_connect(dialog,"delete-event",G_CALLBACK(on_dialog_cancel_clicked),0);
865 g_signal_connect(cancel_button,"clicked",G_CALLBACK(on_dialog_cancel_clicked),0);
866 g_signal_connect(apply_button,"clicked",G_CALLBACK(on_dialog_apply_clicked),drawable);
867 g_signal_connect(ok_button,"clicked",G_CALLBACK(on_dialog_ok_clicked),0);
869 GtkWidget *dialog_hbox = gtk_hbox_new(false,0);
870 gtk_widget_show(dialog_hbox);
871 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),dialog_hbox);
873 // Create the left pane, containing the preview, the show commmand and the update buttons and the author name.
874 GtkWidget *left_pane = gtk_vbox_new(false,4);
875 gtk_widget_show(left_pane);
876 gtk_box_pack_start(GTK_BOX(dialog_hbox),left_pane,true,true,0);
878 GtkWidget *preview = gimp_zoom_preview_new(drawable);
879 gtk_widget_show(preview);
880 gtk_box_pack_start(GTK_BOX(left_pane),preview,true,true,0);
881 gimp_set_data("gmic_gui_preview",&preview,sizeof(GtkWidget*));
882 g_signal_connect(preview,"invalidated",G_CALLBACK(process_preview),0);
883 g_signal_connect_swapped(apply_button,"clicked",G_CALLBACK(gimp_preview_invalidate),preview);
885 GtkWidget *verbosity_hbuttonbox = gtk_hbutton_box_new();
886 gtk_widget_show(verbosity_hbuttonbox);
887 gtk_box_pack_start(GTK_BOX(left_pane),verbosity_hbuttonbox,false,false,0);
889 GtkWidget *verbosity_combobox = gtk_combo_box_new_text();
890 gtk_widget_show(verbosity_combobox);
891 gtk_combo_box_append_text(GTK_COMBO_BOX(verbosity_combobox),"Quiet mode");
892 gtk_combo_box_append_text(GTK_COMBO_BOX(verbosity_combobox),"Verbose mode");
893 gtk_combo_box_append_text(GTK_COMBO_BOX(verbosity_combobox),"Debug mode");
894 gtk_combo_box_set_active(GTK_COMBO_BOX(verbosity_combobox),get_verbosity_level());
895 gtk_container_add(GTK_CONTAINER(verbosity_hbuttonbox),verbosity_combobox);
896 g_signal_connect(verbosity_combobox,"changed",G_CALLBACK(on_verbosity_level_changed),0);
898 GtkWidget *update_hbuttonbox = gtk_hbutton_box_new();
899 gtk_widget_show(update_hbuttonbox);
900 gtk_box_pack_start(GTK_BOX(left_pane),update_hbuttonbox,false,false,0);
901 GtkWidget
902 *tmp_button = gtk_button_new_from_stock(GTK_STOCK_REFRESH),
903 *update_image = gtk_button_get_image(GTK_BUTTON(tmp_button)),
904 *update_button = gtk_button_new_with_mnemonic("_Update filters");
905 gtk_button_set_image(GTK_BUTTON(update_button),update_image);
906 gtk_widget_show(update_button);
907 gtk_container_add(GTK_CONTAINER(update_hbuttonbox),update_button);
908 g_signal_connect(update_button,"clicked",G_CALLBACK(on_update_button_clicked),(void*)dialog);
910 GtkWidget *about_label = gtk_label_new(NULL);
911 gtk_label_set_markup(GTK_LABEL(about_label),
912 "\n<span color=\"#666666\"><small>"
913 "<b>G'MIC</b> is proposed to you\n"
914 " by <i>David Tschumperle</i>"
915 "</small></span>");
916 gtk_widget_show(about_label);
917 gtk_box_pack_start(GTK_BOX(left_pane),about_label,false,false,0);
919 const unsigned int logo_width = 102, logo_height = 22;
920 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(data_logo,GDK_COLORSPACE_RGB,false,8,
921 logo_width,logo_height,3*logo_width,0,0);
922 GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
923 gtk_widget_show(image);
924 gtk_box_pack_start(GTK_BOX(left_pane),image,false,false,0);
926 // Create the middle pane, which contains the filters treeview.
927 GtkWidget *middle_pane = gtk_frame_new(NULL);
928 gtk_widget_show(middle_pane);
929 gtk_container_set_border_width(GTK_CONTAINER(middle_pane),4);
930 gtk_widget_set_size_request(middle_pane,250,-1);
931 gtk_box_pack_start(GTK_BOX(dialog_hbox),middle_pane,false,false,0);
933 GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL,NULL);
934 gtk_widget_show(scrolledwindow);
935 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow),GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
936 gtk_container_add(GTK_CONTAINER(middle_pane),scrolledwindow);
938 GtkWidget *treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(filter_store));
939 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
940 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(" Available filters :",renderer,"text",1,NULL);
941 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview),column);
943 GtkTreeSelection *select = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
944 gtk_tree_selection_set_mode(select,GTK_SELECTION_SINGLE);
945 g_signal_connect(G_OBJECT(select),"changed",G_CALLBACK(on_filter_changed),0);
946 g_signal_connect_swapped(select,"changed",G_CALLBACK(gimp_preview_invalidate),preview);
947 gtk_widget_show(treeview);
948 gtk_container_add(GTK_CONTAINER(scrolledwindow),treeview);
949 g_signal_connect(reset_button,"clicked",G_CALLBACK(on_dialog_reset_clicked),select);
950 g_signal_connect_swapped(reset_button,"clicked",G_CALLBACK(gimp_preview_invalidate),preview);
952 // Create the right pane which contains the parameters frame.
953 GtkWidget *parameters_frame = gtk_frame_new(NULL);
954 gtk_widget_show(parameters_frame);
955 gtk_container_set_border_width(GTK_CONTAINER(parameters_frame),4);
956 gtk_widget_set_size_request(parameters_frame,450,-1);
957 gtk_box_pack_start(GTK_BOX(dialog_hbox),parameters_frame,false,false,0);
958 gimp_set_data("gmic_gui_frame",¶meters_frame,sizeof(GtkWidget*));
959 create_parameters_gui(false);
961 // Show dialog window and wait for user response.
962 gtk_widget_show(dialog);
963 gtk_main();
965 // Destroy dialog box widget and free resources.
966 gtk_widget_destroy(dialog);
967 gtk_widget_destroy(tmp_button);
968 if (event_infos) delete[] event_infos;
969 return return_create_dialog;
970 }
972 // 'Run' function needed by GIMP plug-in API.
973 //-------------------------------------------
974 void gmic_run(const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) {
976 // Init plug-in variables.
977 static GimpParam values[1];
978 values[0].type = GIMP_PDB_STATUS;
979 *return_vals = values;
980 *nreturn_vals = 1;
981 name = 0;
982 nparams = 0;
983 GimpRunMode run_mode;
984 run_mode = (GimpRunMode)param[0].data.d_int32;
985 if (run_mode==GIMP_RUN_NONINTERACTIVE) {
986 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : ERROR, this plug-in cannot be run in non-interactive mode.\n");
987 values[0].data.d_status = GIMP_PDB_CALLING_ERROR;
988 return;
989 }
990 gmic_macros = 0;
991 filter_store = 0;
992 return_create_dialog = true;
993 path_home = getenv(cimg_OS!=2?"HOME":"APPDATA");
995 // Check that no instance of the plug-in is already running.
996 bool is_existing_instance = 0;
997 gimp_get_data("gmic_instance",&is_existing_instance);
998 if (is_existing_instance) {
999 std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Existing instance of the plug-in is already running.\n");
1000 return;
1001 }
1002 is_existing_instance = true;
1003 gimp_set_data("gmic_instance",&is_existing_instance,sizeof(bool));
1005 // Read user-defined configuration files '.gmic_def' and '.gmic', when possible.
1006 unsigned size_update = 0, size_custom = 0, size_def = sizeof(data_gmic4gimp_def);
1007 char filename_update[1024] = { 0 }, filename_custom[1024] = { 0 };
1008 std::sprintf(filename_update,"%s/.gmic4gimp_def.%d",path_home,gmic_version);
1009 std::sprintf(filename_custom,"%s/.gmic4gimp",path_home);
1010 std::FILE
1011 *file_update = std::fopen(filename_update,"r"),
1012 *file_custom = std::fopen(filename_custom,"r");
1013 if (file_update) {
1014 std::fseek(file_update,0,SEEK_END);
1015 size_update = (unsigned int)std::ftell(file_update);
1016 std::rewind(file_update);
1017 }
1018 if (file_custom) {
1019 std::fseek(file_custom,0,SEEK_END);
1020 size_custom = (unsigned int)std::ftell(file_custom);
1021 std::rewind(file_custom);
1022 }
1023 const unsigned int size_final = size_update + size_custom + size_def + 1;
1024 char *ptrd = gmic_macros = new char[size_final];
1025 if (size_custom) { ptrd+=std::fread(ptrd,1,size_custom,file_custom); std::fclose(file_custom); }
1026 if (size_update) { ptrd+=std::fread(ptrd,1,size_update,file_update); std::fclose(file_update); }
1027 if (size_def) { std::memcpy(ptrd,data_gmic4gimp_def,size_def); ptrd+=size_def; }
1028 *ptrd = 0;
1030 // Parse available G'MIC filters definitions.
1031 GtkTreeIter iter, parent[16];
1032 filter_store = gtk_tree_store_new(2,G_TYPE_UINT,G_TYPE_STRING);
1033 char line[256*1024] = { 0 }, entry[4096] = { 0 }, command[4096] = { 0 };
1034 char preview_command[4096] = { 0 }, arguments[4096] = { 0 };
1035 int level = 0;
1036 for (const char *data = gmic_macros; *data; ) {
1037 if (*data=='\n') ++data;
1038 else {
1039 if (std::sscanf(data,"%262143[^\n]\n",line)>0) data += cimg::strlen(line) + 1;
1040 arguments[0] = 0;
1041 if (line[0]=='#') {
1042 const int err = std::sscanf(line,"#@gimp %4095[^:]: %4095[^, ]%*c %4095[^, ]%*c %4095[^\n]",
1043 entry,command,preview_command,arguments);
1044 strparenthesis(entry);
1045 if (err==1) { // If entry is a menu folder.
1046 cimg::strclean(entry);
1047 char *nentry = entry;
1048 while (*nentry=='_') { ++nentry; --level; }
1049 if (level<0) level = 0;
1050 if (level>15) level = 15;
1051 cimg::strclean(nentry);
1052 if (*nentry) {
1053 gtk_tree_store_append(filter_store,&parent[level],level?&parent[level-1]:0);
1054 gtk_tree_store_set(filter_store,&parent[level],0,0,1,nentry,-1);
1055 ++level;
1056 }
1057 } else if (err>=2) { // If entry is a regular filter.
1058 cimg::strclean(entry);
1059 cimg::strclean(command);
1060 gmic_entries.insert(CImg<char>(entry,cimg::strlen(entry)+1));
1061 gmic_commands.insert(CImg<char>(command,cimg::strlen(command)+1));
1062 gmic_arguments.insert(CImg<char>(arguments,cimg::strlen(arguments)+1));
1063 if (err>=3) {
1064 cimg::strclean(preview_command);
1065 gmic_preview_commands.insert(CImg<char>(preview_command,cimg::strlen(preview_command)+1));
1066 }
1067 gtk_tree_store_append(filter_store,&iter,level?&parent[level-1]:0);
1068 gtk_tree_store_set(filter_store,&iter,0,gmic_entries.size,1,entry,-1);
1069 }
1070 }
1071 }
1072 }
1074 // Get currenty selected drawable and run image filter on it.
1075 GimpDrawable *drawable = gimp_drawable_get(param[2].data.d_drawable);
1076 gimp_tile_cache_ntiles(2*(drawable->width/gimp_tile_width()+1));
1077 if (run_mode==GIMP_RUN_INTERACTIVE) {
1078 if (create_dialog_gui(drawable)) {
1079 process_image(drawable,0);
1080 const char *commandline = get_commandline(false);
1081 if (commandline) { // Remember command line for the next use of the filter.
1082 char s_tmp[256] = { 0 };
1083 std::sprintf(s_tmp,"gmic_commandline%u",get_current_filter());
1084 gimp_set_data(s_tmp,commandline,cimg::strlen(commandline));
1085 }
1086 }
1087 } else if (run_mode==GIMP_RUN_WITH_LAST_VALS) {
1088 const unsigned int filter = get_current_filter();
1089 if (filter) {
1090 char s_tmp[256] = { 0 };
1091 std::sprintf(s_tmp,"gmic_commandline%u",filter);
1092 char commandline[4096] = { 0 };
1093 gimp_get_data(s_tmp,&commandline);
1094 process_image(drawable,commandline);
1095 }
1096 }
1098 // Free plug-in resources.
1099 delete[] gmic_macros;
1100 values[0].data.d_status = GIMP_PDB_SUCCESS;
1101 is_existing_instance = false;
1102 gimp_set_data("gmic_instance",&is_existing_instance,sizeof(bool));
1103 }
1105 // 'Query' function needed by GIMP plug-in API.
1106 //---------------------------------------------
1107 void gmic_query() {
1108 static const GimpParamDef args[] = {
1109 {GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"},
1110 {GIMP_PDB_IMAGE, "image", "(unused)"},
1111 {GIMP_PDB_DRAWABLE, "drawable", "Drawable to draw on"}
1112 };
1114 gimp_install_procedure("gmic", // name
1115 "G'MIC Toolbox", // blurb
1116 "G'MIC Toolbox", // help
1117 "David Tschumperle", // author
1118 "David Tschumperle", // copyright
1119 "2008-12-02", // date
1120 "_G'MIC Toolbox...", // menu_path
1121 "RGB*, GRAY*", // image_types
1122 GIMP_PLUGIN, // type
1123 G_N_ELEMENTS(args), // nparams
1124 0, // nreturn_vals
1125 args, // params
1126 0); // return_vals
1128 gimp_plugin_menu_register("gmic", "<Image>/Filters");
1129 }
1131 GimpPlugInInfo PLUG_IN_INFO = { 0, 0, gmic_query, gmic_run };
1132 MAIN();