1 /* file-selector.cc -- customized file selection dialog
2    Copyright (C) 2003, 2005, 2008  SEIKO EPSON Corporation
3 
4    This file is part of the `iscan' program.
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 
20    As a special exception, the copyright holders give permission
21    to link the code of this program with the esmod library and
22    distribute linked combinations including the two.  You must obey
23    the GNU General Public License in all respects for all of the
24    code used other then esmod.
25  */
26 
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30 
31 #include "file-selector.h"
32 #include "gettext.h"
33 #define  _(msg_id)	gettext (msg_id)
34 
35 #include <cstdio>
36 #include <cstdlib>
37 #include <cstring>
38 #include <unistd.h>
39 
40 #include "pisa_aleart_dialog.h"
41 #include "pisa_enums.h"
42 #include "pisa_error.h"
43 
44 #include "../lib/imgstream.hh"
45 #include "../lib/pnmstream.hh"
46 #include "../lib/pngstream.hh"
47 #include "../lib/jpegstream.hh"
48 
49 #ifndef DEBUG
50 #define g_print(...)
51 #endif
52 
53 
54 #ifdef DONT_HAVE_GTK_2
55 #undef HAVE_GTK_2
56 #endif
57 
58 #ifndef HAVE_GTK_2
59 #define G_CALLBACK  GTK_SIGNAL_FUNC
60 #define g_signal_stop_emission_by_name gtk_signal_emit_stop_by_name
61 #define g_signal_connect_swapped gtk_signal_connect_object
62 #define g_signal_connect gtk_signal_connect
63 #define GTK1_OBJ(obj) GTK_OBJECT (obj)
64 GtkWidget *			// "stolen" from GTK+2.0
gtk_widget_get_parent(GtkWidget * widget)65 gtk_widget_get_parent (GtkWidget *widget)
66 {
67   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
68 
69   return widget->parent;
70 }
71 #else
72 #define GTK1_OBJ(obj) obj
73 #endif
74 
75 struct menu_info		// helper struct for callbacks
76 {
77   file_selector *fs;
78   int            index;
79 };
80 
81 
82 // callback implementations
83 
84 static void
_destroy(file_selector * fs)85 _destroy (file_selector *fs)
86 {
87   g_print ("%s\n", __func__);
88 
89   fs->destroy (true);
90 }
91 
92 #if 0
93 static void
94 _click_ng( GtkWidget *, file_selector * )
95 {
96   DBG_FS fprintf( stderr, "_click_ng\n" );
97 
98   gtk_main_quit();
99 }
100 #endif
101 
102 static void
_click_ok(file_selector * fs)103 _click_ok (file_selector *fs)
104 {
105   g_print ("%s\n", __func__);
106 
107   if (fs->save_pathname ())
108     {
109       fs->destroy ();
110       gtk_main_quit ();
111     }
112 }
113 
114 #if 0
115 #ifndef HAVE_GTK_2
116 static void
117 _select_filename( GtkCList *clist, gint row, gint column, GdkEventButton *,
118 		  file_selector *fs )
119 {
120   DBG_FS fprintf( stderr, "_select_filename\n" );
121 
122   char *text;
123   gtk_clist_get_text( clist, row, column, &text );
124 
125   fs->set_filename( text );
126 }
127 #endif
128 
129 static void
130 _change_filename( GtkEditable *editable, file_selector *fs )
131 {
132   DBG_FS fprintf( stderr, "_change_filename\n" );
133 
134   gchar *name = gtk_editable_get_chars( editable, 0, -1 );
135 
136   gint cursor = gtk_editable_get_position( editable );
137   gtk_signal_handler_block_by_func( GTK_OBJECT( editable ),
138 				    GTK_SIGNAL_FUNC( _change_filename ),
139 				    fs );
140   fs->set_filename( name, true );
141   gtk_signal_handler_unblock_by_func( GTK_OBJECT( editable ),
142 				      GTK_SIGNAL_FUNC( _change_filename ),
143 				      fs );
144   gtk_editable_set_position( editable, cursor );
145 
146   g_free( name );
147 }
148 #endif
149 
150 static void
_change_format(GtkWidget *,struct menu_info * id)151 _change_format (GtkWidget *, struct menu_info *id)
152 {
153   id->fs->change_format( id->index );
154 }
155 
156 static void
_delete_text(GtkEditable * ed,gint start,gint end,file_selector * fs)157 _delete_text (GtkEditable *ed, gint start, gint end, file_selector *fs)
158 {
159   g_print ("%s\n", __func__);
160 
161 #ifndef HAVE_GTK_2
162   gtk_signal_handler_block_by_func (GTK_OBJECT (ed),
163 				    GTK_SIGNAL_FUNC (_delete_text), fs);
164   fs->delete_text (start, end);
165   gtk_signal_handler_unblock_by_func (GTK_OBJECT (ed),
166 				      GTK_SIGNAL_FUNC (_delete_text), fs);
167 #else
168   g_signal_handlers_block_by_func (ed, (gpointer) _delete_text, fs);
169   fs->delete_text (start, end);
170   g_signal_handlers_unblock_by_func (ed, (gpointer) _delete_text, fs);
171 #endif
172 
173   g_signal_stop_emission_by_name (GTK1_OBJ(ed), "delete-text");
174 }
175 
176 static void
_insert_text(GtkEditable * ed,gchar * text,gint len,gint * pos,file_selector * fs)177 _insert_text (GtkEditable *ed, gchar *text, gint len, gint *pos,
178 	      file_selector *fs)
179 {
180   g_print ("%s\n", __func__);
181 
182 #ifndef HAVE_GTK_2
183   gtk_signal_handler_block_by_func (GTK_OBJECT (ed),
184 				    GTK_SIGNAL_FUNC ( _insert_text), fs);
185   fs->insert_text (text, len, pos);
186   gtk_signal_handler_unblock_by_func (GTK_OBJECT (ed),
187 				      GTK_SIGNAL_FUNC (_insert_text), fs);
188 #else
189   g_signal_handlers_block_by_func (ed, (gpointer) _insert_text, fs);
190   fs->insert_text (text, len, pos);
191   g_signal_handlers_unblock_by_func (ed, (gpointer) _insert_text, fs);
192 #endif
193 
194   g_signal_stop_emission_by_name (GTK1_OBJ(ed), "insert-text");
195 }
196 
197 static void
_change_seqnum(GtkAdjustment *,file_selector * fs)198 _change_seqnum (GtkAdjustment *, file_selector *fs)
199 {
200   fs->change_seqnum ();
201 }
202 
203 static void
_change_digits(GtkAdjustment *,file_selector * fs)204 _change_digits (GtkAdjustment *, file_selector *fs)
205 {
206   fs->change_digits ();
207 }
208 
209 
210 // 	class implementation
211 
212 			 	// define static variables
213 const file_selector::format
214 file_selector::_format[]  = {
215   { "PNM", 0, "pnm", "pnm" },
216   { "PNG", 0, "png", "png" },
217   { "JPEG", 0, "jpg", "jpg|jpeg" },
218   { "TIFF", 0, "tiff", "tiff" },
219   { 0 }				// array terminator
220 };
221 
222 void
init()223 file_selector::init ()
224 {
225   g_print ("%s\n", __func__);
226 
227   _widget   = NULL;
228   _filename = NULL;
229   _pathname = NULL;
230 
231   _using_adf = false;
232   _file_type = 0;
233   _deleted_all = false;
234 
235   _number = -1;
236   _seqnum = NULL;
237   _digits = NULL;
238 
239   int size = 1;			// terminating { 0 } element
240   for (int i = 0; 0 != _format[i].name; ++i)
241     ++size;
242   _fmt = new const format * [size];
243 				// okay, so we may waste a few
244 
245   int cnt = 0;
246   int len = 0;
247   for (int i = 0; i < size - 1; ++i)
248     {
249       _fmt[cnt] = &_format[i];	// FIXME: check support
250       len += strlen (_fmt[cnt]->rgx);
251       ++len;			// for the '|'
252       ++cnt;
253     }
254   _fmt[cnt] = 0;		// array terminator
255 
256   _ext_regex = new char[len];	// overwrite last '|' with '\0'
257   char *pos = _ext_regex;
258   for (int i = 0; _fmt[i]; ++i)
259     {
260       strcpy (pos, _fmt[i]->rgx);
261       pos += strlen (_fmt[i]->rgx);
262       *pos++ = '|';		// overwrite '\0', then advance
263     }
264   *--pos = '\0';		// go back one, then terminate
265 
266   g_print ("%s: ext_regex =`%s'\n", __func__, _ext_regex);
267 }
268 
269 void
create_window(GtkWidget * parent,int option,bool enable_start_button,bool monochrome)270 file_selector::create_window( GtkWidget *parent, int option,
271 			      bool enable_start_button, bool monochrome)
272 {
273   create_window (GTK_WINDOW (gtk_widget_get_parent (parent)), parent,
274 		 (   PISA_OP_ADF     == option
275 		  || PISA_OP_ADFDPLX == option
276 		  || enable_start_button));
277 }
278 
279 void
create_window(GtkWindow * window,GtkWidget * parent,bool do_consecutive_scanning)280 file_selector::create_window (GtkWindow *window, GtkWidget *parent,
281 			      bool do_consecutive_scanning)
282 {
283   g_print ("%s: enter\n", __func__);
284 
285   _parent    = parent;
286   _using_adf = do_consecutive_scanning;
287 
288   _widget = GTK_FILE_SELECTION (gtk_file_selection_new (PACKAGE));
289 
290   if (!_widget)
291     throw pisa_error( PISA_STATUS_NO_MEM );
292 
293   gtk_window_set_transient_for (GTK_WINDOW (_widget), window);
294   gtk_file_selection_hide_fileop_buttons (_widget);
295   add_dialog_extensions ();
296 				// needs to be called before we canonize
297 
298   if (!_filename)
299     _filename = canonize ("default");
300 
301   free (_pathname);
302   _pathname = 0;
303 				// FIXME? 0 == _filename
304   gtk_file_selection_set_filename (_widget, _filename);
305 
306   g_signal_connect_swapped (GTK1_OBJ (_widget->ok_button), "clicked",
307 			    G_CALLBACK (_click_ok), GTK1_OBJ (this));
308   g_signal_connect_swapped (GTK1_OBJ (_widget->cancel_button), "clicked",
309 			    G_CALLBACK (_destroy), GTK1_OBJ (this));
310   g_signal_connect_swapped (GTK1_OBJ (_widget), "delete_event",
311 			    G_CALLBACK (_destroy), GTK1_OBJ (this));
312   g_signal_connect_swapped (GTK1_OBJ (_widget), "destroy",
313 			    G_CALLBACK (gtk_main_quit), GTK1_OBJ (_widget));
314 
315   // We need to interfere with users selecting and editing filenames
316   // to guarantee a correct extension and the appropriate templating
317   // bit when _using_adf.
318   g_signal_connect (GTK1_OBJ (_widget->selection_entry), "delete-text",
319 		    G_CALLBACK (_delete_text), this);
320   g_signal_connect (GTK1_OBJ (_widget->selection_entry), "insert-text",
321 		    G_CALLBACK (_insert_text), this);
322 
323   gtk_widget_show (GTK_WIDGET (_widget));
324   gtk_grab_add (GTK_WIDGET (_widget));
325   gtk_main ();
326   if (_widget)
327     {
328       gtk_grab_remove (GTK_WIDGET (_widget));
329     }
330   g_print ("%s: exit\n", __func__);
331 }
332 
333 void
destroy()334 file_selector::destroy ()
335 {
336   g_print ("%s\n", __func__);
337 
338   if (_using_adf)
339     {
340       hide ();
341     }
342   else
343     {
344       destroy (true);
345     }
346 }
347 
348 void
destroy(bool really)349 file_selector::destroy (bool really)
350 {
351   g_print ("%s (%i)\n", __func__, really);
352 
353   free (_filename);
354   _filename = NULL;
355 
356   if (_widget)
357     {
358       gtk_widget_destroy (GTK_WIDGET (_widget));
359     }
360   _widget = NULL;
361 
362   if (_parent)
363     {
364       gtk_widget_set_sensitive (_parent, true);
365     }
366   _parent = NULL;
367 
368   _number = -1;
369   _seqnum =  NULL;		// gtk_widget_destroy cleans these up
370   _digits =  NULL;
371 
372   _using_adf = false;
373   _file_type = 0;
374   _deleted_all = false;
375 
376   delete [] _fmt;
377   _fmt = NULL;
378   delete [] _ext_regex;
379   _ext_regex = NULL;
380 }
381 
382 char *
get_filename() const383 file_selector::get_filename () const
384 {
385   return (_pathname ? strdup (_pathname) : NULL);
386 }
387 
388 bool
set_filename(const char * text,bool edit)389 file_selector::set_filename( const char *text, bool edit )
390 {
391   g_print ("file_selector::set_filename( %s, %d )\n", text, edit);
392 
393   if (!_filename)
394     return false;		// logical error but return for now
395 
396   if (!text
397       || !*text			// empty string
398       || text == _filename || 0 == strcmp( text, _filename ))
399     return false;		// nothing to change
400 
401   g_print ("filename bfore = `%s'\n", _filename);
402   g_print ("filename inGUI = `%s'\n",
403 	   gtk_file_selection_get_filename( _widget ));
404 
405   char *old_name  = _filename;	// hang on to original
406 #ifdef HAVE_GTK_2
407   {				// editing file extension not allowed
408     int n = strrchr( _filename, '.') - _filename;
409     edit = (0 == strncmp (text, _filename, n));
410   }
411 #endif
412   _filename = (edit ? validate( text ) : canonize( text ));
413 
414   if (_filename)		// we got a new name
415     free( old_name );
416   else
417     _filename = old_name;	// revert to original
418 
419 				// update the GUI
420   gtk_file_selection_set_filename( _widget, _filename );
421 
422   g_print ("filename after = `%s'\n", _filename );
423   g_print ("filename inGUI = `%s'\n",
424 	   gtk_file_selection_get_filename( _widget ) );
425 
426   g_print ("set_filename returns %d\n", _filename != old_name );
427   return _filename != old_name;
428 }
429 
430 int
get_sequence_number() const431 file_selector::get_sequence_number () const
432 {
433   return _number;
434 }
435 
436 void
set_sequence_number(int number)437 file_selector::set_sequence_number (int number)
438 {
439   if (!_using_adf || !_seqnum)
440     return;
441 
442   _number = number;
443 
444   while (_number > _seqnum->upper)
445     {
446       int max = int (_seqnum->upper) * 10 + 9;
447       _seqnum->upper = max;
448     }
449   gtk_adjustment_set_value (_seqnum, _number);
450   gtk_adjustment_changed (_seqnum);
451 }
452 
453 bool
save_pathname()454 file_selector::save_pathname()
455 {
456   g_print ("%s\n", __func__);
457 
458   if (!_widget)
459     {
460       g_print ("%s: widget's gone!\n", __func__);
461       free (_pathname);
462       _pathname = NULL;
463       return true;
464     }
465 
466   const char *current = gtk_file_selection_get_filename (_widget);
467 
468   if (128 < strlen (current)	// blame the spec for this one
469       || !permission (current))
470     {
471       show_message (pisa_error (PISA_ERR_FILENAME));
472       return false;
473     }
474   if (0 == access (current, F_OK))
475     {
476       bool ok = show_message (pisa_error (PISA_ERR_OVERWRITE), true);
477       if (!ok)
478 	return false;
479     }
480 
481   char *new_name = (char *) malloc ((strlen (current) + 1)
482 				    * sizeof (char));
483   if (!new_name)
484     throw pisa_error (PISA_STATUS_NO_MEM);
485 
486   strcpy (new_name, current);
487   free (_pathname);
488   _pathname = new_name;
489   free (_filename);
490   _filename = 0;
491 
492   g_print ("%s: pathname = `%s'\n", __func__, _pathname);
493 
494   return true;
495 }
496 
497 void
hide() const498 file_selector::hide () const
499 {
500   g_print ("%s\n", __func__);
501 
502   if (_using_adf)
503     {
504       g_print ("%s: calling gtk_widget_hide\n", __func__);
505       if (_widget)
506 	{
507 	  gtk_widget_hide (GTK_WIDGET (_widget));
508 	}
509       if (_parent)
510 	{
511 	  gtk_widget_set_sensitive (_parent, true);
512 	}
513     }
514 }
515 
516 void
set_entry(const char * text)517 file_selector::set_entry (const char *text)
518 {
519   g_print ("%s (%s)\n", __func__, text);
520 
521   if (!_filename)
522     return;			// logical error but return for now
523 
524   if (!text
525       || !*text			// empty string
526       || text == _filename
527       || 0 == strcmp (text, _filename))
528     return;			// nothing to change
529 
530   char *new_name = (canonize (text));
531   if (new_name)
532     {
533       free (_filename);
534       _filename = new_name;
535 #ifndef HAVE_GTK_2
536       gtk_signal_handler_block_by_func (GTK_OBJECT (_widget->selection_entry),
537 					GTK_SIGNAL_FUNC (_delete_text), this);
538       gtk_signal_handler_block_by_func (GTK_OBJECT (_widget->selection_entry),
539 					GTK_SIGNAL_FUNC (_insert_text), this);
540       gtk_entry_set_text (GTK_ENTRY (_widget->selection_entry), _filename);
541       gtk_signal_handler_unblock_by_func (GTK_OBJECT (_widget->selection_entry),
542 					  GTK_SIGNAL_FUNC (_insert_text), this);
543       gtk_signal_handler_unblock_by_func (GTK_OBJECT (_widget->selection_entry),
544 					  GTK_SIGNAL_FUNC (_delete_text), this);
545 #else
546       g_signal_handlers_block_by_func (GTK_EDITABLE (_widget->selection_entry),
547 				       (gpointer) _delete_text, this);
548       g_signal_handlers_block_by_func (GTK_EDITABLE (_widget->selection_entry),
549 				       (gpointer) _insert_text, this);
550       gtk_entry_set_text (GTK_ENTRY (_widget->selection_entry), _filename);
551       g_signal_handlers_unblock_by_func (GTK_EDITABLE (_widget->selection_entry),
552 					 (gpointer) _insert_text, this);
553       g_signal_handlers_unblock_by_func (GTK_EDITABLE (_widget->selection_entry),
554 					 (gpointer) _delete_text, this);
555 #endif
556     }
557 }
558 
559 void
delete_text(gint start,gint end)560 file_selector::delete_text (gint start, gint end)
561 {
562   g_print ("%s (%d,%d)\n", __func__, start, end);
563   g_print ("%s orig _filename '%s'\n", __func__, _filename);
564 
565   _deleted_all = ((end - start) == (gint) strlen (_filename));
566 
567   if (0 > end) end = strlen (_filename) + 1;
568   int dot = strrchr (_filename, (_using_adf ? '-' : '.')) - _filename;
569   if (end > dot) end = dot;
570 
571   if (start < end)
572     {
573       GtkWidget *ed = _widget->selection_entry;
574       gtk_editable_delete_text (GTK_EDITABLE (ed), start, end);
575       _filename = strdup (gtk_entry_get_text (GTK_ENTRY (ed)));
576 
577       g_print ("%s new  _filename '%s'\n", __func__, _filename);
578     }
579 }
580 
581 void
insert_text(gchar * text,gint len,gint * pos)582 file_selector::insert_text (gchar *text, gint len, gint *pos)
583 {
584   g_print ("%s (%s,%d,%d)\n", __func__, text, len, (pos ? *pos : -1));
585   g_print ("%s orig _filename '%s'\n", __func__, _filename);
586 
587   if (_deleted_all && pos && (0 == *pos))
588     {
589       _deleted_all = false;
590       text = canonize (text);
591       len  = strlen (text);
592 
593       GtkEditable *ed = GTK_EDITABLE (_widget->selection_entry);
594 #ifndef HAVE_GTK_2
595       gtk_signal_handler_block_by_func (GTK_OBJECT (ed),
596 					GTK_SIGNAL_FUNC (_delete_text), this);
597       gtk_editable_delete_text (ed, 0, -1);
598       gtk_signal_handler_unblock_by_func (GTK_OBJECT (ed),
599 					GTK_SIGNAL_FUNC (_delete_text), this);
600 #else
601       g_signal_handlers_block_by_func (ed, (gpointer) _delete_text, this);
602       gtk_editable_delete_text (ed, 0, -1);
603       g_signal_handlers_unblock_by_func (ed, (gpointer) _delete_text, this);
604 #endif
605     }
606 
607   int dot = strrchr (_filename, (_using_adf ? '-' : '.')) - _filename;
608 
609   if (pos && (*pos <= dot))
610     {
611       GtkWidget *ed = _widget->selection_entry;
612       gtk_editable_insert_text (GTK_EDITABLE (ed), text, len, pos);
613 #ifndef HAVE_GTK_2
614       {				// not getting delete-event's for some
615 				// reason
616 	char *name = canonize (gtk_entry_get_text (GTK_ENTRY (ed)));
617 	gtk_entry_set_text (GTK_ENTRY (ed), name);
618 	free (name);
619       }
620 #endif
621       free (_filename);
622       _filename = strdup (gtk_entry_get_text (GTK_ENTRY (ed)));
623 
624       g_print ("%s new  _filename '%s'\n", __func__, _filename);
625     }
626 }
627 
628 void
change_format(int index)629 file_selector::change_format (int index)
630 {
631   g_print ("%s (%d)\n", __func__, index);
632 
633   _file_type = index;
634 				// reflect changes in filename
635   char *canon = canonize (_filename);
636   set_entry (canon);
637   free (canon);
638 }
639 
640 void
change_seqnum()641 file_selector::change_seqnum ()
642 {
643   g_print ("%s\n", __func__);
644 
645   _number = int (_seqnum->value);
646 }
647 
648 void
change_digits()649 file_selector::change_digits ()
650 {
651   g_print ("%s\n", __func__);
652 
653   int max = 9;			// there's at least one digit
654   for (int i = 2; i <= int (_digits->value); ++i)
655     {
656       max *= 10;
657       max +=  9;
658     }
659 
660   if (_seqnum)
661     {
662       _seqnum->upper = max;
663       if (max < _seqnum->value)	// also updates the GUI
664 	gtk_adjustment_set_value (_seqnum, max);
665 
666       gtk_adjustment_changed (_seqnum);
667     }
668 
669   if (_filename)		// reflect changes in filename
670     {
671       char *canon = canonize (_filename);
672       set_entry (canon);
673       free (canon);
674     }
675 }
676 
677 // caller needs to free returned char *
678 char *
validate(const char * text) const679 file_selector::validate (const char *text) const
680 {
681   if (!text || 0 == *text)
682     return 0;
683 
684   g_print ("%s (%s)\n", __func__, text);
685 
686   char *valid = 0;
687 
688   // match extension with filetype
689   // match number of #'s with number of digits iff using ADF
690 
691   const char *regex_fmt = "^(.+)(-#{%d}){%d}\\.(%s)$";
692   const char *ext_regex = _fmt[_file_type]->rgx;
693   char *regex = new char[strlen( regex_fmt ) - 3	// 3 formatters
694 			 + strlen( ext_regex ) + 1];	// final '\0'
695   sprintf(regex, regex_fmt,
696 	  (_digits    ? int (_digits->value) : 0),
697 	  (_using_adf ?      1               : 0),
698 	  ext_regex);
699 
700   regex_t *comp_regex = new regex_t;
701   int comp = regcomp (comp_regex, regex, REG_EXTENDED);
702 
703   if (0 == comp)
704     {
705       int result = regexec (comp_regex, text, 0, 0, 0);
706       if (0 == result)
707 	{
708 	  valid = (char *) malloc ((strlen (text) + 1)
709 				   * sizeof (char));
710 	  if (valid)
711 	    strcpy (valid, text);
712 	  else
713 	    {			// FIXME!
714 	      // no memory
715 	    }
716 	}
717       else
718 	if (REG_NOMATCH != result)
719 	  regerror (comp, comp_regex);
720     }
721   else
722     regerror (comp, comp_regex);
723 
724   regfree (comp_regex);
725   delete comp_regex;
726   delete[] regex;
727 
728   return valid;
729 }
730 
731 // caller needs to free returned char *, because we validate()
732 char *
canonize(const char * text) const733 file_selector::canonize (const char *text) const
734 {
735   if (!text || 0 == *text)
736     return 0;
737 
738   g_print ("%s (%s)\n", __func__, text);
739 
740   char *interim = 0;
741 
742   {	    // "replace" existing extension with one matching filetype
743     const char * ext_regex_fmt = "^(.+)\\.(%s)$";
744     char *regex = new char[strlen (ext_regex_fmt) - 1
745 			   + strlen (_ext_regex) + 1];
746     sprintf (regex, ext_regex_fmt, _ext_regex);
747 
748     regex_t *comp_regex = new regex_t;
749     int comp = regcomp (comp_regex, regex, REG_EXTENDED);
750 
751     if (0 == comp)
752       {
753 	const char *file_ext = _fmt[_file_type]->ext;
754 
755 	size_t      nsub  = comp_regex->re_nsub + 1;
756 	regmatch_t *match = new regmatch_t[nsub];
757 
758 	int result = regexec (comp_regex, text, nsub, match, 0);
759 	if (0 == result)
760 	  {			// replace existing extension
761 	    // FIXME: keep extension if in _fmt[_file_type]->rgx
762 	    int pre = 1;
763 
764 	    regoff_t l_pre = match[pre].rm_eo - match[pre].rm_so;
765 
766 	    size_t len = l_pre + strlen (file_ext) + 2;
767 				// period and terminating '\0'
768 	    interim = (char *) malloc (len * sizeof (char));
769 	    if (interim)
770 	      {
771 		char *c = interim;
772 		{		// copy prefix
773 		  const char *p = text + match[pre].rm_so;
774 		  while (0 < l_pre--)
775 		    *c++ = *p++;
776 		}
777 		{		// append extension
778 		  *c++ = '.';
779 		  while (*file_ext)
780 		    *c++ = *file_ext++;
781 		  *c++ = '\0';
782 		}
783 	      }
784 	    else
785 	      {			// FIXME!
786 		// no memory
787 	      }
788 	  } // 0 == result
789 	else
790 	  if (REG_NOMATCH == result)
791 	    {			// append a new extension
792 	      size_t len = strlen (text) + strlen (file_ext) + 2;
793 				// period and terminating '\0'
794 	      interim = (char *) malloc (len * sizeof (char));
795 	      if (interim)
796 		sprintf (interim, "%s.%s", text, file_ext);
797 	      else
798 		{		// FIXME!
799 		  // no memory
800 		}
801 	    }
802 	  else
803 	    regerror (result, comp_regex);
804 	delete[] match;
805       }	// 0 == comp
806     else
807       regerror (comp, comp_regex);
808 
809     regfree (comp_regex);
810     delete comp_regex;
811     delete[] regex;
812   }
813 
814   // FIXME: free interim if one of the new's throws
815 
816   if (interim && _using_adf)
817     {	   // adjust the number of #'s to the current number of digits
818 
819       const char * adf_regex_fmt = "^(((.+)-#+)|(.+))\\.(%s)$";
820       char *regex = new char[strlen (adf_regex_fmt) - 1
821 			     + strlen (_ext_regex) + 1];
822       sprintf (regex, adf_regex_fmt, _ext_regex);
823 
824       regex_t *comp_regex = new regex_t;
825       int comp = regcomp (comp_regex, regex, REG_EXTENDED);
826 
827       if (0 == comp)
828 	{
829 	  size_t      nsub  = comp_regex->re_nsub + 1;
830 	  regmatch_t *match = new regmatch_t[nsub];
831 
832 	  int result = regexec (comp_regex, interim, nsub, match, 0);
833 	  if (0 == result)
834 	    {			// see adf_regex_fmt
835 	      int pre = (-1 == match[4].rm_so
836 			 ? 3	// already contains sequence template
837 			 : 4);
838 	      int ext = 5;
839 
840 	      regoff_t l_pre = match[pre].rm_eo - match[pre].rm_so;
841 	      regoff_t l_ext = match[ext].rm_eo - match[ext].rm_so;
842 
843 	      size_t len = l_pre + l_ext + int( _digits->value ) + 3;
844 				// hyphen, period and terminating '\0'
845 	      char *name = (char *) malloc (len * sizeof (char));
846 	      if (name)
847 		{
848 		  char *c = name;
849 		  {		// copy prefix
850 		    const char *p = interim + match[pre].rm_so;
851 		    while (0 < l_pre--)
852 		      *c++ = *p++;
853 		  }
854 		  {		// insert -### part
855 		    *c++ = '-';
856 		    for (int i = 0; i < int (_digits->value); ++i)
857 		      *c++ = '#';
858 		  }
859 		  {		// append extension
860 		    *c++ = '.';
861 		    const char *p = interim + match[ext].rm_so;
862 		    while (0 < l_ext--)
863 		      *c++ = *p++;
864 		  }
865 		  *c = '\0';
866 		} // name
867 	      else
868 		{		// FIXME!
869 		  // no memory
870 		}
871 	      free (interim);
872 	      interim = name;
873 	    } // 0 == result
874 	  else
875 	    if (REG_NOMATCH != result)
876 	      regerror (result, comp_regex);
877 	  delete[] match;
878 	} // 0 == comp
879       else
880 	regerror (comp, comp_regex);
881 
882       regfree (comp_regex);
883       delete comp_regex;
884       delete[] regex;
885     } // _using_adf
886 
887   char *result = validate (interim);
888   free (interim);		// clean up what we allocated
889 
890   return result;
891 }
892 
893 void
add_dialog_extensions()894 file_selector::add_dialog_extensions ()
895 {
896   g_print ("%s: enter\n", __func__);
897 
898   GtkWidget *frame = gtk_frame_new (_("Save Options"));
899   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
900 
901   GtkTable *opts
902     = (GtkTable *) gtk_table_new ((_using_adf ? 3 : 1), 2, TRUE);
903   gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (opts));
904 
905   GtkWidget *w = 0;
906   {				// file type selector
907     w = gtk_label_new (_("Determine File Type:"));
908     gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
909     gtk_table_attach_defaults (opts, w, 0, 1, 0, 1);
910     gtk_misc_set_alignment (GTK_MISC (w), 0.1, 0.5);
911     gtk_widget_show (w);
912 
913     w = gtk_option_menu_new ();
914     {
915       GtkWidget *m = gtk_menu_new ();
916 
917       for (int i = 0; _fmt[i]; ++i)
918 	{
919 	  GtkWidget *mi = gtk_menu_item_new_with_label (_fmt[i]->name);
920 	  gtk_menu_append (GTK_MENU (m), mi);
921 	  gtk_widget_show (mi);
922 
923 	  struct menu_info *id = new struct menu_info;
924 	  id->fs = this;
925 	  id->index = i;
926 
927 	  g_signal_connect (GTK1_OBJ (mi), "activate",
928 			    G_CALLBACK (_change_format), (void *) id);
929 
930 	  // FIXME! ugly kludge to "check" for support
931 	  if (0 == strcmp ("PNM", _fmt[i]->name))
932 	    {
933 	      gtk_widget_set_sensitive (mi, iscan::pnmstream::is_usable ());
934 	    }
935 	  if (0 == strcmp ("PNG", _fmt[i]->name))
936 	    {
937 	      gtk_widget_set_sensitive (mi, iscan::pngstream::is_usable ());
938 	    }
939 	  if (0 == strcmp ("JPEG", _fmt[i]->name))
940 	    {
941 	      gtk_widget_set_sensitive (mi, iscan::jpegstream::is_usable ());
942 	    }
943 	  // FIXME: this is only here to show what the GUI would look like
944 	  //	TIFF support is planned but was not ready in time for this
945 	  //	release :-(
946 	  if (0 == strcmp ("TIFF", _fmt[i]->name))
947 	    {
948 	      gtk_widget_set_sensitive (mi, false);
949 	    }
950 	}
951       gtk_option_menu_set_menu( GTK_OPTION_MENU( w ), m );
952     }
953     gtk_table_attach_defaults (opts, w, 1, 2, 0, 1);
954     gtk_widget_show (w);
955   }
956 
957   if (_using_adf)
958     {
959       g_print ("%s: using adf\n", __func__);
960 
961  				// sequence number selector
962       w = gtk_label_new (_("Start filing at:"));
963       gtk_table_attach_defaults (opts, w, 0, 1, 1, 2);
964       gtk_misc_set_alignment (GTK_MISC (w), 0.1, 0.5);
965       gtk_widget_show (w);
966 
967       if (!_seqnum)
968 	_seqnum = (GtkAdjustment *) gtk_adjustment_new (1, 0, 9, 1, 10, 0);
969       if (_seqnum)
970 	_number = int (_seqnum->value);
971       else			// play it safe and make sure
972 	_number = -1;
973 
974       w = gtk_spin_button_new (_seqnum, 0, 0);
975       gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (w), true);
976       gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (w), true);
977       gtk_table_attach_defaults (opts, w, 1, 2, 1, 2);
978       gtk_widget_show (w);
979 
980 				// number of digits selector
981       w = gtk_label_new (_("Number of digits:"));
982       gtk_table_attach_defaults (opts, w, 0, 1, 2, 3);
983       gtk_misc_set_alignment (GTK_MISC (w), 0.1, 0.5);
984       gtk_widget_show (w);
985 
986       if (!_digits)
987 	_digits = (GtkAdjustment *) gtk_adjustment_new (3, 1, 6, 1,  1, 0);
988       if (_digits)
989 	change_digits();
990 
991       w = gtk_spin_button_new (_digits, 0, 0);
992       gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (w), true);
993       gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (w), true);
994       gtk_table_attach_defaults (opts, w, 1, 2, 2, 3);
995       gtk_widget_show (w);
996 
997       g_signal_connect (GTK1_OBJ (_seqnum), "value_changed",
998 			G_CALLBACK (_change_seqnum), this);
999       g_signal_connect (GTK1_OBJ (_digits), "value_changed",
1000 			G_CALLBACK (_change_digits), this);
1001    }
1002 
1003   gtk_box_pack_end (GTK_BOX (_widget->main_vbox), frame, false, false, 0);
1004 
1005   gtk_widget_show (frame);
1006   gtk_widget_show (GTK_WIDGET (opts));
1007 
1008   g_print ("%s: exit\n", __func__);
1009 }
1010 
1011 void
add_dialog_extensions(bool)1012 file_selector::add_dialog_extensions (bool)
1013 {
1014   return add_dialog_extensions ();
1015 }
1016 
1017 bool
permission(const char * file) const1018 file_selector::permission( const char *file ) const
1019 {
1020   if (0 == access( file, F_OK ))	// file exists
1021     return (0 == access( file, W_OK ));	// whether we can write to it
1022 
1023   // check write access to the directory (note that we need execute
1024   // privileges as well)
1025 
1026   char *slash = strrchr( file, '/');
1027   *slash = '\0';		// temporarily truncate to dirname
1028   const char *dir = (file == slash
1029 		     ? "/"	// whoops!, file in root directory
1030 		     : file);
1031 
1032   bool w_ok = false;		// assume the worst
1033   if (0 == access( dir, F_OK ))
1034     w_ok = (0 == access( dir, W_OK | X_OK ));
1035 
1036   *slash = '/';			// restore filename
1037 
1038   return w_ok;
1039 }
1040 
1041 bool
show_message(const pisa_error & oops,bool yes_no) const1042 file_selector::show_message( const pisa_error& oops, bool yes_no ) const
1043 {
1044   aleart_dialog dlg;
1045 
1046   if (yes_no)			// binary question
1047     return (1 == dlg.message_box( GTK_WIDGET( _widget ),
1048 				  oops.get_error_string(),
1049 				  _( "  Yes  " ), _( "  No  " ) ));
1050 
1051   dlg.message_box( GTK_WIDGET( _widget ), oops.get_error_string() );
1052   return true;
1053 }
1054 
1055 void
regerror(int code,regex_t * regex) const1056 file_selector::regerror (int code, regex_t *regex) const
1057 {
1058   size_t length = ::regerror (code, regex, 0, 0);
1059   char *message = new char[length];
1060 
1061   ::regerror (code, regex, message, length);
1062   fprintf (stderr, "%s\n", message);
1063 
1064   delete[] message;
1065 }
1066