1 /* sane - Scanner Access Now Easy.
2    Copyright (C) 1997 Jeffrey S. Freedman
3    This file is part of the SANE package.
4 
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 
18    As a special exception, the authors of SANE give permission for
19    additional uses of the libraries contained in this release of SANE.
20 
21    The exception is that, if you link a SANE library with other files
22    to produce an executable, this does not by itself cause the
23    resulting executable to be covered by the GNU General Public
24    License.  Your use of that executable is in no way restricted on
25    account of linking the SANE library code into it.
26 
27    This exception does not, however, invalidate any other reasons why
28    the executable file might be covered by the GNU General Public
29    License.
30 
31    If you submit changes to SANE to the maintainers to be included in
32    a subsequent release, you agree by submitting the changes that
33    those changes may be distributed with this exception intact.
34 
35    If you write modifications of your own for SANE, it is your choice
36    whether to permit this exception to apply to your modifications.
37    If you do not wish that, delete this exception notice.  */
38 
39 /**
40  **	Jscanimage.java - Java scanner program using SANE.
41  **
42  **	Written: 10/31/97 - JSF
43  **/
44 
45 import java.awt.*;
46 import java.awt.event.*;
47 import java.util.*;
48 import java.awt.image.ImageConsumer;
49 import java.awt.image.ColorModel;
50 import java.io.File;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.text.NumberFormat;
54 import javax.swing.*;
55 import javax.swing.border.*;
56 import javax.swing.event.*;
57 
58 /*
59  *	Main program.
60  */
61 public class Jscanimage extends Frame implements WindowListener,
62 			ActionListener, ItemListener, ImageCanvasClient
63     {
64     //
65     //	Static data.
66     //
67     private static Sane sane;		// The main class.
68     private static SaneDevice devList[];// List of devices.
69     //
70     //	Instance data.
71     //
72     private Font font;			// For dialog items.
73     private int saneHandle;		// Handle of open device.
74     private ScanIt scanIt = null;	// Does the actual scan.
75 					// File to output to.
76     private FileOutputStream outFile = null;
77     private String outDir = null;	// Stores dir. for output dialog.
78     private Vector controls;		// Dialog components for SANE controls.
79     private double unitLength = 1;	// # of mm for units to display
80 					//    (mm = 1, cm = 10, in = 25.4).
81     private ImageCanvas imageDisplay = null;
82 					// "Scan", "Preview" buttons.
83     private JButton scanButton, previewButton;
84     private JButton browseButton;	// For choosing output filename.
85     private JTextField outputField;	// Field for output filename.
86     private MenuItem exitMenuItem;	// Menu items.
87     private CheckboxMenuItem toolTipsMenuItem;
88     private CheckboxMenuItem mmMenuItem;
89     private CheckboxMenuItem cmMenuItem;
90     private CheckboxMenuItem inMenuItem;
91 
92 	/*
93 	 *	Main program.
94 	 */
main(String args[])95     public static void main(String args[])
96 	{
97 	if (!initSane())		// Initialize SANE.
98 		return;
99 	Jscanimage prog = new Jscanimage();
100 	prog.pack();
101 	prog.show();
102 	}
103 
104 	/*
105 	 *	Create main window.
106 	 */
Jscanimage()107     public Jscanimage()
108 	{
109 	super("SANE Scanimage");
110 	addWindowListener(this);
111 					// Open SANE device.
112 	saneHandle = initSaneDevice(devList[0].name);
113 	if (saneHandle == 0)
114 		System.exit(-1);
115 					// Create scanner class.
116 	scanIt = new ScanIt(sane, saneHandle);
117 	init();
118 	}
119 
120 	/*
121 	 *	Clean up.
122 	 */
finalize()123     public void finalize()
124 	{
125 	if (sane != null)
126 		{
127 		if (saneHandle != 0)
128 			sane.close(saneHandle);
129 		sane.exit();
130 		sane = null;
131 		}
132 	saneHandle = 0;
133 	if (outFile != null)
134 		{
135 		try
136 			{
137 			outFile.close();
138 			}
139 		catch (IOException e)
140 			{ }
141 		outFile = null;
142 		}
143 	System.out.println("In finalize()");
144 	}
145 
146 	/*
147 	 *	Return info.
148 	 */
getSane()149     public Sane getSane()
150 	{ return sane; }
getSaneHandle()151     public int getSaneHandle()
152 	{ return saneHandle; }
getUnitLength()153     public double getUnitLength()
154 	{ return unitLength; }
155 
156 	/*
157 	 *	Initialize SANE.
158 	 */
initSane()159     private static boolean initSane()
160 	{
161 	sane = new Sane();
162 	int version[] = new int[1];	// Array to get version #.
163 	int status = sane.init(version);
164 	if (status != Sane.STATUS_GOOD)
165 		{
166 		System.out.println("getDevices() failed.  Status= " + status);
167 		return (false);
168 		}
169 					// Get list of devices.
170 					// Allocate room for 50.
171 	devList = new SaneDevice[50];
172 	status = sane.getDevices(devList, false);
173 	if (status != Sane.STATUS_GOOD)
174 		{
175 		System.out.println("getDevices() failed.  Status= " + status);
176 		return (false);
177 		}
178 	for (int i = 0; i < 50 && devList[i] != null; i++)
179 		{
180 		System.out.println("Device '" + devList[i].name + "' is a " +
181 			devList[i].vendor + " " + devList[i].model + " " +
182 			devList[i].type);
183 		}
184 	return (true);
185 	}
186 
187 	/*
188 	 *	Open device.
189 	 *
190 	 *	Output:	Handle, or 0 if error.
191 	 */
initSaneDevice(String name)192     private int initSaneDevice(String name)
193 	{
194 	int handle[] = new int[1];
195 					// Open 1st device, for now.
196 	int status = sane.open(name, handle);
197 	if (status != Sane.STATUS_GOOD)
198 		{
199 		System.out.println("open() failed.  Status= " + status);
200 		return (0);
201 		}
202 	setTitle("SANE - " + name);
203 	System.out.println("Open handle=" + handle[0]);
204 	return (handle[0]);
205 	}
206 
207 	/*
208 	 *	Add a labeled option to the main dialog.
209 	 */
addLabeledOption(JPanel group, String title, Component opt, GridBagConstraints c)210     private void addLabeledOption(JPanel group, String title, Component opt,
211 						GridBagConstraints c)
212 	{
213 	JLabel label = new JLabel(title);
214 	c.gridwidth = GridBagConstraints.RELATIVE;
215 	c.fill = GridBagConstraints.NONE;
216 	c.anchor = GridBagConstraints.WEST;
217 	c.weightx = .1;
218 	group.add(label, c);
219 	c.gridwidth = GridBagConstraints.REMAINDER;
220 	c.fill = GridBagConstraints.HORIZONTAL;
221 	c.weightx = .4;
222 	group.add(opt, c);
223 	}
224 
225 	/*
226 	 *	Get options for device.
227 	 */
initSaneOptions()228     private boolean initSaneOptions()
229 	{
230 	JPanel group = null;
231 	GridBagConstraints c = new GridBagConstraints();
232 	c.weightx = .4;
233 	c.weighty = .4;
234 					// Get # of device options.
235 	int numDevOptions[] = new int[1];
236 	int status = sane.getControlOption(saneHandle, 0, numDevOptions, null);
237 	if (status != Sane.STATUS_GOOD)
238 		{
239 		System.out.println("controlOption() failed.  Status= "
240 								+ status);
241 		return (false);
242 		}
243 	System.out.println("Number of device options=" + numDevOptions[0]);
244 					// Do each option.
245 	for (int i = 0; i < numDevOptions[0]; i++)
246 		{
247 		SaneOption opt = sane.getOptionDescriptor(saneHandle, i);
248 		if (opt == null)
249 			{
250 			System.out.println("getOptionDescriptor() failed for "
251 						+ i);
252 			continue;
253 			}
254 /*
255 		System.out.println("Option title: " + opt.title);
256 		System.out.println("Option desc:  " + opt.desc);
257 		System.out.println("Option type:  " + opt.type);
258  */
259 		String title;		// Set up title.
260 		if (opt.unit == SaneOption.UNIT_NONE)
261 			title = opt.title;
262 		else			// Show units.
263 			title = opt.title + " [" +
264 					opt.unitString(unitLength) + ']';
265 		switch (opt.type)
266 			{
267 		case SaneOption.TYPE_GROUP:
268 					// Group for a set of options.
269 			group = new JPanel(new GridBagLayout());
270 			c.gridwidth = GridBagConstraints.REMAINDER;
271 			c.fill = GridBagConstraints.BOTH;
272 			c.anchor = GridBagConstraints.CENTER;
273 			add(group, c);
274 			group.setBorder(new TitledBorder(title));
275 			break;
276 		case SaneOption.TYPE_BOOL:
277 					// A checkbox.
278 			SaneCheckBox cbox = new SaneCheckBox(opt.title,
279 						this, i, opt.desc);
280 			c.gridwidth = GridBagConstraints.REMAINDER;
281 			c.fill = GridBagConstraints.NONE;
282 			c.anchor = GridBagConstraints.WEST;
283 			if (group != null)
284 				group.add(cbox, c);
285 			addControl(cbox);
286 			break;
287 		case SaneOption.TYPE_FIXED:
288 					// Fixed-point value.
289 			if (opt.size != 4)
290 				break;	// Not sure about this.
291 			switch (opt.constraintType)
292 				{
293 			case SaneOption.CONSTRAINT_RANGE:
294 					// A scale.
295 				SaneSlider slider = new FixedSaneSlider(
296 						opt.rangeConstraint.min,
297 						opt.rangeConstraint.max,
298 						opt.unit == SaneOption.UNIT_MM,
299 						this, i, opt.desc);
300 				addLabeledOption(group, title, slider, c);
301 				addControl(slider);
302 				break;
303 			case SaneOption.CONSTRAINT_WORD_LIST:
304 					// Select from a list.
305 				SaneFixedBox list = new SaneFixedBox(
306 							this, i, opt.desc);
307 				addLabeledOption(group, title, list, c);
308 				addControl(list);
309 				break;
310 				}
311 			break;
312 		case SaneOption.TYPE_INT:
313 					// Integer value.
314 			if (opt.size != 4)
315 				break;	// Not sure about this.
316 			switch (opt.constraintType)
317 				{
318 			case SaneOption.CONSTRAINT_RANGE:
319 					// A scale.
320 				SaneSlider slider = new SaneSlider(
321 						opt.rangeConstraint.min,
322 						opt.rangeConstraint.max,
323 						this, i, opt.desc);
324 				addLabeledOption(group, title, slider, c);
325 				addControl(slider);
326 				break;
327 			case SaneOption.CONSTRAINT_WORD_LIST:
328 					// Select from a list.
329 				SaneIntBox list = new SaneIntBox(
330 							this, i, opt.desc);
331 				addLabeledOption(group, title, list, c);
332 				addControl(list);
333 				break;
334 				}
335 			break;
336 		case SaneOption.TYPE_STRING:
337 					// Text entry or choice box.
338 			switch (opt.constraintType)
339 				{
340 			case SaneOption.CONSTRAINT_STRING_LIST:
341 					// Make a list.
342 				SaneStringBox list = new SaneStringBox(
343 							this, i, opt.desc);
344 				addLabeledOption(group, title, list, c);
345 				addControl(list);
346 				break;
347 			case SaneOption.CONSTRAINT_NONE:
348 				SaneTextField tfield = new SaneTextField(16,
349 							this, i, opt.desc);
350 				addLabeledOption(group, title, tfield, c);
351 				addControl(tfield);
352 				break;
353 				}
354 			break;
355 		case SaneOption.TYPE_BUTTON:
356 			c.gridwidth = GridBagConstraints.REMAINDER;
357 			c.fill = GridBagConstraints.HORIZONTAL;
358 			c.anchor = GridBagConstraints.CENTER;
359 			c.insets = new Insets(8, 4, 4, 4);
360 			JButton btn = new SaneButton(title, this, i, opt.desc);
361 			group.add(btn, c);
362 			c.insets = null;
363 			break;
364 		default:
365 			break;
366 			}
367 		}
368 	return (true);
369 	}
370 
371 	/*
372 	 *	Set up "Output" panel.
373 	 */
initOutputPanel()374     private JPanel initOutputPanel()
375 	{
376 	JPanel panel = new JPanel(new GridBagLayout());
377 	GridBagConstraints c = new GridBagConstraints();
378 					// Want 1 row.
379 	c.gridx = GridBagConstraints.RELATIVE;
380 	c.gridy = 0;
381 	c.anchor = GridBagConstraints.WEST;
382 //	c.fill = GridBagConstraints.HORIZONTAL;
383 	c.weightx = .4;
384 	c.weighty = .4;
385 	panel.setBorder(new TitledBorder("Output"));
386 	panel.add(new Label("Filename"), c);
387 	outputField = new JTextField("out.pnm", 16);
388 	panel.add(outputField, c);
389 	c.insets = new Insets(0, 8, 0, 0);
390 	browseButton = new JButton("Browse");
391 	browseButton.addActionListener(this);
392 	panel.add(browseButton, c);
393 	return (panel);
394 	}
395 
396 	/*
397 	 *	Initialize main window.
398 	 */
init()399     private void init()
400 	{
401 	controls = new Vector();	// List of SaneComponent's.
402 					// Try a light blue:
403 	setBackground(new Color(140, 200, 255));
404 					// And a larger font.
405 	font = new Font("Helvetica", Font.PLAIN, 12);
406 	setFont(font);
407 	initMenu();
408 					// Main panel will be 1 column.
409 	setLayout(new GridBagLayout());
410 	GridBagConstraints c = new GridBagConstraints();
411 	c.gridwidth = GridBagConstraints.REMAINDER;
412 	c.fill = GridBagConstraints.BOTH;
413 	c.weightx = .4;
414 	c.weighty = .4;
415 					// Panel for output:
416 	JPanel outputPanel = initOutputPanel();
417 	add(outputPanel, c);
418 	initSaneOptions();		// Get SANE options, set up dialog.
419 	initButtons();			// Put buttons at bottom.
420 	}
421 
422 	/*
423 	 *	Add a control to the dialog and set its initial value.
424 	 */
addControl(SaneComponent comp)425     private void addControl(SaneComponent comp)
426 	{
427 	controls.addElement(comp);
428 	comp.setFromControl();		// Get initial value.
429 	}
430 
431 	/*
432 	 *	Reinitialize components from SANE controls.
433 	 */
reinit()434     private void reinit()
435 	{
436 	Enumeration ctrls = controls.elements();
437 	while (ctrls.hasMoreElements())	// Do each control.
438 		{
439 		SaneComponent comp = (SaneComponent) ctrls.nextElement();
440 		comp.setFromControl();
441 		}
442 	}
443 
444 	/*
445 	 *	Set up the menubar.
446 	 */
initMenu()447     private void initMenu()
448 	{
449 	MenuBar mbar = new MenuBar();
450 	Menu file = new Menu("File");
451 	mbar.add(file);
452 	exitMenuItem = new MenuItem("Exit");
453 	exitMenuItem.addActionListener(this);
454 	file.add(exitMenuItem);
455 	Menu pref = new Menu("Preferences");
456 	mbar.add(pref);
457 	toolTipsMenuItem = new CheckboxMenuItem("Show tooltips", true);
458 	toolTipsMenuItem.addItemListener(this);
459 	pref.add(toolTipsMenuItem);
460 	Menu units = new Menu("Length unit");
461 	pref.add(units);
462 	mmMenuItem = new CheckboxMenuItem("millimeters", true);
463 	mmMenuItem.addItemListener(this);
464 	units.add(mmMenuItem);
465 	cmMenuItem = new CheckboxMenuItem("centimeters", false);
466 	cmMenuItem.addItemListener(this);
467 	units.add(cmMenuItem);
468 	inMenuItem = new CheckboxMenuItem("inches", false);
469 	inMenuItem.addItemListener(this);
470 	units.add(inMenuItem);
471 	setMenuBar(mbar);
472 	}
473 
474 	/*
475 	 *	Set up buttons panel at very bottom.
476 	 */
initButtons()477     private void initButtons()
478 	{
479 					// Buttons are at bottom.
480 	JPanel bottomPanel = new JPanel(new GridBagLayout());
481 					// Indent around buttons.
482 	GridBagConstraints c = new GridBagConstraints();
483 	c.gridwidth = GridBagConstraints.REMAINDER;
484 	c.insets = new Insets(8, 8, 8, 8);
485 	c.weightx = .4;
486 	c.weighty = .2;
487 	c.fill = GridBagConstraints.HORIZONTAL;
488 	add(bottomPanel, c);
489 	c.weighty = c.weightx = .4;
490 	c.fill = GridBagConstraints.BOTH;
491 	c.gridwidth = c.gridheight = 1;
492 					// Create image display box.
493 	imageDisplay = new ImageCanvas();
494 	bottomPanel.add(imageDisplay, c);
495 					// Put btns. to right in 1 column.
496 	JPanel buttonsPanel = new JPanel(new GridLayout(0, 1, 8, 8));
497 	bottomPanel.add(buttonsPanel, c);
498 	scanButton = new JButton("Scan");
499 	buttonsPanel.add(scanButton);
500 	scanButton.addActionListener(this);
501 	previewButton = new JButton("Preview Window");
502 	buttonsPanel.add(previewButton);
503 	previewButton.addActionListener(this);
504 	}
505 
506 	/*
507 	 *	Set a SANE integer option.
508 	 */
setControlOption(int optNum, int val)509     public void setControlOption(int optNum, int val)
510 	{
511 	int [] info = new int[1];
512 	if (sane.setControlOption(saneHandle, optNum,
513 			SaneOption.ACTION_SET_VALUE, val, info)
514 							!= Sane.STATUS_GOOD)
515 		System.out.println("setControlOption() failed.");
516 	checkOptionInfo(info[0]);
517 	}
518 
519 	/*
520 	 *	Set a SANE text option.
521 	 */
setControlOption(int optNum, String val)522     public void setControlOption(int optNum, String val)
523 	{
524 	int [] info = new int[1];
525 	if (sane.setControlOption(saneHandle, optNum,
526 			SaneOption.ACTION_SET_VALUE, val, info)
527 							!= Sane.STATUS_GOOD)
528 		System.out.println("setControlOption() failed.");
529 	checkOptionInfo(info[0]);
530 	}
531 
532 	/*
533 	 *	Check the 'info' word returned from setControlOption().
534 	 */
checkOptionInfo(int info)535     private void checkOptionInfo(int info)
536 	{
537 					// Does everything completely change?
538 	if ((info & SaneOption.INFO_RELOAD_OPTIONS) != 0)
539 		reinit();
540 					// Need to update status line?
541 	if ((info & SaneOption.INFO_RELOAD_PARAMS) != 0)
542 		;			// ++++++++FILL IN.
543 	}
544 
545 	/*
546 	 *	Handle a user action.
547 	 */
actionPerformed(ActionEvent e)548     public void actionPerformed(ActionEvent e)
549 	{
550 	if (e.getSource() == scanButton)
551 		{
552 		System.out.println("Rescanning");
553 					// Get file name.
554 		String fname = outputField.getText();
555 		if (fname == null || fname.length() == 0)
556 			fname = "out.pnm";
557 		try
558 			{
559 			outFile = new FileOutputStream(fname);
560 			}
561 		catch (IOException oe)
562 			{
563 			System.out.println("Error creating file:  " + fname);
564 			outFile = null;
565 			return;
566 			}
567 					// Disable "Scan" button.
568 		scanButton.setEnabled(false);
569 		scanIt.setOutputFile(outFile);
570 		imageDisplay.setImage(scanIt, this);
571 		}
572 	else if (e.getSource() == browseButton)
573 		{
574 		File file;		// For working with names.
575 		FileDialog dlg = new FileDialog(this, "Output Filename",
576 						FileDialog.SAVE);
577 		if (outDir != null)	// Set to last directory.
578 			dlg.setDirectory(outDir);
579 					// Get current name.
580 		String fname = outputField.getText();
581 		if (fname != null)
582 			{
583 			file = new File(fname);
584 			dlg.setFile(file.getName());
585 			String dname = file.getParent();
586 			if (dname != null)
587 				dlg.setDirectory(outDir);
588 			}
589 		dlg.show();		// Wait for result.
590 		fname = dlg.getFile();
591 					// Store dir. for next time.
592 		outDir = dlg.getDirectory();
593 		if (fname != null)
594 			{
595 			file = new File(outDir, fname);
596 			String curDir;
597 			String fullName;
598 			try
599 				{
600 				curDir = (new File(".")).getCanonicalPath();
601 				fullName = file.getCanonicalPath();
602 					// Not in current directory?
603 				if (!curDir.equals(
604 					(new File(fullName)).getParent()))
605 					fname = fullName;
606 				}
607 			catch (IOException ex)
608 				{ curDir = "."; fname = file.getName(); }
609 			outputField.setText(fname);
610 			}
611 		}
612 	else if (e.getSource() == exitMenuItem)
613 		{
614 		finalize();		// Just to be safe.
615 		System.exit(0);
616 		}
617 	}
618 
619 	/*
620 	 *	Handle checkable menu items.
621 	 */
itemStateChanged(ItemEvent e)622     public void itemStateChanged(ItemEvent e)
623 	{
624 	if (e.getSource() == mmMenuItem)
625 		{			// Wants mm.
626 		unitLength = 1;
627 		if (e.getStateChange() == ItemEvent.SELECTED)
628 			{
629 			cmMenuItem.setState(false);
630 			inMenuItem.setState(false);
631 			reinit();	// Re-display controls.
632 			}
633 		else			// Don't let him deselect.
634 			mmMenuItem.setState(true);
635 		}
636 	if (e.getSource() == cmMenuItem)
637 		{			// Wants cm.
638 		unitLength = 10;
639 		if (e.getStateChange() == ItemEvent.SELECTED)
640 			{
641 			mmMenuItem.setState(false);
642 			inMenuItem.setState(false);
643 			reinit();	// Re-display controls.
644 			}
645 		else
646 			cmMenuItem.setState(true);
647 		}
648 	if (e.getSource() == inMenuItem)
649 		{			// Wants inches.
650 		unitLength = 25.4;
651 		if (e.getStateChange() == ItemEvent.SELECTED)
652 			{
653 			mmMenuItem.setState(false);
654 			cmMenuItem.setState(false);
655 			reinit();	// Re-display controls.
656 			}
657 		else			// Don't let him deselect.
658 			inMenuItem.setState(true);
659 		}
660 	}
661 
662 	/*
663 	 *	Scan is complete.
664 	 */
imageDone(boolean error)665     public void imageDone(boolean error)
666 	{
667 	if (error)
668 		System.out.println("Scanning error.");
669 	if (outFile != null)		// Close file.
670 		try
671 			{
672 			outFile.close();
673 			}
674 		catch (IOException e)
675 			{ }
676 	scanButton.setEnabled(true);	// Can scan again.
677 	}
678 
679 	/*
680 	 *	Default window event handlers.
681 	 */
windowClosed(WindowEvent event)682     public void windowClosed(WindowEvent event)
683 	{
684 	finalize();			// Just to be safe.
685 	}
windowDeiconified(WindowEvent event)686     public void windowDeiconified(WindowEvent event) { }
windowIconified(WindowEvent event)687     public void windowIconified(WindowEvent event) { }
windowActivated(WindowEvent event)688     public void windowActivated(WindowEvent event) { }
windowDeactivated(WindowEvent event)689     public void windowDeactivated(WindowEvent event) { }
windowOpened(WindowEvent event)690     public void windowOpened(WindowEvent event) { }
691 					// Handle closing event.
windowClosing(WindowEvent event)692     public void windowClosing(WindowEvent event)
693 	{
694 	finalize();			// Just to be safe.
695 	System.exit(0);
696 	}
697     }
698 
699 /*
700  *	Interface for our dialog controls.
701  */
702 interface SaneComponent
703     {
setFromControl()704     public void setFromControl();	// Ask SANE control for current value.
705     }
706 
707 /*
708  *	A checkbox in our dialog.
709  */
710 class SaneCheckBox extends JCheckBox implements ActionListener,
711 					SaneComponent
712     {
713     private Jscanimage dialog;		// That which created us.
714     private int optNum;			// Option #.
715 
716 	/*
717 	 *	Create with given state.
718 	 */
SaneCheckBox(String title, Jscanimage dlg, int opt, String tip)719     public SaneCheckBox(String title, Jscanimage dlg, int opt, String tip)
720 	{
721 	super(title, false);
722 	dialog = dlg;
723 	optNum = opt;
724 					// Set tool tip.
725 	if (tip != null && tip.length() != 0)
726 		super.setToolTipText(tip);
727 	addActionListener(this);	// Listen to ourself.
728 	}
729 
730 	/*
731 	 *	Update Sane device option with current setting.
732 	 */
actionPerformed(ActionEvent e)733     public void actionPerformed(ActionEvent e)
734 	{
735 	System.out.println("In SaneCheckBox::actionPerformed()");
736 	int val = isSelected() ? 1 : 0;
737 	dialog.setControlOption(optNum, val);
738 	}
739 
740 	/*
741 	 *	Update from Sane control's value.
742 	 */
setFromControl()743     public void setFromControl()
744 	{
745 	int [] val = new int[1];
746 	if (dialog.getSane().getControlOption(
747 			dialog.getSaneHandle(), optNum, val, null)
748 						== Sane.STATUS_GOOD)
749 		setSelected(val[0] != 0);
750 	}
751     }
752 
753 /*
754  *	A slider in our dialog.  This base class handles integer ranges.
755  *	It consists of a slider and a label which shows the current value.
756  */
757 class SaneSlider extends JPanel implements SaneComponent, ChangeListener
758     {
759     protected Jscanimage dialog;	// That which created us.
760     protected int optNum;		// Option #.
761     protected JSlider slider;		// The slider itself.
762     protected JLabel label;		// Shows current value.
763 
764 	/*
765 	 *	Create with given state.
766 	 */
SaneSlider(int min, int max, Jscanimage dlg, int opt, String tip)767     public SaneSlider(int min, int max, Jscanimage dlg, int opt, String tip)
768 	{
769 	super(new GridBagLayout());	// Create panel.
770 	dialog = dlg;
771 	optNum = opt;
772 	GridBagConstraints c = new GridBagConstraints();
773 					// Want 1 row.
774 	c.gridx = GridBagConstraints.RELATIVE;
775 	c.gridy = 0;
776 	c.anchor = GridBagConstraints.CENTER;
777 	c.fill = GridBagConstraints.HORIZONTAL;
778 	c.weighty = .8;
779 	c.weightx = .2;			// Give less weight to value label.
780 	label = new JLabel("00", SwingConstants.RIGHT);
781 	add(label, c);
782 	c.weightx = .8;			// Give most weight to slider.
783 	c.fill = GridBagConstraints.HORIZONTAL;
784 	slider = new JSlider(JSlider.HORIZONTAL, min, max,
785 							min + (max - min)/2);
786 	add(slider, c);
787 					// Set tool tip.
788 	if (tip != null && tip.length() != 0)
789 		super.setToolTipText(tip);
790 	slider.addChangeListener(this);	// Listen to ourself.
791 	}
792 
793 	/*
794 	 *	Update Sane device option.
795 	 */
stateChanged(ChangeEvent e)796     public void stateChanged(ChangeEvent e)
797 	{
798 	int val = slider.getValue();
799 	label.setText(String.valueOf(val));
800 	dialog.setControlOption(optNum, val);
801 	}
802 
803 	/*
804 	 *	Update from Sane control's value.
805 	 */
setFromControl()806     public void setFromControl()
807 	{
808 	int [] val = new int[1];	// Get current SANE control value.
809 	if (dialog.getSane().getControlOption(
810 			dialog.getSaneHandle(), optNum, val, null)
811 						== Sane.STATUS_GOOD)
812 		{
813 		slider.setValue(val[0]);
814 		label.setText(String.valueOf(val[0]));
815 		}
816 	}
817     }
818 
819 /*
820  *	A slider with fixed-point values:
821  */
822 class FixedSaneSlider extends SaneSlider
823     {
824     private static final int SCALE_MIN = -32000;
825     private static final int SCALE_MAX = 32000;
826     double min, max;			// Min, max in floating-point.
827     boolean optMM;			// Option is in millimeters, so should
828 					//   be scaled to user's pref.
829     private NumberFormat format;	// For printing label.
830 	/*
831 	 *	Create with given fixed-point range.
832 	 */
FixedSaneSlider(int fixed_min, int fixed_max, boolean mm, Jscanimage dlg, int opt, String tip)833     public FixedSaneSlider(int fixed_min, int fixed_max, boolean mm,
834 					Jscanimage dlg, int opt, String tip)
835 	{
836 					// Create with large integer range.
837 	super(SCALE_MIN, SCALE_MAX, dlg, opt, tip);
838 	format = NumberFormat.getInstance();
839 					// For now, show 1 decimal point.
840 	format.setMinimumFractionDigits(1);
841 	format.setMaximumFractionDigits(1);
842 					// Store actual range.
843 	min = dlg.getSane().unfix(fixed_min);
844 	max = dlg.getSane().unfix(fixed_max);
845 	optMM = mm;
846 	}
847 
848 	/*
849 	 *	Update Sane device option.
850 	 */
stateChanged(ChangeEvent e)851     public void stateChanged(ChangeEvent e)
852 	{
853 	double val = (double) slider.getValue();
854 					// Convert to actual control scale.
855 	val = min + ((val - SCALE_MIN)/(SCALE_MAX - SCALE_MIN)) * (max - min);
856 	label.setText(format.format(optMM ?
857 					val/dialog.getUnitLength() : val));
858 	dialog.setControlOption(optNum, dialog.getSane().fix(val));
859 	}
860 
861 	/*
862 	 *	Update from Sane control's value.
863 	 */
setFromControl()864     public void setFromControl()
865 	{
866 	int [] ival = new int[1];	// Get current SANE control value.
867 	if (dialog.getSane().getControlOption(
868 			dialog.getSaneHandle(), optNum, ival, null)
869 						== Sane.STATUS_GOOD)
870 		{
871 		double val = dialog.getSane().unfix(ival[0]);
872 					// Show value with user's pref.
873 		label.setText(format.format(optMM ?
874 					val/dialog.getUnitLength() : val));
875 		val = SCALE_MIN + ((val - min)/(max - min)) *
876 						(SCALE_MAX - SCALE_MIN);
877 		slider.setValue((int) val);
878 		}
879 	}
880     }
881 
882 /*
883  *	A Button in our dialog.
884  */
885 class SaneButton extends JButton implements ActionListener
886     {
887     private Jscanimage dialog;		// That which created us.
888     private int optNum;			// Option #.
889 
890 	/*
891 	 *	Create with given state.
892 	 */
SaneButton(String title, Jscanimage dlg, int opt, String tip)893     public SaneButton(String title, Jscanimage dlg, int opt, String tip)
894 	{
895 	super(title);
896 	dialog = dlg;
897 	optNum = opt;
898 					// Set tool tip.
899 	if (tip != null && tip.length() != 0)
900 		super.setToolTipText(tip);
901 	addActionListener(this);	// Listen to ourself.
902 	}
903 
904 	/*
905 	 *	Update Sane device option.
906 	 */
actionPerformed(ActionEvent e)907     public void actionPerformed(ActionEvent e)
908 	{
909 	dialog.setControlOption(optNum, 0);
910 	System.out.println("In SaneButton::actionPerformed()");
911 	}
912     }
913 
914 /*
915  *	A combo-box for showing a list of items.
916  */
917 abstract class SaneComboBox extends JComboBox
918 				implements SaneComponent, ItemListener
919     {
920     protected Jscanimage dialog;	// That which created us.
921     protected int optNum;		// Option #.
922 
923 	/*
924 	 *	Create it.
925 	 */
SaneComboBox(Jscanimage dlg, int opt, String tip)926     public SaneComboBox(Jscanimage dlg, int opt, String tip)
927 	{
928 	dialog = dlg;
929 	optNum = opt;
930 					// Set tool tip.
931 	if (tip != null && tip.length() != 0)
932 		super.setToolTipText(tip);
933 	addItemListener(this);
934 	}
935 
936 	/*
937 	 *	Select desired item by its text.
938 	 */
select(String text)939     protected void select(String text)
940 	{
941 	int cnt = getItemCount();
942 	for (int i = 0; i < cnt; i++)
943 		if (text.equals(getItemAt(i)))
944 			{
945 			setSelectedIndex(i);
946 			break;
947 			}
948 	}
949 
setFromControl()950     abstract public void setFromControl();
itemStateChanged(ItemEvent e)951     abstract public void itemStateChanged(ItemEvent e);
952     }
953 
954 /*
955  *	A list of strings.
956  */
957 class SaneStringBox extends SaneComboBox
958     {
959 
960 	/*
961 	 *	Create it.
962 	 */
SaneStringBox(Jscanimage dlg, int opt, String tip)963     public SaneStringBox(Jscanimage dlg, int opt, String tip)
964 	{
965 	super(dlg, opt, tip);
966 	}
967 
968 	/*
969 	 *	Update from Sane control's value.
970 	 */
setFromControl()971     public void setFromControl()
972 	{
973 					// Let's get the whole list.
974 	SaneOption opt = dialog.getSane().getOptionDescriptor(
975 					dialog.getSaneHandle(), optNum);
976 	if (opt == null)
977 		return;
978 	removeAllItems();		// Clear list.
979 					// Go through list from option.
980 	for (int i = 0; opt.stringListConstraint[i] != null; i++)
981 		addItem(opt.stringListConstraint[i]);
982 					// Get value.
983 	byte buf[] = new byte[opt.size + 1];
984 	if (dialog.getSane().getControlOption(
985 			dialog.getSaneHandle(), optNum, buf, null)
986 						== Sane.STATUS_GOOD)
987 		select(new String(buf));
988 	}
989 
990 	/*
991 	 *	An item was selected.
992 	 */
itemStateChanged(ItemEvent e)993     public void itemStateChanged(ItemEvent e)
994 	{
995 				// Get current selection.
996 	String val = (String) getSelectedItem();
997 	if (val != null)
998 		dialog.setControlOption(optNum, val);
999 	}
1000     }
1001 
1002 /*
1003  *	A list of integers.
1004  */
1005 class SaneIntBox extends SaneComboBox
1006     {
1007 
1008 	/*
1009 	 *	Create it.
1010 	 */
SaneIntBox(Jscanimage dlg, int opt, String tip)1011     public SaneIntBox(Jscanimage dlg, int opt, String tip)
1012 	{
1013 	super(dlg, opt, tip);
1014 	}
1015 
1016 	/*
1017 	 *	Update from Sane control's value.
1018 	 */
setFromControl()1019     public void setFromControl()
1020 	{
1021 					// Let's get the whole list.
1022 	SaneOption opt = dialog.getSane().getOptionDescriptor(
1023 					dialog.getSaneHandle(), optNum);
1024 	if (opt == null)
1025 		return;
1026 	removeAllItems();		// Clear list.
1027 					// Go through list from option.
1028 	int cnt = opt.wordListConstraint[0];
1029 	for (int i = 0; i < cnt; i++)
1030 		addItem(String.valueOf(opt.wordListConstraint[i + 1]));
1031 					// Get value.
1032 	int [] val = new int[1];	// Get current SANE control value.
1033 	if (dialog.getSane().getControlOption(
1034 			dialog.getSaneHandle(), optNum, val, null)
1035 						== Sane.STATUS_GOOD)
1036 		select(String.valueOf(val[0]));
1037 	}
1038 
1039 	/*
1040 	 *	An item was selected.
1041 	 */
itemStateChanged(ItemEvent e)1042     public void itemStateChanged(ItemEvent e)
1043 	{
1044 				// Get current selection.
1045 	String val = (String) getSelectedItem();
1046 	if (val != null)
1047 		try
1048 			{
1049 			Integer v = Integer.valueOf(val);
1050 			dialog.setControlOption(optNum, v.intValue());
1051 			}
1052 		catch (NumberFormatException ex) {  }
1053 	}
1054     }
1055 
1056 /*
1057  *	A list of fixed-point floating point numbers.
1058  */
1059 class SaneFixedBox extends SaneComboBox
1060     {
1061 
1062 	/*
1063 	 *	Create it.
1064 	 */
SaneFixedBox(Jscanimage dlg, int opt, String tip)1065     public SaneFixedBox(Jscanimage dlg, int opt, String tip)
1066 	{
1067 	super(dlg, opt, tip);
1068 	}
1069 
1070 	/*
1071 	 *	Update from Sane control's value.
1072 	 */
setFromControl()1073     public void setFromControl()
1074 	{
1075 					// Let's get the whole list.
1076 	SaneOption opt = dialog.getSane().getOptionDescriptor(
1077 					dialog.getSaneHandle(), optNum);
1078 	if (opt == null)
1079 		return;
1080 	removeAllItems();		// Clear list.
1081 					// Go through list from option.
1082 	int cnt = opt.wordListConstraint[0];
1083 	for (int i = 0; i < cnt; i++)
1084 		addItem(String.valueOf(dialog.getSane().unfix(
1085 					opt.wordListConstraint[i + 1])));
1086 					// Get value.
1087 	int [] val = new int[1];	// Get current SANE control value.
1088 	if (dialog.getSane().getControlOption(
1089 			dialog.getSaneHandle(), optNum, val, null)
1090 						== Sane.STATUS_GOOD)
1091 		select(String.valueOf(dialog.getSane().unfix(val[0])));
1092 	}
1093 
1094 	/*
1095 	 *	An item was selected.
1096 	 */
itemStateChanged(ItemEvent e)1097     public void itemStateChanged(ItemEvent e)
1098 	{
1099 				// Get current selection.
1100 	String val = (String) getSelectedItem();
1101 	if (val != null)
1102 		try
1103 			{
1104 			Double v = Double.valueOf(val);
1105 			dialog.setControlOption(optNum,
1106 					dialog.getSane().fix(v.doubleValue()));
1107 			}
1108 		catch (NumberFormatException ex) {  }
1109 	}
1110     }
1111 
1112 /*
1113  *	A text-entry field in our dialog.
1114  */
1115 class SaneTextField extends JTextField implements SaneComponent
1116     {
1117     private Jscanimage dialog;		// That which created us.
1118     private int optNum;			// Option #.
1119 
1120 	/*
1121 	 *	Create with given state.
1122 	 */
SaneTextField(int width, Jscanimage dlg, int opt, String tip)1123     public SaneTextField(int width, Jscanimage dlg, int opt, String tip)
1124 	{
1125 	super(width);			// Set initial text, # chars.
1126 	dialog = dlg;
1127 	optNum = opt;
1128 					// Set tool tip.
1129 	if (tip != null && tip.length() != 0)
1130 		super.setToolTipText(tip);
1131 	}
1132 
1133 	/*
1134 	 *	Update Sane device option with current setting when user types.
1135 	 */
processKeyEvent(KeyEvent e)1136     public void processKeyEvent(KeyEvent e)
1137 	{
1138 	super.processKeyEvent(e);	// Handle it normally.
1139 	if (e.getID() != KeyEvent.KEY_TYPED)
1140 		return;			// Just do it after keystroke.
1141 	String userText = getText();	// Get what's there, & save copy.
1142 	String newText = new String(userText);
1143 	dialog.setControlOption(optNum, newText);
1144 	if (!newText.equals(userText))	// Backend may have changed it.
1145 		setText(newText);
1146 	}
1147 
1148 	/*
1149 	 *	Update from Sane control's value.
1150 	 */
setFromControl()1151     public void setFromControl()
1152 	{
1153 	byte buf[] = new byte[1024];
1154 	if (dialog.getSane().getControlOption(
1155 			dialog.getSaneHandle(), optNum, buf, null)
1156 						== Sane.STATUS_GOOD)
1157 		{
1158 		String text = new String(buf);
1159 		System.out.println("SaneTextField::setFromControl: " + text);
1160 		setText(text);		// Set text in field.
1161 		setScrollOffset(0);
1162 		}
1163 	}
1164     }
1165