package uk.co.mmscomputing.device.twain;
import java.io.*;
import uk.co.mmscomputing.device.scanner.ScannerDevice;
import uk.co.mmscomputing.device.scanner.ScannerIOException;
import uk.co.mmscomputing.concurrent.*;
public class TwainSource extends TwainIdentity implements TwainConstants,ScannerDevice{
private boolean busy;
private int state;
private long hWnd;
private int showUI = 1; // show DS GUI
private int modalUI = 0; // is modeless GUI
private int iff = TWFF_BMP; // image file format
private TwainITransferFactory transferFactory;
private boolean userCancelled;
private Semaphore tw20Semaphore = null; // twain 2.0: wait for source to signal new image
private boolean tw20HaveImage = false;
TwainSource(TwainSourceManager manager,long hwnd,boolean bus){
super(manager);
this.hWnd=hwnd;
this.busy=bus;
this.state=3;
this.userCancelled=false;
this.transferFactory=new TwainDefaultTransferFactory();
}
byte[] getIdentity(){return identity;}
public boolean isBusy(){ return busy;}
void setBusy(boolean b){ busy=b; jtwain.signalStateChange(this);}
public int getState(){ return state;}
void setState(int s){ state=s;jtwain.signalStateChange(this);}
public void setCancel(boolean c){ userCancelled=c;}
public boolean getCancel(){ return userCancelled;}
void checkState(int state)throws TwainIOException{
if(this.state==state){return;}
throw new TwainIOException(getClass().getName()+".checkState:\n\tSource not in state "+state+" but in state "+this.state+".");
}
int getConditionCode()throws TwainIOException{
byte[] status=new byte[4]; // TW_STATUS
int rc =jtwain.callSource(identity,DG_CONTROL,DAT_STATUS,MSG_GET,status);
if(rc!=TWRC_SUCCESS){
throw new TwainResultException("Cannot retrieve twain source's status.",rc);
}
return jtwain.getINT16(status,0);
}
private void checkrc(int rc)throws TwainIOException{
switch(rc){
case TWRC_SUCCESS: return;
case TWRC_FAILURE:{
int cc = getConditionCode();
if(cc==TWCC_OPERATIONERROR){
// appl shouldn't bother user
}else{
throw TwainFailureException.create(cc);
}
}break;
case TWRC_CHECKSTATUS: throw new TwainResultException.CheckStatus();
case TWRC_CANCEL: throw new TwainResultException.Cancel();
case TWRC_DSEVENT: return;
case TWRC_NOTDSEVENT: throw new TwainResultException.NotDSEvent();
case TWRC_XFERDONE: throw new TwainResultException.TransferDone();
case TWRC_ENDOFLIST: throw new TwainResultException.EndOfList();
case TWRC_INFONOTSUPPORTED: throw new TwainResultException.InfoNotSupported();
case TWRC_DATANOTAVAILABLE: throw new TwainResultException.DataNotAvailable();
default:
System.err.println(getClass().getName()+".checkrc\n\trc="+rc);
throw new TwainResultException("Failed to call source.",rc);
}
}
public void call(int dg,int dat,int msg,byte[] data)throws TwainIOException{
checkrc(jtwain.callSource(identity,dg,dat,msg,data));
}
private void setCallbackProcedure()throws TwainIOException{
/*
typedef struct {
TW_MEMREF CallBackProc; // 0 : 4 or 8 byte ptr to callback procedure
TW_UINT32 RefCon; // 4, 8: application reference number
TW_INT16 Message; // 8,12:
} TW_CALLBACK, FAR * pTW_CALLBACK; // 10,14
*/
byte[] callback = new byte[(jtwain.getPtrSize()==4)?10:14];
int off = jtwain.setPtr(callback,0,jtwain.getCallBackMethod());
jtwain.setINT32(callback,off,0);
off += 4; jtwain.setINT16(callback,off,0);
call(DG_CONTROL,DAT_CALLBACK,MSG_REGISTER_CALLBACK,callback);
}
void open()throws TwainIOException{ // [1] 7-184 state 3: 3 -> 4
super.open();
if(isTwain20Source()){
try{
setCallbackProcedure();
}catch(Exception te){
maskTwain20Source(); // if we cannot set callback routine disable it.
System.out.println("3\b"+getClass().getName()+".open:\n\tCannot set twain 2.0 callback method.");
System.err.println(getClass().getName()+".open:\n\tCannot set twain 2.0 callback method.");
}
}
}
public TwainCapability[] getCapabilities()throws TwainIOException{
return TwainCapability.getCapabilities(this);
}
public TwainCapability getCapability(int cap)throws TwainIOException{ // use only in state 4
return new TwainCapability(this,cap);
}
public TwainCapability getCapability(int cap,int mode)throws TwainIOException{ // use only in state 4
return new TwainCapability(this,cap,mode);
}
public TwainITransferFactory getTransferFactory() {
return transferFactory;
}
public void setTransferFactory(TwainITransferFactory transferFactory){
if(transferFactory==null){
throw new IllegalArgumentException(getClass().getName()+".setTransferFactory\n\tTwain transfer factory cannot be null.");
}
this.transferFactory = transferFactory;
}
public void setShowUI(boolean enable){showUI=0;}
//i' set this to 0 for a dirty hack
public boolean isModalUI(){return (modalUI==0);}
public void setCapability(int cap,boolean v)throws ScannerIOException{
TwainCapability tc=getCapability(cap,MSG_GETCURRENT);
if(tc.booleanValue()!=v){
tc.setCurrentValue(v);
if(getCapability(cap).booleanValue()!=v){
throw new ScannerIOException(getClass().getName()+".setCapability:\n\tCannot set capability "+TwainCapability.getCapName(cap)+" to "+v);
}
}
}
public void setCapability(int cap,int v)throws ScannerIOException{
TwainCapability tc=getCapability(cap,MSG_GETCURRENT);
if(tc.intValue()!=v){
tc.setCurrentValue(v);
if(getCapability(cap).intValue()!=v){
throw new ScannerIOException(getClass().getName()+".setCapability:\n\tCannot set capability "+TwainCapability.getCapName(cap)+" to "+v);
}
}
}
public void setCapability(int cap,double v)throws ScannerIOException{
TwainCapability tc=getCapability(cap,MSG_GETCURRENT);
if(tc.doubleValue()!=v){
tc.setCurrentValue(v);
if(getCapability(cap).doubleValue()!=v){
throw new ScannerIOException(getClass().getName()+".setCapability:\n\tCannot set capability "+TwainCapability.getCapName(cap)+" to "+v);
}
}
}
// negotiation options common to Twain & Sane
public boolean isUIControllable(){
try{ // TW_ONEVALUE/TW_BOOL if >=1.6 TWAIN compliant
return getCapability(CAP_UICONTROLLABLE).booleanValue();
}catch(Exception e){ // if some error assume source does not support ShowUI=false
jtwain.signalException(getClass().getName()+".isUIControllable:\n\t"+e);return false;
}
}
public boolean isDeviceOnline(){ // [1] 9-369 CAP_DEVICEONLINE
try{ // TW_ONEVALUE/TW_BOOL
return getCapability(CAP_DEVICEONLINE).booleanValue();
}catch(Exception e){ // if some error assume source is on
jtwain.signalException(getClass().getName()+".isOnline:\n\t"+e);return true;
}
}
public void setShowUserInterface(boolean show)throws ScannerIOException{
setShowUI(show);
}
public void setShowProgressBar(boolean show)throws ScannerIOException{
setCapability(CAP_INDICATORS,show); // works only if user interface is inactive
}
public void setResolution(double dpi)throws ScannerIOException{
setCapability(ICAP_UNITS,TWUN_INCHES); // setResolution expects dots per inch
setCapability(ICAP_XRESOLUTION,dpi);
setCapability(ICAP_YRESOLUTION,dpi);
}
public void setRegionOfInterest(int x, int y, int w, int h)throws ScannerIOException{
if((x==-1)&&(y==-1)&&(w==-1)&&(h==-1)){ // int version of setRegionOfInterest expects pixels
new TwainImageLayout(this).reset();
}else{
setCapability(ICAP_UNITS,TWUN_PIXELS);
TwainImageLayout til=new TwainImageLayout(this);
til.get(); // System.out.println(til.toString());
til.setLeft(x);til.setTop(y); // scan from topLeft(x,y) to rightBottom(x+width,y+height)
til.setRight(x+w);til.setBottom(y+h);
til.set();
}
}
public void setRegionOfInterest(double x, double y, double w, double h)throws ScannerIOException{
if((x==-1)&&(y==-1)&&(w==-1)&&(h==-1)){ // double version of setRegionOfInterest expects millimeters
new TwainImageLayout(this).reset();
}else{
setCapability(ICAP_UNITS,TWUN_CENTIMETERS);
TwainImageLayout til=new TwainImageLayout(this);
til.get(); // System.out.println(til.toString());
til.setLeft(x/10.0);til.setTop(y/10.0); // scan from topLeft(x,y) to rightBottom(x+width,y+height)
til.setRight((x+w)/10.0);til.setBottom((y+h)/10.0);
til.set();
}
// til.get(); // System.out.println(til.toString());
}
public void select(String name)throws ScannerIOException{
checkState(3);
TwainSourceManager manager=jtwain.getSourceManager();
try{
TwainIdentity device=new TwainIdentity(manager);
device.getFirst(); // get first identity
while(true){ // while(not EndOfList Exception thrown)
if(device.getProductName().equals(name)){
System.arraycopy(device.identity,0,identity,0,identity.length);break;
}
device.getNext(); // get next identity
}
}catch(TwainResultException.EndOfList treeol){
throw new TwainIOException(getClass().getName()+".select(String name)\n\tCannot find twain data source: '"+name+"'");
}
}
// END: negotiation options common to Twain & Sane
// image acquisition
void enable()throws TwainIOException{ // state 4 -> 5
checkState(4);
jtwain.negotiateCapabilities(this); // still in state 4 tell application to negotiate capabilities and defaults
if(getState()<4){return;} // application <=> source negotiation failed => application closed source
int xfer=new TwainCapability.XferMech(this).intValue(); // what transfer mode do we use
if(xfer==TWSX_NATIVE){ // if native transfer (dib)
}else if(xfer==TWSX_FILE){ // if file transfer mode
try{ // if source supports different file formats
iff=getCapability(ICAP_IMAGEFILEFORMAT).intValue(); // cache file format
}catch(Exception e){ // else
iff=TWFF_BMP; // use default
}
}
if(isTwain20Source()){
tw20Semaphore = new Semaphore(0,true);
tw20HaveImage = false;
}
byte[] gui=null;
try{
gui=new byte[(jtwain.getPtrSize()==4)?8:12]; // TW_USERINTERFACE
jtwain.setINT16(gui,0,showUI); // 1: show gui; 0: do not show gui
jtwain.setINT16(gui,2,modalUI);
jtwain.setPtr(gui,4,hWnd); // set parent window
call(DG_CONTROL,DAT_USERINTERFACE,MSG_ENABLEDS,gui); // enable source; pop up gui if ShowGUI=true
modalUI=jtwain.getINT16(gui,2);
setState(5);
}catch(TwainResultException.CheckStatus trecs){ // ShowGUI=false not supported continue with GUI
setState(5); // or (twain 2.0 test ds does this the wrong way round;)
showUI=(~showUI)&0x01; // ShowGUI=true not supported continue without GUI
}catch(TwainResultException.Cancel trec){
disable();
close();
}
if(isTwain20Source()){
try{
tw20Semaphore.tryAcquire(5*60000,TimeUnit.MILLISECONDS); // wait for max 5 min
tw20Semaphore.release(); // release other threads
if(tw20HaveImage==true){
transferImage();
}else{
System.out.println("9\b"+getClass().getName()+".enable:\n\tscan timed out. Close data source.");
System.err.println(getClass().getName()+".enable:\n\tscan timed out Close data source.");
}
}catch(TwainIOException tioe){
System.out.println("9\b"+getClass().getName()+".enable:\n\t"+tioe);
System.err.println(getClass().getName()+".enable:\n\t"+tioe);
}catch(InterruptedException ie){
System.out.println("9\b"+getClass().getName()+".enable:\n\tscan interrupted");
System.err.println(getClass().getName()+".enable:\n\tscan interrupted");
}
}
}
private void transfer(TwainITransfer tt)throws TwainIOException{ // 5 -> 6
try{
byte[] pendingXfers=new byte[6]; // TW_PENDINGXFERS
do{
setState(6); // [2] 4-31
jtwain.setINT16(pendingXfers,0,-1); // pendingXfer.Count = -1 => application can deal with multiple images
try{
tt.setCancel(userCancelled);
tt.initiate();
}catch(TwainResultException.TransferDone tretd){ // state 7: memory allocated
setState(7);
tt.finish();
call(DG_CONTROL,DAT_PENDINGXFERS,MSG_ENDXFER,pendingXfers); // tell source we are done with image
if(jtwain.getINT16(pendingXfers,0)==0){ // state 6: if pendingXfers!=0
setState(5); // state 5: if pendingXfers==0
}
}catch(TwainUserCancelException tuce){ // state 6: cancel via setCancel(true)
call(DG_CONTROL,DAT_PENDINGXFERS,MSG_RESET,pendingXfers); // tell source to cancel pending images
setState(5);
}catch(TwainResultException.Cancel trec){ // state 7: cancel via source gui
tt.cancel();
call(DG_CONTROL,DAT_PENDINGXFERS,MSG_ENDXFER,pendingXfers); // state 6: tell source we are done with image
if(jtwain.getINT16(pendingXfers,0) > 0){
call(DG_CONTROL,DAT_PENDINGXFERS,MSG_RESET,pendingXfers); // tell source to cancel pending images
}
setState(5);
}catch(TwainFailureException tfe){ // state 6/7: no memory allocated
jtwain.signalException(getClass().getName()+".transfer:\n\t"+tfe);
call(DG_CONTROL,DAT_PENDINGXFERS,MSG_ENDXFER,pendingXfers); // tell source we are done with image
if(jtwain.getINT16(pendingXfers,0) > 0){
call(DG_CONTROL,DAT_PENDINGXFERS,MSG_RESET,pendingXfers); // tell source to cancel pending images
}
setState(5);
}finally{
tt.cleanup();
}
}while(jtwain.getINT16(pendingXfers,0)!=0); // ADF scanner: pendingXfers = -1 if not known
}finally{
if(userCancelled||(showUI==0)){ // User cannot close source
userCancelled=false;
disable(); // hence close here
close();
}
}
}
void transferImage()throws TwainIOException{
switch(getXferMech()){
case TWSX_NATIVE: transfer(transferFactory.createNativeTransfer(this)); break;
case TWSX_FILE: transfer(transferFactory.createFileTransfer(this)); break;
case TWSX_MEMORY: transfer(transferFactory.createMemoryTransfer(this)); break;
default: // shouldn't be here
System.out.println(getClass().getName()+".transferImage:\n\tDo not support this transfer mode: "+getXferMech());
System.err.println(getClass().getName()+".transferImage:\n\tDo not support this transfer mode: "+getXferMech());
break;
}
}
void disable()throws TwainIOException{ // state 5 -> 4
if(state<5){return;}
byte[] gui=new byte[(jtwain.getPtrSize()==4)?8:12]; // TW_USERINTERFACE
jtwain.setINT16(gui,0,-1); // TWON_DONTCARE8
jtwain.setINT16(gui,2,0);
jtwain.setPtr(gui,4,hWnd); // set parent window
call(DG_CONTROL,DAT_USERINTERFACE,MSG_DISABLEDS,gui);
setState(4);
}
public void close()throws TwainIOException{ // state 4 -> 3
if(state!=4){return;} // [1] 7-176 state 4: 4 -> 3
call(DG_CONTROL,DAT_IDENTITY,MSG_CLOSEDS,identity); // close session
busy=false;
setState(3);
}
int callback(int dg,int dat,int msg,long data)throws TwainIOException{
switch(msg){ // always messages from source
case MSG_XFERREADY: // state 5 -> 6
tw20HaveImage = true;
tw20Semaphore.release();
break;
case MSG_CLOSEDSOK: // Do not use DG_CONTROL/DAT_USERINTERFACE/MSG_ENABLEDSUIONLY,
// hence shouldn't get this event
case MSG_CLOSEDSREQ: // source wants us to close it
System.err.println("MSG_CLOSEDSOK,MSG_CLOSEDSREQ in callback routine");
tw20HaveImage = false;
tw20Semaphore.release();
break;
case MSG_DEVICEEVENT: // Do not allow this event, hence don't get this event
case MSG_NULL: // Event fully processed by source
break;
default:
System.err.println("9\b"+getClass().getName()+".callback:\n\tUnknown message in Twain 2.0 callback routine");
return TWRC_FAILURE;
}
return TWRC_SUCCESS;
}
int handleGetMessage(long msgPtr)throws TwainIOException{ // callback functions cpp -> java; windows thread;
if(state<5){return TWRC_NOTDSEVENT;}
try{
byte[] event=new byte[(jtwain.getPtrSize()==4)?6:10]; // TW_EVENT
int off = jtwain.setPtr(event,0,msgPtr); // twEvent.pEvent=(TW_MEMREF)&msg;
jtwain.setINT16(event,off,0); // twEvent.TWMessage=MSG_NULL;
call(DG_CONTROL,DAT_EVENT,MSG_PROCESSEVENT,event); // [1] 7 - 162
int message=jtwain.getINT16(event,off); // if event was handled by source
switch(message){ // any messages from source
case MSG_XFERREADY: // state 5 -> 6
transferImage(); // source has an image for us
break;
case MSG_CLOSEDSOK: // Do not use DG_CONTROL/DAT_USERINTERFACE/MSG_ENABLEDSUIONLY,
// hence shouldn't get this event
case MSG_CLOSEDSREQ: // source wants us to close it
disable();
close();
break;
case MSG_DEVICEEVENT: // Do not allow this event, hence don't get this event
case MSG_NULL: // Event fully processed by source
default:
}
return TWRC_DSEVENT;
}catch(TwainResultException.NotDSEvent trendse){ // if event was not handled by source
return TWRC_NOTDSEVENT;
}
}
public int getXferMech()throws TwainIOException{
return new TwainCapability.XferMech(this).intValue();
}
public void setXferMech(int mech){
try{
switch(mech){
case TWSX_NATIVE:
case TWSX_FILE:
case TWSX_MEMORY: break;
default: mech=TWSX_NATIVE; break;
}
TwainCapability tc;
tc=getCapability(ICAP_XFERMECH,MSG_GETCURRENT); // some sources cannot use enumerations to set capabilities,
if(tc.intValue()!=mech){ // hence use MSG_GETCURRENT => TW_ONEVALUE/TW_UINT16
tc.setCurrentValue(mech);
if(getCapability(ICAP_XFERMECH).intValue()!=mech){
jtwain.signalException(getClass().getName()+".setXferMech:\n\tCannot change transfer mechanism to mode="+mech);
}
}
}catch(TwainIOException e){ // Shouldn't happen must be supported by all sources.
jtwain.signalException(getClass().getName()+".setXferMech:\n\t"+e);
}
}
int getImageFileFormat(){return iff;}
public void setImageFileFormat(int iff){
try{
TwainCapability tc;
switch(iff){
case TWFF_TIFF: case TWFF_BMP: case TWFF_JFIF:
case TWFF_TIFFMULTI: case TWFF_PNG:
break;
default:
iff=TWFF_BMP; // (must be supported by all windows sources)
break;
}
tc=getCapability(ICAP_IMAGEFILEFORMAT,MSG_GETCURRENT);// TW_ONEVALUE/TW_UINT16
if(tc.intValue()!=iff){
tc.setCurrentValue(iff);
if(getCapability(ICAP_IMAGEFILEFORMAT).intValue()!=iff){
jtwain.signalException(getClass().getName()+".setImageFileFormat:\n\tCannot change file format to format="+iff);
}
}
}catch(Exception e){
jtwain.signalException(getClass().getName()+".setImageFileFormat:\n\t"+e);
}
}
// public TwainFileSystem getFileSystem(){return new TwainFileSystem(this);}
}
// [1] Twain Spec 1.9
// [2] Twain Spec 2.0