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