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