1 /* pisa_view_manager.cc
2    Copyright (C) 2001, 2004, 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 #include <config.h>
28 
29 #include "gettext.h"
30 #define  _(msg_id)	gettext (msg_id)
31 
32 /*------------------------------------------------------------*/
33 #include <gtk/gtk.h>
34 #ifndef HAVE_GTK_2
35 #include <gdk_imlib.h>
36 #endif
37 #include <fcntl.h>
38 #include <unistd.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <stdlib.h>
42 #include <assert.h>
43 #include <regex.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <dirent.h>
47 #include <locale.h>
48 
49 /*------------------------------------------------------------*/
50 #include "pisa_view_manager.h"
51 #include "pisa_error.h"
52 #include "pisa_main.h"
53 #include "pisa_scan_tool.h"
54 #include "pisa_default_val.h"
55 #include "pisa_gimp.h"
56 #include "pisa_aleart_dialog.h"
57 #include "pisa_change_unit.h"
58 #include "pisa_preference.h"
59 #include "pisa_scan_selector.h"
60 
61 #include "file-selector.h"
62 // FIXME: lump these together in ../lib/image-stream.hh or something
63 #include "../lib/imgstream.hh"
64 #include "../lib/pnmstream.hh"
65 #include "../lib/pngstream.hh"
66 #include "../lib/jpegstream.hh"
67 #include "../lib/colour.hh"
68 
69 #define DEFAULT_RESOLUTION	300	// dpi
70 
71 /*------------------------------------------------------------*/
72 view_manager * g_view_manager = 0;
73 
74 /*------------------------------------------------------------*/
create_view_manager(int argc,char * argv[])75 int view_manager::create_view_manager ( int argc, char * argv [ ] )
76 {
77   ::gtk_set_locale ( );
78   ::setlocale (LC_NUMERIC, "C");
79   ::gtk_init ( & argc, & argv );
80 
81 #ifndef HAVE_GTK_2
82   ::gdk_imlib_init ( );
83   ::gtk_widget_push_visual ( ::gdk_imlib_get_visual ( ) );
84   ::gtk_widget_push_colormap ( ::gdk_imlib_get_colormap ( ) );
85 #endif
86 
87   try
88     {
89       if ( ::g_view_manager )
90 	throw pisa_error (  PISA_ERR_PARAMETER );
91 
92       ::g_view_manager = new view_manager;
93       if ( ::g_view_manager == 0 )
94 	throw pisa_error (  PISA_ERR_OUTOFMEMORY );
95 
96       // initialize parameters
97       ::g_view_manager->init ( );
98 
99     }
100   catch ( pisa_error & err )
101     {
102       aleart_dialog aleart_dlg;
103 
104       aleart_dlg.message_box ( 0, err.get_error_string ( ) );
105 
106       if ( ::g_view_manager )
107 	{
108 	  delete ::g_view_manager;
109 	  ::g_view_manager = 0;
110 	}
111 
112       return err.get_error_id ( );
113     }
114 
115   return PISA_ERR_SUCCESS;
116 }
117 
118 /*------------------------------------------------------------*/
release_view_manager(void)119 void view_manager::release_view_manager ( void )
120 {
121   if ( ::g_view_manager )
122     {
123       delete ::g_view_manager;
124       ::g_view_manager = 0;
125     }
126 }
127 
128 /*------------------------------------------------------------*/
main(void)129 int view_manager::main ( void )
130 {
131   gtk_main ( );
132 
133   return PISA_ERR_SUCCESS;
134 }
135 
136 /*------------------------------------------------------------*/
init(void)137 void view_manager::init ( void )
138 {
139   // initialize
140   m_scanmanager_cls	= 0;
141   m_main_cls		= 0;
142   m_prev_cls		= 0;
143   m_imgctrl_cls		= 0;
144   m_gamma_cls		= 0;
145   m_config_cls		= 0;
146   m_filsel_cls		= 0;
147   m_scansel_cls		= 0;
148 
149   // open scanner
150   m_scanmanager_cls = new scan_manager;
151 
152   if ( ! m_scanmanager_cls )
153     throw pisa_error ( PISA_ERR_OUTOFMEMORY );
154 
155   open_device ( );
156 
157   m_set.option		= PISA_OP_FLATBED;
158   m_set.film		= PISA_FT_REFLECT;
159   m_set.resolution	= DEFAULT_RESOLUTION;
160   m_set.enable_start_button = false;
161   m_set.usm		= 1;
162   m_set.unit		= PISA_UNIT_INCHES;
163 
164   if ( pisa_gimp_plugin ( ) )
165     m_set.destination	= PISA_DE_GIMP;
166   else
167     m_set.destination	= PISA_DE_FILE;
168 
169   m_main_cls	= new main_window;
170   m_prev_cls	= new preview_window;
171   m_imgctrl_cls	= new image_controls;
172   m_gamma_cls	= new gamma_correction;
173   m_config_cls	= new config_window;
174   m_filsel_cls	= new file_selector;
175 
176   if ( ! m_main_cls ||
177        ! m_prev_cls ||
178        ! m_imgctrl_cls ||
179        ! m_gamma_cls ||
180        ! m_config_cls ||
181        ! m_filsel_cls )
182     {
183       destroy ( );
184       throw ( PISA_ERR_OUTOFMEMORY );
185     }
186 
187   init_img_info ( static_cast <pisa_option_type> ( m_set.option ),
188 		  static_cast <pisa_film_type> ( m_set.film ) );
189 
190   m_main_cls->init ( );
191   m_prev_cls->init ( );
192   m_gamma_cls->init ( );
193   m_config_cls->init ( );
194   m_filsel_cls->init ( );
195 
196   load_preference ( );
197 
198 }
199 
200 /*------------------------------------------------------------*/
destroy(void)201 void view_manager::destroy ( void )
202 {
203   save_preference ( );
204 
205   m_set.delete_all ( );
206 
207   if ( m_scanmanager_cls )
208     {
209       try
210 	{
211 	  close_device ( );
212 	}
213       catch ( pisa_error & err )
214 	{
215 	  aleart_dialog aleart_dlg;
216 
217 	  aleart_dlg.message_box ( 0, err.get_error_string ( ) );
218 	}
219 
220       delete m_scanmanager_cls;
221       m_scanmanager_cls = 0;
222     }
223 
224   if ( m_main_cls )
225     {
226       delete m_main_cls;
227       m_main_cls = 0;
228     }
229 
230   if ( m_prev_cls )
231     {
232       delete m_prev_cls;
233       m_prev_cls = 0;
234     }
235 
236   if ( m_imgctrl_cls )
237     {
238       delete m_imgctrl_cls;
239       m_imgctrl_cls = 0;
240     }
241 
242   if ( m_gamma_cls )
243     {
244       delete m_gamma_cls;
245       m_gamma_cls = 0;
246     }
247 
248   if ( m_config_cls )
249     {
250       delete m_config_cls;
251       m_config_cls = 0;
252     }
253 
254   if ( m_filsel_cls )
255     {
256       delete m_filsel_cls;
257       m_filsel_cls = 0;
258     }
259 
260   if ( m_scansel_cls )
261     {
262       delete m_scansel_cls;
263       m_scansel_cls = 0;
264     }
265 
266   ::pisa_quit ( );
267 }
268 
269 /*------------------------------------------------------------*/
create_window(pisa_window_id id)270 GtkWidget * view_manager::create_window ( pisa_window_id id )
271 {
272   GtkWidget * ret = 0;
273 
274   switch ( id )
275     {
276     case ID_WINDOW_MAIN:
277       ret = m_main_cls->create_window ( 0 );
278       break;
279 
280     case ID_WINDOW_PREV:
281       ret = m_prev_cls->create_window ( 0 );
282       break;
283 
284     case ID_WINDOW_IMGCTRL:
285       ret = m_imgctrl_cls->create_window ( 0 );
286       break;
287 
288     case ID_WINDOW_GAMMA:
289       ret = m_gamma_cls->create_window ( m_main_cls->get_widget ( ) );
290       break;
291 
292     case ID_WINDOW_CONFIG:
293       ret = m_config_cls->create_window ( m_main_cls->get_widget ( ) );
294       break;
295     }
296 
297   return ret;
298 }
299 
300 /*------------------------------------------------------------*/
close_window(pisa_window_id id,int destroy_flag)301 int view_manager::close_window ( pisa_window_id id, int destroy_flag )
302 {
303   switch ( id )
304     {
305     case ID_WINDOW_MAIN:
306       m_main_cls->close_window ( destroy_flag );
307       destroy ( );
308       break;
309 
310     case ID_WINDOW_PREV:
311       m_prev_cls->close_window ( destroy_flag );
312       break;
313 
314     case ID_WINDOW_GAMMA:
315       m_gamma_cls->close_window ( destroy_flag );
316       break;
317 
318     case ID_WINDOW_CONFIG:
319       m_config_cls->close_window ( destroy_flag );
320       break;
321 
322     default:
323       return PISA_ERR_PARAMETER;
324     }
325 
326   return PISA_ERR_PARAMETER;
327 }
328 
329 /*------------------------------------------------------------*/
get_window_cls(pisa_window_id id)330 void * view_manager::get_window_cls ( pisa_window_id id )
331 {
332   void * ret = 0;
333 
334   switch ( id )
335     {
336     case ID_WINDOW_MAIN:
337       ret = m_main_cls;
338       break;
339 
340     case ID_WINDOW_PREV:
341       ret = m_prev_cls;
342       break;
343 
344     case ID_WINDOW_IMGCTRL:
345       ret = m_imgctrl_cls;
346       break;
347 
348     case ID_WINDOW_GAMMA:
349       ret = m_gamma_cls;
350       break;
351 
352     case ID_WINDOW_CONFIG:
353       ret = m_config_cls;
354       break;
355 
356     }
357 
358   return ret;
359 }
360 
361 /*------------------------------------------------------------*/
sensitive(void)362 void view_manager::sensitive ( void )
363 {
364   int is_img = m_prev_cls->is_prev_img ( );
365 
366   m_main_cls->sensitive ( is_img );
367   m_imgctrl_cls->sensitive ( is_img );
368   m_gamma_cls->sensitive ( is_img );
369 }
370 
371 /*------------------------------------------------------------*/
is_gimp(void)372 int view_manager::is_gimp ( void )
373 {
374   return pisa_gimp_plugin ( );
375 }
376 
377 /*------------------------------------------------------------*/
start_scan(void)378 void view_manager::start_scan ( void )
379 {
380   if (PISA_ERR_SUCCESS != init_scan_param ())
381     return;
382 
383   // All types of scans need to provide visual feedback on their
384   // progress and for consecutive scans (via ADF or with the start
385   // button enabled) it is better to create it here.  That way, it
386   // will not disappear and reappear between calls to scan_file() and
387   // scan_gimp(), eliminating rather annoying flicker.
388   _feedback = new progress_window (m_main_cls->get_widget ());
389   switch ( m_set.destination )
390     {
391     case PISA_DE_FILE:
392       do_scan_file ();
393       break;
394 
395     case PISA_DE_PRINTER:
396       do_scan_printer ();
397       break;
398 
399     case PISA_DE_GIMP:
400       do_scan_gimp ();
401       break;
402     }
403   delete _feedback;
404 }
405 
406 int
update_lut(long index)407 view_manager::update_lut (long index)
408 {
409   return update_lut (m_set.get_marquee (index));
410 }
411 
412 int
update_lut(marquee & m)413 view_manager::update_lut (marquee& m)
414 {
415   iscan::build_LUT (&m_set, &m, m_scanmanager_cls->is_dumb ());
416 
417   return PISA_ERR_SUCCESS;
418 }
419 
420 bool
change_document_source(char option,char film,const imagetype * img_type)421 view_manager::change_document_source (char option, char film,
422 				      const imagetype *img_type)
423 {
424   bool success = true;
425 
426   if (option == m_set.option && film == m_set.film)
427     return success;		// nothing to change
428 
429   try
430     {
431       memcpy (&m_set.imgtype, img_type, sizeof (imagetype));
432       m_set.delete_all ();	// init_img_info will call init()
433       init_img_info (static_cast <pisa_option_type> (option),
434 		     static_cast <pisa_film_type> (film));
435 
436       m_set.option = option;
437       m_set.film   = film;
438 
439       main_window *main_cls
440 	= static_cast <main_window *> (get_window_cls (ID_WINDOW_MAIN));
441       main_cls->enable_start_button (   PISA_OP_ADF     != option
442 				     && PISA_OP_ADFDPLX != option);
443     }
444   catch (pisa_error& err)
445     {
446       aleart_dialog aleart_dlg;
447 
448       aleart_dlg.message_box (m_main_cls->get_widget (),
449 			      err.get_error_string ());
450       success = false;
451     }
452 
453   set_resolution (DEFAULT_RESOLUTION);
454   m_gamma_cls->reset (1);
455   m_prev_cls->resize_window ();
456   sensitive ();
457 
458   return success;
459 }
460 
set_device(char * name)461 void view_manager::set_device( char * name )
462 {
463   m_scanmanager_cls->close_device();
464   m_scanmanager_cls->open_device( name );
465 }
466 
get_device_name() const467 char * view_manager::get_device_name() const
468 {
469   return m_scansel_cls->get_device( true );
470 }
471 
472 /*------------------------------------------------------------*/
open_device(void)473 void view_manager::open_device ( void )
474 {
475   sane_init( 0, 0 );
476   if (!m_scansel_cls)
477     m_scansel_cls = new scan_selector( true );	// dialog box
478   m_scanmanager_cls->open_device ( m_scansel_cls->get_device() );
479 }
480 
481 /*------------------------------------------------------------*/
close_device(void)482 void view_manager::close_device ( void )
483 {
484   m_scanmanager_cls->close_device ( );
485 }
486 
487 /*------------------------------------------------------------*/
load_preference(void)488 void view_manager::load_preference ( void )
489 {
490   char pref_path [ 256 ];
491   char pips_path [ 1024 ] = "lpr";
492 
493   cfg_struct cfg [ ] =
494   {
495     { "PIPS", CFG_STRING, pips_path }
496   };
497 
498   ::strcpy ( pref_path, ::getenv ( "HOME" ) );
499   ::strcat ( pref_path, "/" );
500   ::strcat ( pref_path, PREFERENCE );
501 
502   ::get_cfg ( pref_path, cfg, sizeof ( cfg ) / sizeof ( cfg [ 0 ] ) );
503 
504   ::strcpy ( m_config_cls->m_cmd, pips_path );
505 }
506 
507 /*------------------------------------------------------------*/
save_preference(void)508 void view_manager::save_preference ( void )
509 {
510   char pref_path [ 256 ];
511   char pips_path [ 1024 ] = "";
512 
513   cfg_struct cfg [ ] =
514   {
515     { "PIPS", CFG_STRING, pips_path }
516   };
517 
518   ::strcpy ( pref_path, ::getenv ( "HOME" ) );
519   ::strcat ( pref_path, "/" );
520   ::strcat ( pref_path, PREFERENCE );
521 
522   ::strcpy ( pips_path, m_config_cls->m_cmd );
523 
524   ::set_cfg ( pref_path, cfg, sizeof ( cfg ) / sizeof ( cfg [ 0 ] ) );
525 }
526 
527 /*------------------------------------------------------------*/
init_img_info(pisa_option_type option,pisa_film_type film)528 int view_manager::init_img_info ( pisa_option_type option,
529 				  pisa_film_type film )
530 {
531   int		i, j;
532   marquee	* marq;
533   double	max_width, max_height;
534   double	coef [ 9 ];
535 
536   max_width	= 8.5;
537   max_height	= 11.7;
538 
539   // initialize marquee
540   marq = new marquee;
541   m_set.init ( & marq );
542 
543   marq->offset.x	= 0.0;
544   marq->offset.y	= 0.0;
545   marq->area.x		= max_width;
546   marq->area.y		= max_height;
547 
548   marq->scale		= 100;
549 
550   marq->gamma		= ( long ) ( 100 * DEFGAMMA );
551   marq->highlight	= DEFHIGHLIGHT;
552   marq->shadow		= DEFSHADOW;
553   marq->threshold	= DEFTHRESHOLD;
554 
555   for ( i = 0; i < 4; i++ )
556     for ( j = 0; j < 256; j++ )
557       marq->gamma_table [ i ] [ j ] = j;
558 
559   marq->graybalance	= DEFGRAYBALANCE;
560   marq->saturation	= DEFSATURATION;
561 
562   if ( option == PISA_OP_TPU )
563     marq->focus		= 25;
564   else
565     marq->focus		= 0;
566 
567   for ( i = 0; i < 3; i++ )
568     {
569       marq->film_gamma [ i ]	= 1.0;
570       marq->film_yp [ i ]	= 0.0;
571       marq->grayl [ i ]		= 0.0;
572     }
573 
574   for ( i = 0; i < 256; i++ )
575     {
576       marq->lut.gamma_r [ i ]	= i;
577       marq->lut.gamma_g [ i ]	= i;
578       marq->lut.gamma_b [ i ]	= i;
579     }
580 
581   // get max area
582   m_scanmanager_cls->set_option ( option );
583   m_scanmanager_cls->set_film_type ( film );
584   m_scanmanager_cls->get_current_max_size ( & max_width, & max_height );
585   m_scanmanager_cls->get_color_profile ( coef );
586 
587   m_set.max_area [ 0 ]	= max_width;
588   m_set.max_area [ 1 ]	= max_height;
589 
590   // update marquee to reflect new scanner status
591   marq->area.x		= max_width;
592   marq->area.y		= max_height;
593 
594   for ( i = 0; i < 9; i++ )
595     m_set.coef [ i ] = coef [ i ];
596 
597   return PISA_ERR_SUCCESS;
598 }
599 
600 int
init_scan_param(void)601 view_manager::init_scan_param (void)
602 {
603   marquee marq = get_marquee (m_set.get_marquee_size ( ) - 1);
604 
605   pisa_error_id err = m_scanmanager_cls->set_scan_parameters (m_set, marq);
606 
607   if (PISA_ERR_SUCCESS != err)
608     {
609       aleart_dialog dlg;
610 
611       dlg.message_box (m_main_cls->get_widget (),
612 		       pisa_error (err).get_error_string ());
613     }
614   return err;
615 }
616 
617 int
do_scan_file(file_selector * fs_cls,bool first_time_around)618 view_manager::do_scan_file (file_selector *fs_cls,
619 			    bool first_time_around)
620 {
621   int  cancel;
622 
623   if (!fs_cls)
624     {
625       fs_cls = (file_selector *) m_filsel_cls;
626 
627       fs_cls->init();
628 #ifndef HAVE_GTK_2
629       fs_cls->create_window (m_main_cls->get_widget ( ), m_set.option,
630 			     m_set.enable_start_button,
631 			     PISA_PT_BW == m_set.imgtype.pixeltype);
632 #else
633       GtkWidget *w = m_main_cls->get_widget ();
634       fs_cls->create_window (GTK_WINDOW (gtk_widget_get_parent (w)), w,
635 			     (   PISA_OP_ADF     == m_set.option
636 			      || PISA_OP_ADFDPLX == m_set.option
637 			      || m_set.enable_start_button));
638 #endif /* HAVE_GTK_2 */
639       fs_cls->hide();
640     }
641 
642   char *filename = fs_cls->get_filename();
643 
644   if (   PISA_OP_ADF     != m_set.option
645       && PISA_OP_ADFDPLX != m_set.option
646       && !m_set.enable_start_button)
647     {
648       fs_cls->destroy();
649 
650       if (!filename)
651 	return PISA_ERR_SUCCESS;
652 
653       scan_file (filename, & cancel);
654     }
655   else				// ADF scanning
656     if (filename)
657       {
658 	char *dash = strrchr( filename, '-' );
659 	char *dot  = strrchr( filename, '.' );
660 	size_t len = strlen( filename );
661 	int digits = dot - dash - 1;
662 
663 	assert( digits < 10 ); // assumed for string lengths!
664 	char *format = new char[len + dash - dot +  5];
665 	char *regexp = new char[len + dash - dot + 29];
666 	char *f_name = new char[len + dash - dot + 11];
667 
668 	++*dash = '\0';		// cut filename
669 	sprintf( format, "%s-%%%dd%s"          , filename, digits, dot );
670 	sprintf( regexp, "%s-([1-9][0-9]{%d,}|[0-9]{%d})\\%s$",
671 		 filename, digits, digits, dot );
672 
673 	cancel = check_overwrite( regexp );
674 	delete[] regexp;
675 
676 	bool eos = (0 != cancel);	// scan if NOT cancelled
677 	int  cnt = fs_cls->get_sequence_number();
678 
679 	while (!eos)
680 	  {
681 	    sprintf( f_name, format, cnt );
682 	    char *p = strrchr( f_name, '-' );
683 	    while (' ' == *(++p))
684 	      *p = '0';	// zero out any spaces
685 
686 	    // FIXME: update fs_cls' filename?
687 	    eos = scan_file (f_name, & cancel, first_time_around);
688 	    first_time_around = false;
689 	    if (!eos)
690 	      {
691 		fs_cls->set_sequence_number( ++cnt );
692 	      }
693 	  }
694 	delete[] format;
695 	delete[] f_name;
696 	if (!cancel) {
697 	  do_scan_file (fs_cls, first_time_around);
698 	}
699 	fs_cls->destroy();
700       }
701     else
702       fs_cls->destroy();
703 
704   free( filename );
705 
706   return PISA_ERR_SUCCESS;
707 }
708 
709 //! Sends scan results to a printer.
710 /*!
711  */
712 int
do_scan_printer(int fd,bool first_time_around)713 view_manager::do_scan_printer (int fd, bool first_time_around)
714 {
715   int   cancel;
716   char  cmd[256];		// FIXME: buffer overflow!
717 
718   if (   PISA_OP_ADF     != m_set.option
719       && PISA_OP_ADFDPLX != m_set.option
720       && !m_set.enable_start_button)
721     {
722       char *filename = NULL;
723       int   status   = get_temp_filename (&filename);
724 
725       if (PISA_ERR_SUCCESS != status)
726 	{
727 	  free (filename);
728 	  return PISA_ERR_SUCCESS; // FIXME: should return status;
729 	}
730 
731       scan_file (filename, &cancel);
732 
733       if (!cancel)
734 	{
735 	  while (gtk_events_pending ())
736 	    gtk_main_iteration ();
737 
738 	  sprintf (cmd, "%s %s", m_config_cls->m_cmd, filename);
739 	  system (cmd);
740 	}
741       remove (filename);
742       free (filename);
743    }
744   else				// ADF scanning
745     {
746       bool eos = false;
747       while (!eos)
748 	{
749 	  char *filename = NULL;
750 	  int   status   = get_temp_filename (&filename);
751 
752 	  if (PISA_ERR_SUCCESS != status)
753 	    {
754 	      free (filename);
755 	      return PISA_ERR_SUCCESS; // FIXME: should return status;
756 	    }
757 
758 	  eos = scan_file (filename, &cancel, first_time_around);
759 	  first_time_around = false;
760 
761 	  if (!cancel && !eos)
762 	    {
763 	      while (gtk_events_pending ())
764 		gtk_main_iteration ();
765 
766 	      sprintf (cmd, "%s %s", m_config_cls->m_cmd, filename);
767 	      system (cmd);
768 	    }
769 	  remove (filename);
770 	  free (filename);
771 	}
772       if (!cancel)
773 	do_scan_printer (fd, first_time_around);
774     }
775 
776   return PISA_ERR_SUCCESS;
777 }
778 
779 int
do_scan_gimp(bool first_time_around)780 view_manager::do_scan_gimp (bool first_time_around)
781 {
782   int cancel = 0;
783 
784   if (   PISA_OP_ADF     != m_set.option
785       && PISA_OP_ADFDPLX != m_set.option
786       && !m_set.enable_start_button)
787     return scan_gimp (&cancel);
788 
789   bool eos    = false;
790 
791   while (!eos)
792     {
793       eos = scan_gimp (&cancel, first_time_around);
794       first_time_around = false;
795     }
796   if (!cancel)
797     do_scan_gimp (first_time_around);
798 
799   return PISA_ERR_SUCCESS;
800 }
801 
802 int
dialog_reply(const pisa_error & err) const803 view_manager::dialog_reply( const pisa_error& err ) const
804 {
805   int reply = 0;
806 
807   aleart_dialog dlg;
808 
809   if ( ( PISA_STATUS_GOOD < err.get_error_id() ) &&
810        ( PISA_OP_ADF     == m_set.option ||
811          PISA_OP_ADFDPLX == m_set.option            ) )
812     {
813       int i = dlg.message_box( m_main_cls->get_widget(),
814 			       err.get_error_string(),
815 			       _("  Continue  "), _("  Cancel  ") );
816       if (2 == i)
817 	reply = 1;
818     }
819   else if ( ( PISA_STATUS_GOOD == err.get_error_id() ) &&
820 	    m_set.enable_start_button )
821     {
822 
823       int i = dlg.message_box( m_main_cls->get_widget(),
824 			       "Waiting for ...",
825 			       _("  Finish  ") );
826 
827       if ( 1 == i )
828 	reply = 1;
829     }
830   else
831     {
832       dlg.message_box( m_main_cls->get_widget(),
833 		       err.get_error_string() );
834 
835       if (PISA_ERR_FILEOPEN != err.get_error_id())
836 	reply = 1;
837     }
838 
839   return reply;
840 }
841 
842 /*------------------------------------------------------------*/
843 bool
scan_gimp(int * cancel,bool first_time_around)844 view_manager::scan_gimp (int *cancel, bool first_time_around)
845 {
846 
847 #ifndef HAVE_ANY_GIMP
848 
849   *cancel = 1;			// can't scan to GIMP
850   return true;
851 
852 #else
853   bool error = false;		// be optimistic
854   try
855     {
856       *cancel = 0;
857       bool wait_for_button = (m_set.enable_start_button
858 			      && !(   PISA_OP_ADF     == m_set.option
859 				   || PISA_OP_ADFDPLX == m_set.option));
860 
861       bool reset_params = false;
862       if (wait_for_button
863 	  && m_scanmanager_cls->is_button_pressed ()) {
864 	// We have an impatient user here who pressed the scanner's
865 	// button *before* we even got a chance to show our WAITING
866 	// message.
867 	// We ensure the user gets to see this message be (re)setting
868 	// the document source (which indirectly resets the scanner's
869 	// push button status) for lack of a more elegant way and raise
870 	// a flag so that any parameters that may have been erased as
871 	// a result are reset before we request scan data.
872 	m_scanmanager_cls
873 	  ->set_option (static_cast <pisa_option_type> (m_set.option));
874 	reset_params = true;
875       }
876 
877       _feedback->set_text (wait_for_button
878 			   ? progress_window::WAITING
879 			   : progress_window::WARMING_UP);
880       _feedback->set_progress (0, 1);
881       _feedback->show ();
882 
883       if (wait_for_button) {
884 	while (!m_scanmanager_cls->is_button_pressed ()
885 	       && !_feedback->is_cancelled ()) {
886 	  sleep (1);
887 	  while (gtk_events_pending ()) {
888 	    gtk_main_iteration ();
889 	  }
890 	}
891 	if (_feedback->is_cancelled ()) {
892 	  *cancel = 1;
893 	  return true;
894 	}
895       }
896 
897       _feedback->set_text (progress_window::WARMING_UP);
898 
899       int width, height;
900       m_scanmanager_cls->init_scan (&width, &height,
901 				    first_time_around || reset_params);
902 
903       while ( ::gtk_events_pending ( ) )
904 	::gtk_main_iteration ( );
905 
906       char depth;
907       int rowbytes;
908       switch (m_set.imgtype.pixeltype)
909 	{
910 	case PISA_PT_RGB:
911 	  depth	   = 8;
912 	  rowbytes = width * 3;
913 	  break;
914 	case PISA_PT_GRAY:
915 	  depth	   = 8;
916 	  rowbytes = width;
917 	  break;
918 	case PISA_PT_BW:
919 	  depth	   = 1;
920 	  rowbytes = (width + 7) / 8;
921 	  break;
922 	default:
923 	  rowbytes = 0;
924 	}
925 
926       gimp_scan gimp_cls;
927       if (PISA_ERR_SUCCESS !=
928 	  gimp_cls.create_gimp_image (width, height,
929 				      m_set.imgtype.pixeltype, depth))
930 	{
931 	  m_scanmanager_cls->acquire_image ( 0, 0, 1, 1 );
932 
933 	  throw pisa_error ( PISA_ERR_OUTOFMEMORY );
934 	}
935 
936       for (int i = 0; i < height; i++)
937 	{
938 	  m_scanmanager_cls->acquire_image ( gimp_cls.get_next_buf ( ),
939 					     rowbytes,
940 					     1,
941 					     *cancel );
942 
943 	  if ( i == 0 )
944 	    _feedback->set_text (progress_window::SCANNING);
945 
946 	  if (*cancel)
947 	    {
948 	      error = true;
949 	      break;
950 	    }
951 
952 	  _feedback->set_progress (i, height);
953 	  *cancel = _feedback->is_cancelled ();
954 
955 	  gimp_cls.set_image_rect ( );
956 
957 	  while ( ::gtk_events_pending ( ) )
958 	    ::gtk_main_iteration ( );
959 	}
960       _feedback->set_progress (height, height);
961 
962       gimp_cls.finish_scan ( *cancel );
963     }
964   catch ( pisa_error & err )
965     {
966       error = true;
967       *cancel = dialog_reply( err );
968     }
969 
970   m_scanmanager_cls->finish_acquire ( );
971 
972   return error;
973 #endif	// HAVE_ANY_GIMP
974 }
975 
976 bool
scan_file(const char * filename,int * cancel,bool first_time_around)977 view_manager::scan_file (const char *filename, int *cancel,
978 			 bool first_time_around)
979 {
980   bool error = false;		// be optimistic
981   try
982     {
983       *cancel = 0;
984       bool wait_for_button = (m_set.enable_start_button
985 			      && !(   PISA_OP_ADF     == m_set.option
986 				   || PISA_OP_ADFDPLX == m_set.option));
987 
988       bool reset_params = false;
989       if (wait_for_button
990 	  && m_scanmanager_cls->is_button_pressed ()) {
991 	// We have an impatient user here who pressed the scanner's
992 	// button *before* we even got a chance to show our WAITING
993 	// message.
994 	// We ensure the user gets to see this message be (re)setting
995 	// the document source (which indirectly resets the scanner's
996 	// push button status) for lack of a more elegant way and raise
997 	// a flag so that any parameters that may have been erased as
998 	// a result are reset before we request scan data.
999 	m_scanmanager_cls
1000 	  ->set_option (static_cast <pisa_option_type> (m_set.option));
1001 	reset_params = true;
1002       }
1003 
1004       _feedback->set_text (wait_for_button
1005 			   ? progress_window::WAITING
1006 			   : progress_window::WARMING_UP);
1007       _feedback->set_progress (0, 1);
1008       _feedback->show ();
1009 
1010       while (::gtk_events_pending())
1011 	::gtk_main_iteration();
1012 
1013       if (wait_for_button) {
1014 	while (!m_scanmanager_cls->is_button_pressed ()
1015 	       && !_feedback->is_cancelled ()) {
1016 	  sleep (1);
1017 	  while (gtk_events_pending ()) {
1018 	    gtk_main_iteration ();
1019 	  }
1020 	}
1021 	if (_feedback->is_cancelled ()) {
1022 	  *cancel = 1;
1023 	  return true;
1024 	}
1025       }
1026 
1027       _feedback->set_text (progress_window::WARMING_UP);
1028 
1029       int width, height;
1030       m_scanmanager_cls->init_scan (&width, &height,
1031 				    first_time_around || reset_params);
1032 
1033       while (::gtk_events_pending())
1034 	::gtk_main_iteration();
1035 
1036       int rowbytes;
1037       iscan::colour_space cs;
1038 
1039       switch (m_set.imgtype.pixeltype)
1040 	{
1041 	case PISA_PT_RGB:
1042 	  rowbytes = width * 3;
1043 	  cs = iscan::RGB;
1044 	  break;
1045 	case PISA_PT_GRAY:
1046 	  rowbytes = width;
1047 	  cs = iscan::gray;
1048 	  break;
1049 	case PISA_PT_BW:
1050 	  rowbytes = (width + 7) / 8;
1051 	  cs = iscan::mono;
1052 	  break;
1053 	default:
1054 	  rowbytes = 0;
1055 	  throw pisa_error (PISA_ERR_PARAMETER);
1056 	}
1057 
1058       iscan::imgstream *save_cls = NULL;
1059       iscan::imgstream::filebuf fb (fopen (filename, "wb"));
1060 
1061       switch (get_file_type( filename )) // FIXME: use factory method
1062 	{
1063 	case PISA_FI_PNM:
1064 	  save_cls = new iscan::pnmstream (&fb);
1065 	  break;
1066 	case PISA_FI_PNG:
1067 	  save_cls = new iscan::pngstream (&fb);
1068 	  break;
1069 	case PISA_FI_JPG:
1070 	  save_cls = new iscan::jpegstream (&fb);
1071 	  break;
1072 	default:
1073 	  throw pisa_error( PISA_ERR_FILEOPEN );
1074 	}
1075 
1076       try
1077 	{
1078       try {
1079 	save_cls->size (width, height);
1080 	save_cls->depth (PISA_PT_BW == m_set.imgtype.pixeltype
1081 			 ? 1 : 8);
1082 	save_cls->colour (cs);
1083 	save_cls->resolution (m_set.resolution, m_set.resolution);
1084       } catch (std::ios_base::failure& oops) {
1085 	// map to old API and rethrow
1086 	throw (save_cls->rdbuf ()
1087 	       ? pisa_error (PISA_ERR_OUTOFMEMORY)
1088 	       : pisa_error (PISA_ERR_FILEOPEN));
1089       }
1090 	}
1091       catch (pisa_error& oops)
1092 	{
1093 	  m_scanmanager_cls->acquire_image( 0, 0, 1, 1 );
1094 	  ::remove( filename );
1095 	  delete save_cls;
1096 	  throw oops;
1097 	}
1098 
1099       unsigned char *img = new unsigned char[rowbytes];
1100       for (int i = 0; i < height; ++i)
1101 	{
1102 	  m_scanmanager_cls->acquire_image( img, rowbytes, 1, *cancel );
1103 
1104 	  if (0 == i)
1105 	    _feedback->set_text (progress_window::SCANNING);
1106 
1107 	  if (*cancel)
1108 	    {
1109 	      ::remove( filename );
1110 	      error = true;
1111 	      break;
1112 	    }
1113 
1114 	  _feedback->set_progress (i, height);
1115 	  *cancel = _feedback->is_cancelled ();
1116 
1117 	  try
1118 	    {
1119 	  try {
1120 	    save_cls->write ((const char *)img, rowbytes);
1121 	  } catch (std::ios_base::failure& oops) {
1122 	    // map to old API and rethrow
1123 	    throw (save_cls->rdbuf ()
1124 		   ? pisa_error (PISA_ERR_OUTOFMEMORY)
1125 		   : pisa_error (PISA_ERR_FILEOPEN));
1126 	  }
1127 	    }
1128 	  catch (pisa_error& oops)
1129 	    {
1130 	      *cancel = 1;
1131 
1132 	      if (i < height)
1133 		m_scanmanager_cls->acquire_image( img, rowbytes, 1,
1134 						  *cancel );
1135 	      aleart_dialog aleart_dlg;
1136 	      aleart_dlg.message_box( m_main_cls->get_widget(),
1137 				      oops.get_error_string() );
1138 
1139 	      ::remove( filename );
1140 	      break;
1141 	    }
1142 	  while (::gtk_events_pending())
1143 	    ::gtk_main_iteration();
1144 	}
1145       delete[] img;
1146       delete save_cls;
1147 
1148       _feedback->set_progress (height, height);
1149     }
1150   catch (pisa_error& oops)
1151     {
1152       error = true;
1153       *cancel = dialog_reply( oops );
1154       ::remove (filename);
1155     }
1156 
1157   m_scanmanager_cls->finish_acquire();
1158 
1159   while (::gtk_events_pending())
1160     ::gtk_main_iteration();
1161 
1162   return error;
1163 }
1164 
1165 //! Indicates the expected output file format.
1166 /*! File format is normally indicated by the file extension, but for
1167     scanning to printer we use temporary files that can not fit that
1168     rule.
1169  */
1170 pisa_file_type
get_file_type(const char * filename)1171 view_manager::get_file_type (const char *filename)
1172 {
1173   char *dot   = strrchr (filename, '.');
1174   if (!dot)
1175     {
1176       char *slash = strrchr (filename, '/');
1177       if (   (strlen (slash) == strlen ("/" PACKAGE_TARNAME "XXXXXX"))
1178 	  && (0 == strncmp (slash, "/" PACKAGE_TARNAME,
1179 			    strlen ("/" PACKAGE_TARNAME))))
1180 	return PISA_FI_PNG;	// temporary file for output to printer
1181 
1182       return PISA_FI_UNSUPPORTED;
1183     }
1184 
1185   if (0 == strcmp (dot, ".pnm"))
1186     return PISA_FI_PNM;
1187   if (0 == strcmp (dot, ".png"))
1188     return PISA_FI_PNG;
1189   if ((0 == strcmp (dot, ".jpg")) || (0 == strcmp (dot, ".jpeg")))
1190     return PISA_FI_JPG;
1191 
1192   return PISA_FI_UNSUPPORTED;
1193 }
1194 
1195 //! Creates a unique temporary filename and opens that file.
1196 /*! The unique file is created and opened in the directory indicated
1197     by the TMPDIR environment variable, whatever your system's stdio.h
1198     file has defined for \c P_tmpdir or the \c /tmp directory.  The
1199     first directory that is writable will be used.
1200 
1201     Caller will need to free the memory pointed to by *filename.
1202  */
1203 int
get_temp_filename(char ** filename)1204 view_manager::get_temp_filename (char **filename)
1205 {
1206   const char *tmpnam = "/" PACKAGE_TARNAME "XXXXXX";
1207   const char *tmpdir;
1208   {
1209     const char *dir_array[] = {
1210       getenv ("TMPDIR"),
1211 #ifdef P_tmpdir
1212       P_tmpdir,
1213 #endif
1214       "/tmp",
1215       NULL
1216     };
1217     struct stat buf;
1218 
1219     unsigned int i = 0;
1220     tmpdir = dir_array[i];
1221     while (i < sizeof (dir_array) / sizeof (char *)
1222 	   && (!tmpdir
1223 	       || (0 != access (tmpdir, W_OK | X_OK))
1224 	       || (0 != stat (tmpdir, &buf))
1225 	       || !S_ISDIR(buf.st_mode)))
1226       {
1227 	tmpdir = dir_array[++i];
1228       }
1229     if (!tmpdir)
1230       return PISA_ERR_FILEOPEN;
1231   }
1232 
1233   *filename = (char *) malloc ((strlen (tmpdir) + strlen (tmpnam) + 1)
1234 			       * sizeof (char));
1235   if (!*filename)
1236     return PISA_STATUS_NO_MEM;
1237 
1238   memset (*filename, 0, (strlen (tmpdir) + strlen (tmpnam) + 1));
1239   strcpy (*filename, tmpdir);
1240   strcpy (*filename + strlen (tmpdir), tmpnam);
1241 
1242   mode_t previous_mask = umask (0077);
1243 
1244   if (0 > mkstemp (*filename))
1245     {
1246       umask (previous_mask);
1247       free (*filename);
1248       *filename = NULL;
1249       return PISA_ERR_FILEOPEN;
1250     }
1251 
1252   umask (previous_mask);
1253 
1254   return PISA_ERR_SUCCESS;
1255 }
1256 
1257 // FIXME: file_selector can and should be responsible for this!
1258 int
check_overwrite(const char * regexp)1259 view_manager::check_overwrite( const char *regexp )
1260 {
1261   int cancel = 0;		// default: don't cancel
1262 
1263   char *slash = strrchr( regexp, '/' );
1264 
1265   if (!slash)
1266     return cancel = 1;
1267 
1268   *slash = '\0';		// regexp now holds the directory name
1269   char dirname[ strlen( regexp )];
1270   strcpy( dirname, regexp );
1271 
1272   *slash = '^';			// re-anchor the regexp
1273 
1274   regex_t *comp_regex = new regex_t;
1275   int comp = regcomp( comp_regex, slash, REG_EXTENDED );
1276 
1277   if (0 == comp)
1278     {
1279       size_t     nsub = comp_regex->re_nsub + 1;
1280       regmatch_t match[nsub];
1281 
1282       file_selector *fs = (file_selector *) m_filsel_cls;
1283 
1284       DIR *dir = opendir( dirname );
1285       if (!dir)
1286 	return 0;		// file creation failure handles this
1287 
1288       struct dirent *file = 0;
1289       bool overwrite = false;	// be conservative ;-)
1290       while (0 == cancel && !overwrite && (file = readdir( dir )))
1291 	{
1292 	  int result = regexec( comp_regex, file->d_name, nsub, match, 0 );
1293 	  if (0 == result)
1294 	    {
1295 	      size_t digits = match[1].rm_eo - match[1].rm_so;
1296 	      char num[digits + 1];
1297 	      char *c = num;
1298 	      {
1299 		char *p = file->d_name + match[1].rm_so;
1300 		while (0 < digits--)
1301 		  *c++ = *p++;
1302 	      }
1303 	      int seq_num = atoi( num );
1304 
1305 	      if (seq_num >= fs->get_sequence_number())
1306 		{
1307 		  aleart_dialog dlg;
1308 		  int answer = dlg.message_box(m_main_cls->get_widget ( ),
1309 					       _("Overwrite?"),
1310 					       _("  Yes  "), _("  No  ") );
1311 
1312 		  if (2 == answer)
1313 		    cancel = 1;
1314 		  if (1 == answer)
1315 		    overwrite = true;
1316 		}
1317 	    }
1318 	  else
1319 	    if (REG_NOMATCH != result)
1320 	      regerror( comp, comp_regex );
1321 	}
1322       closedir( dir );
1323     }
1324   else
1325     regerror( comp, comp_regex );
1326 
1327   regfree( comp_regex );
1328   delete comp_regex;
1329 
1330   return cancel;
1331 }
1332 
1333 void
regerror(int code,regex_t * regex)1334 view_manager::regerror( int code, regex_t *regex )
1335 {
1336   size_t length = ::regerror( code, regex, 0, 0 );
1337   char *message = new char[length];
1338 
1339   ::regerror( code, regex, message, length );
1340   fprintf( stderr, "%s\n", message );
1341 
1342   delete[] message;
1343 }
1344