/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org Code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2001
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
/*
 * Implements the nsSanePluginInterface for the SANE plugin
 */

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <setjmp.h>
#include <gdk/gdk.h>
#include <gdk/gdkprivate.h>
#include <gtk/gtk.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "nsIIOService.h"
#include "nsSanePlugin.h"
#include "plstr.h"
#include "prmem.h"
#include "nsIEventQueueService.h"
#include "nsIEventQueue.h"

extern "C"
{
#include "jpeglib.h"
#include "sane/sane.h"
#include "sane/sanei.h"
#include "sane/saneopts.h"
}

#define MAX_OPT_SIZE (1024 * 2)

struct my_JPEG_error_mgr
{
    struct jpeg_error_mgr pub;
    sigjmp_buf setjmp_buffer;
};
typedef struct my_JPEG_error_mgr *emptr;

static void PR_CALLBACK ThreadedDestroyEvent(PLEvent* aEvent);
static void PR_CALLBACK ThreadedHandleEvent(PLEvent* aEvent);

//static void PR_CALLBACK scanimage_thread_routine( void * arg );

static void store_filename(GtkFileSelection *selector, gpointer user_data);
static nsresult optioncat( char ** dest, const char * src, 
                           const char * ending );
static void my_jpeg_error_exit (j_common_ptr cinfo);
static unsigned char* jpeg_file_to_rgb (FILE * f, int *w, int *h);
static gboolean draw (GtkWidget *widget, GdkEventExpose *event, gpointer data);
static unsigned char * crop_image(unsigned char *rgb_data, int *rgb_width, 
                                  int *rgb_height,	gint x, gint y, 
                                  gint w, gint h);
static unsigned char * scale_image(unsigned char *rgb_data, int rgb_width, 
                                   int rgb_height, int w, int h);

static PRInt32 gPluginObjectCount = 0;

static NS_DEFINE_CID(kCPluginManagerCID,        NS_PLUGINMANAGER_CID         );
static NS_DEFINE_CID(kEventQueueService,        NS_EVENTQUEUESERVICE_CID     );


///////////////////////////////////////////////////////////////////////////////
// Plugin Instance API Methods

nsSanePluginInstance::nsSanePluginInstance( void )
    : fPeer( NULL ), fWindow( NULL ), fMode( nsPluginMode_Embedded )
{
#ifdef DEBUG
    printf("nsSanePluginInstance::nsSanePluginInstance()\n");
#endif

    PR_AtomicIncrement(&gPluginObjectCount);

    // set default jpeg compression attributes
    mCompQuality = 10;
    mCompMethod = JDCT_FASTEST;

    // Initialize sane interface
    mSaneDevice = nsnull;
    mSaneOpen = SANE_FALSE;
    mRGBData = nsnull;
    mBottomRightXChange = mBottomRightYChange = 
        mTopLeftXChange = mTopLeftYChange = 0;

    // initialize JavaScript callback members
    mOnScanCompleteScript = nsnull;
    mOnInitCompleteScript = nsnull;
    mScanThread = nsnull;
    mUIThread = PR_GetCurrentThread();

    mState = 0; // idle
    mSuccess = 1; // success
    mFileSelection = nsnull;

    // initialize gtk widgets
    fPlatform.widget = nsnull;
    mEvent_box = nsnull;
    mDrawing_area = nsnull;

    if (NS_FAILED(CallCreateInstance(kCPluginManagerCID, &mPluginManager))) {
        NS_ERROR("Error trying to create plugin manager!");
        return;
    }
}

nsSanePluginInstance::~nsSanePluginInstance(void)
{
#ifdef DEBUG
    printf("nsSanePluginInstance::~nsSanePluginInstance(%i)\n", 
           gPluginObjectCount );
#endif 

    PR_AtomicDecrement(&gPluginObjectCount);
    PlatformDestroy(); // Perform platform specific cleanup

    if (mPluginManager) {
        mPluginManager->Release();
        mPluginManager = nsnull;
    }

    sane_exit();

    // cleanup old widgets
    if (fPlatform.widget)
        gtk_widget_destroy(fPlatform.widget);
    if (mEvent_box)
        gtk_widget_destroy(mEvent_box);
    if (mDrawing_area)
        gtk_widget_destroy(mDrawing_area);

    NS_IF_RELEASE(fPeer); 
    PR_FREEIF(mSaneDevice);
    PR_FREEIF(mOnScanCompleteScript);
    PR_FREEIF(mOnInitCompleteScript);

}

// This macro produces simple versions of QueryInterface, AddRef and Release
// See the nsISupports.h header file for details.
NS_IMPL_ISUPPORTS2(nsSanePluginInstance, 
                   nsIPluginInstance, 
                   nsISanePluginInstance)

NS_METHOD
nsSanePluginInstance::Initialize( nsIPluginInstancePeer* peer )
{
#ifdef DEBUG
    printf("nsSanePluginInstance::Initialize()\n");
#endif

    NS_ASSERTION(peer != NULL, "null peer");

    nsIPluginTagInfo * taginfo;
    const char * const * names = nsnull;
    const char * const * values = nsnull;
    PRUint16 count = 0;
    nsresult result;

    gdk_rgb_init();

    fPeer = peer;
    peer->AddRef();
    result = peer->GetMode( &fMode );

    if ( NS_FAILED( result ) )
        return result;

    result = peer->QueryInterface( nsIPluginTagInfo::GetIID(),
                                   ( void ** )&taginfo );

    if ( NS_SUCCEEDED( result ) )
    {
        taginfo->GetAttributes( count, names, values );
        NS_IF_RELEASE( taginfo );
    }

    // Initialize default line attribute info
    mLineWidth = 5;
    mLineStyle = GDK_LINE_ON_OFF_DASH;
    mCapStyle = GDK_CAP_BUTT;
    mJoinStyle = GDK_JOIN_MITER;

    // Get user requested attribute info
    for (int i=0; i < count; i++) {

        if (PL_strcasecmp(names[i], "line_width") == 0) {
            //////////////
            // Line width
            sscanf(values[i], "%i", &mLineWidth);

        } else if (PL_strcasecmp(names[i], "line_style") == 0) {
            //////////////
            // Line style
            if (PL_strcasecmp(values[i], "dash") == 0) 
                mLineStyle = GDK_LINE_ON_OFF_DASH;
            else if (PL_strcasecmp(values[i], "solid") == 0)
                mLineStyle = GDK_LINE_SOLID;
            else if (PL_strcasecmp(values[i], "double_dash") == 0)
                mLineStyle = GDK_LINE_DOUBLE_DASH;

        } else if (PL_strcasecmp(names[i], "cap_style") == 0) {
            /////////////
            // Cap style
            if (PL_strcasecmp(values[i], "round") == 0) 
                mCapStyle = GDK_CAP_ROUND;
            else if (PL_strcasecmp(values[i], "projecting") == 0)
                mCapStyle = GDK_CAP_PROJECTING;
            else if (PL_strcasecmp(values[i], "butt") == 0)
                mCapStyle = GDK_CAP_BUTT;
            else if (PL_strcasecmp(values[i], "not_last") == 0)
                mCapStyle = GDK_CAP_NOT_LAST;

        } else if (PL_strcasecmp(names[i], "join_style") == 0) {
            //////////////
            // Join style
            if (PL_strcasecmp(values[i], "miter") == 0) 
                mJoinStyle = GDK_JOIN_MITER;
            else if (PL_strcasecmp(values[i], "round") == 0)
                mJoinStyle = GDK_JOIN_ROUND;
            else if (PL_strcasecmp(values[i], "bevel") == 0)
                mJoinStyle = GDK_JOIN_BEVEL;

        } else if (PL_strcasecmp(names[i], "device") == 0) {
            //////////
            // Device
            PR_FREEIF(mSaneDevice);
            mSaneDevice = PL_strdup(values[i]);
        } else if (PL_strcasecmp(names[i], "onScanComplete") == 0) {
            //////////////////////////
            // onScanComplete Callback
            PR_FREEIF(mOnScanCompleteScript);
            mOnScanCompleteScript = PL_strdup(values[i]);
        } else if(PL_strcasecmp(names[i], "onInitComplete") == 0) {
            //////////////////////////
            // onInitComplete Callback
            PR_FREEIF(mOnInitCompleteScript);
            mOnInitCompleteScript = PL_strdup(values[i]);
        }
	}
    
    // the mImageFilename is used to store data directly 
    // from the sane device and also rendering to the screen
    sprintf( mImageFilename, "/tmp/nsSanePlugin.%p.jpg", (void *)this );

    PlatformNew(); 	/* Call Platform-specific initializations */
    return NS_OK;

}

NS_METHOD
nsSanePluginInstance::SaveImage()
{
#ifdef DEBUG
    printf("nsSanePluginInstance::SaveImage()\n");
#endif
    
    if (!mImageFilename) {
        NS_ERROR("Attempt to save image before initial scan!");
        return NS_ERROR_FAILURE;
    }

    mFileSelection = gtk_file_selection_new("Choose file to save image to.");
    
    // Set a default filename
    gtk_file_selection_set_filename(GTK_FILE_SELECTION(mFileSelection),
                                    "saved.jpg");

    // Callback for getting the filename
    gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(mFileSelection)->ok_button),
                       "clicked", GTK_SIGNAL_FUNC (store_filename), 
                       (gpointer) this);

    // Ensure the file selection dialog goes away after
    // 'OK' or 'CANCEL' button is selected
    gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(mFileSelection)->ok_button),
                              "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy),
                              GTK_OBJECT(mFileSelection));
    gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(mFileSelection)->cancel_button),
                              "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy),
                              GTK_OBJECT(mFileSelection));

    // ok, now show the file selection dialog
    gtk_widget_show(mFileSelection);
    
    return NS_OK;
}

NS_METHOD
nsSanePluginInstance::DoScanCompleteCallback()
{
#ifdef DEBUG
    printf("nsSanePluginInstance::DoScanCompleteCallback()\n");
#endif

    char *url;

    if (mOnScanCompleteScript && mPluginManager) {
        url = (char *)PR_Malloc(PL_strlen(mOnScanCompleteScript) + 13);
        if (!url)
            return NS_ERROR_OUT_OF_MEMORY;

        sprintf(url, "javascript:%s", mOnScanCompleteScript);   
        mPluginManager->GetURL((nsIPluginInstance *)this, url, "_self");

        PR_FREEIF(url);
    }
    return NS_OK;
}

NS_METHOD
nsSanePluginInstance::DoInitCompleteCallback()
{
#ifdef DEBUG
    printf("nsSanePluginInstance::DoInitCompleteCallback()\n");
#endif

    char *url;

    if (mOnInitCompleteScript && mPluginManager) {
        url = (char *)PR_Malloc(PL_strlen(mOnInitCompleteScript) + 13);
        if (!url)
            return NS_ERROR_OUT_OF_MEMORY;

        sprintf(url, "javascript:%s", mOnInitCompleteScript);   
        mPluginManager->GetURL((nsIPluginInstance *)this, url, "_self");

        PR_FREEIF(url);
    }

    return NS_OK;
}

NS_METHOD
nsSanePluginInstance::GetPeer( nsIPluginInstancePeer* *result )
{
#ifdef DEBUG
    printf("nsSanePluginInstance::GetPeer()\n");
#endif

    fPeer->AddRef();
    *result = fPeer;
    return NS_OK;
}

NS_METHOD
nsSanePluginInstance::Start( void )
{
#ifdef DEBUG
    printf("nsSanePluginInstance::Start()\n");
#endif

    PR_FREEIF(mRGBData);
    mRGBData = nsnull;

    sane_init(0,0);
    mSaneOpen = SANE_FALSE;
    nsresult rv = OpenSaneDeviceIF();
    if (rv != NS_OK) {
        NS_ERROR("Failed to open device in Start()\n");
        return rv;
    }

    PlatformNew();

    // call onInitComplete callback
    DoInitCompleteCallback();
    return NS_OK;
}

NS_METHOD
nsSanePluginInstance::Stop( void )
{
#ifdef DEBUG
    printf("nsSanePluginInstance::Stop()\n");
#endif

    // Wait for scanner operation to complete.
    // TODO: Warn user that they are still scanning
    //       and send abort command to scanner if 
    //       the user wishes.
    if (mScanThread)
        PR_JoinThread(mScanThread);

    PR_FREEIF(mRGBData);

    // close sane
    if (mSaneOpen) {
        sane_close(mSaneHandle);
        sane_exit();
        mSaneOpen = SANE_FALSE;
    }

    if (remove ( mImageFilename ) == -1)
        perror(mImageFilename);

    return NS_OK;
}

NS_METHOD
nsSanePluginInstance::Destroy( void )
{
#ifdef DEBUG
    printf("nsSanePluginInstance::Destroy()\n");
#endif

    return NS_OK;
}

NS_METHOD
nsSanePluginInstance::SetWindow(nsPluginWindow* window)
{
#ifdef DEBUG
    printf("nsSanePluginInstance::SetWindow()\n");
#endif

    nsresult result;

    // The real work is handled in PlatformSetWindow
    // (as if this plug-in was multiplatform)
    result = PlatformSetWindow( window );
    fWindow = window;

    return result;
}

NS_METHOD
nsSanePluginInstance::NewStream( nsIPluginStreamListener** listener )
{
#ifdef DEBUG
    printf("nsSanePluginInstance::NewStream()\n");
#endif

    if ( listener != NULL )
        *listener = new nsSanePluginStreamListener( this );
    
    return NS_OK;
}

NS_METHOD
nsSanePluginInstance::Print( nsPluginPrint* printInfo )
{
#ifdef DEBUG
    printf("nsSanePluginInstance::Print()\n");
#endif

    if ( printInfo == NULL )
        return NS_ERROR_FAILURE;

    // needs to be implemented
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_METHOD
nsSanePluginInstance::HandleEvent(nsPluginEvent* event, PRBool* handled)
{
#ifdef DEBUG
    printf("nsSanePluginInstance::HandleEvent()\n");
#endif

    // Not used on unix platform.  Only here for potential
    // future porting efforts
    *handled = (PRBool)PlatformHandleEvent(event);
    return NS_OK;
}

NS_METHOD
nsSanePluginInstance::GetValue(nsPluginInstanceVariable variable, void *value)
{
#ifdef DEBUG
    printf("nsSanePluginInstance::GetValue()\n");
#endif

    //Needs to be implemented
    return NS_ERROR_NOT_IMPLEMENTED;
}

void
nsSanePluginInstance::PlatformNew( void )
{
#ifdef DEBUG
    printf("nsSaneInstance::PlatformNew\n");
#endif

    fPlatform.window = 0;
}

nsresult
nsSanePluginInstance::PlatformDestroy( void )
{
#ifdef DEBUG
    printf("nsSaneInstance::PlatformDestroy()\n");
#endif

	return NS_OK;
}

nsresult
nsSanePluginInstance::PlatformSetWindow( nsPluginWindow* window )
{
#ifdef DEBUG
    printf("nsSaneInstance::PlatformSetWindow()\n");
#endif


    if ( window == NULL || window->window == NULL )
        return NS_ERROR_NULL_POINTER;
    
    if ( fPlatform.superwin == (GdkSuperWin *)window->window )
        return NS_OK;

    // Record windowing info
    fPlatform.superwin = (GdkSuperWin *)window->window;
    fPlatform.height = window->height;
    fPlatform.width = window->width;

    // cleanup old widgets
    if (fPlatform.widget)
        gtk_widget_destroy(fPlatform.widget);
    if (mEvent_box)
        gtk_widget_destroy(mEvent_box);
    if (mDrawing_area)
        gtk_widget_destroy(mDrawing_area);

    fPlatform.widget = gtk_mozbox_new(fPlatform.superwin->bin_window);

    // Initialize the zoom box to outline the entire image
    mZoom_box.x = 0;
    mZoom_box.y = 0;
    mZoom_box.width = fPlatform.width;
    mZoom_box.height = fPlatform.height;

    // Setup an event box to contain the drawable
    mEvent_box = gtk_event_box_new();
    gtk_container_add(GTK_CONTAINER(fPlatform.widget), mEvent_box);
    gtk_widget_show(mEvent_box);

    // Setup the drawable
    mDrawing_area = ( GtkWidget * )gtk_drawing_area_new();
    gtk_drawing_area_size( GTK_DRAWING_AREA( mDrawing_area ),
                           window->width, window->height);
    gtk_container_add(GTK_CONTAINER(mEvent_box), mDrawing_area);
    gtk_widget_show(mDrawing_area);

    // Initialize line attributes for GC
    GdkGC * da_gc = mDrawing_area->style->fg_gc[mDrawing_area->state];
    gdk_gc_set_line_attributes ( da_gc, mLineWidth, mLineStyle, 
                                 mCapStyle, mJoinStyle);

    // Register signals
    gtk_signal_connect (GTK_OBJECT(mDrawing_area), "expose_event",
                        GTK_SIGNAL_FUNC(draw), this);

    // Now make everything visable
    gtk_widget_show( fPlatform.widget );

    return NS_OK;
}

int16
nsSanePluginInstance::PlatformHandleEvent(nsPluginEvent* event)
{
#ifdef DEBUG
    printf("nsSaneInstance::PlatformHandleEvent()\n");
#endif

    /* UNIX Plugins do not use HandleEvent */
    return 0;
}


NS_IMETHODIMP
nsSanePluginInstance::PaintImage(void)
{
#ifdef DEBUG
    printf("nsSaneInstance::PaintImage(%p)\n", this);
#endif

    if (mDrawing_area && fPlatform.widget) {
        if (mImageFilename) {

            if (!mRGBData) {

                if (mState)
                    // we are currently scanning, so the jpeg file
                    // is not valid yet
                    return NS_OK;
                
                FILE * f = fopen(mImageFilename, "rb");
                if (!f) 
                    // it's possible that an image hasn't been scanned yet
                    return NS_OK;

                mRGBData = jpeg_file_to_rgb(f, &mRGBWidth, &mRGBHeight);
                if (!mRGBData) {
                    NS_ERROR("Error converting jpeg to rgb data!");
                    return NS_ERROR_FAILURE;
                }

                fclose(f);

                // scale down to window size
                unsigned char * tmp = scale_image( mRGBData, 
                                                   mRGBWidth, mRGBHeight,
                                                   fPlatform.width,
                                                   fPlatform.height);
                if (!tmp) {
                    NS_ERROR("Error trying to scale image!");
                    return NS_ERROR_FAILURE;
                }

                // move to new data
                PR_FREEIF(mRGBData);
                mRGBData = tmp;
            }

            // render the image
            gdk_draw_rgb_image (mDrawing_area->window, 
                                mDrawing_area->style->fg_gc[GTK_STATE_NORMAL],
                                0, 0, fPlatform.width, fPlatform.height,
                                GDK_RGB_DITHER_MAX, mRGBData, 
                                fPlatform.width * 3);

            gdk_draw_rectangle (mDrawing_area->window,
                                mDrawing_area->style->fg_gc[mDrawing_area->state],
                                FALSE, mZoom_box.x, mZoom_box.y, 
                                mZoom_box.width, mZoom_box.height);        
            
            gdk_flush();
        }
    }

    return NS_OK;
}

char *
nsSanePluginInstance::GetImageFilename()
{
    if (!mImageFilename)
        return (char *)NULL;
    
    // Caller is responsible for freeing string
    return PL_strdup(mImageFilename);
}

GtkWidget *
nsSanePluginInstance::GetFileSelection()
{
    if (!mFileSelection)
        return (GtkWidget *)NULL;

    return mFileSelection;
}

PRBool
nsSanePluginInstance::IsUIThread()
{
    if (!mUIThread)
        return PR_FALSE;

    return (mUIThread == PR_GetCurrentThread());
}

// End of Plugin Instance API Methods.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Image Preview Interface.

NS_IMETHODIMP
nsSanePluginInstance::ZoomImage( PRUint16 x, PRUint16 y, 
                                 PRUint16 width, PRUint16 height)
{
#ifdef DEBUG
    printf("nsSaneInstance::ZoomImage()\n");
#endif

    int im_width, im_height, c_width, c_height, c_x, c_y;

    if (!mRGBData)
        return NS_ERROR_FAILURE;

    // get the actual image width and height
    im_width = mRGBWidth;
    im_height = mRGBHeight;

    if ( x >= (PRUint16)fPlatform.width ||
         y >= (PRUint16)fPlatform.height )
        return NS_ERROR_FAILURE;

    if ( width + mZoom_box.x >= (PRUint16)fPlatform.width )
        mZoom_box.width = fPlatform.width - mZoom_box.x;

    if ( height + mZoom_box.y >= (PRUint16) fPlatform.height )
        mZoom_box.height = fPlatform.height - mZoom_box.y;

    mZoom_box.x = x;
    mZoom_box.y = y;

    // translate the zoom box geometry to the image geometry
    c_width = (int)(((double)im_width/(double)fPlatform.width) * 
                    (double)mZoom_box.width);
    c_height =(int)(((double)im_height/(double)fPlatform.height) * 
                    (double)mZoom_box.height);
    c_x = (int)(((double)im_width/(double)fPlatform.width) * 
                (double)mZoom_box.x);
    c_y = (int)(((double)im_height/(double)fPlatform.height) * 
                (double)mZoom_box.y);

    unsigned char * tmp = crop_image(mRGBData, &mRGBWidth, &mRGBHeight,
                                     c_x, c_y, c_width, c_height);
    if (!tmp)
        return NS_ERROR_FAILURE;
    PR_FREEIF(mRGBData);
    mRGBData = tmp;

    mRGBWidth = c_width;
    mRGBHeight = c_height;

    mTopLeftXChange = (float)x/(float)fPlatform.width;
    mTopLeftYChange = (float)y/(float)fPlatform.height;
    mBottomRightXChange = (float)(fPlatform.width - (PRInt32)(x + mZoom_box.width))/(float)fPlatform.width;
    mBottomRightYChange = (float)(fPlatform.height - (PRInt32)(y + mZoom_box.height))/(float)fPlatform.height;

    mZoom_box.x = 0;
    mZoom_box.y = 0;
    mZoom_box.width = (PRUint16)fPlatform.width;
    mZoom_box.height = (PRUint16)fPlatform.height;

    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::ZoomImageWithAttributes( PRUint16 x, PRUint16 y, 
                                               PRUint16 width, PRUint16 height,
                                               PRInt32 req_line_width,
                                               const char * req_line_style,
                                               const char * req_cap_style,
                                               const char * req_join_style )
{
#ifdef DEBUG
    printf("nsSaneInstance::ZoomImageWithAttributes()\n");
#endif

    if (!mRGBData)
        return NS_ERROR_FAILURE;

    ZoomImage(x, y, width, height);

    /*
     * Set zoom box line attributes
     */

    // Line Width
    mLineWidth = req_line_width;

    // Line Style
    if (PL_strcasecmp(req_line_style, "dash") == 0) 
        mLineStyle = GDK_LINE_ON_OFF_DASH;
    else if (PL_strcasecmp(req_line_style, "solid") == 0)
        mLineStyle = GDK_LINE_SOLID;
    else if (PL_strcasecmp(req_line_style, "double_dash") == 0)
        mLineStyle = GDK_LINE_DOUBLE_DASH;

    // Cap Style
    if (PL_strcasecmp(req_cap_style, "round") == 0) 
        mCapStyle = GDK_CAP_ROUND;
    else if (PL_strcasecmp(req_cap_style, "projecting") == 0)
        mCapStyle = GDK_CAP_PROJECTING;
    else if (PL_strcasecmp(req_cap_style, "butt") == 0)
        mCapStyle = GDK_CAP_BUTT;
    else if (PL_strcasecmp(req_cap_style, "not_last") == 0)
        mCapStyle = GDK_CAP_NOT_LAST;

    // Join Style
    if (PL_strcasecmp(req_join_style, "miter") == 0) 
        mJoinStyle = GDK_JOIN_MITER;
    else if (PL_strcasecmp(req_join_style, "round") == 0)
        mJoinStyle = GDK_JOIN_ROUND;
    else if (PL_strcasecmp(req_join_style, "bevel") == 0)
        mJoinStyle = GDK_JOIN_BEVEL;

    // Refresh
    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::Crop(PRUint16 x, PRUint16 y, 
                           PRUint16 width, PRUint16 height)
{
#ifdef DEBUG
    printf("nsSaneInstance::Crop()\n");
#endif

    if ( (x <= (PRUint16)fPlatform.width - width ) &&
         (y <= (PRUint16)fPlatform.height - height) ) {
        
        mZoom_box.x = x; mZoom_box.y = y;
        mZoom_box.width = width;
        mZoom_box.height = height;

    } else {
        return NS_ERROR_FAILURE;
    }

    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::Restore(void)
{
#ifdef DEBUG
    printf("nsSaneInstance::Restore()\n");
#endif

    PR_FREEIF(mRGBData);
    mRGBData = nsnull;

    // Reset zoom box
    mZoom_box.x = mZoom_box.y = 0;
    mZoom_box.width = (PRUint16) fPlatform.width;
    mZoom_box.height = (PRUint16) fPlatform.height;

    PaintImage();

    return NS_OK;
}


NS_IMETHODIMP
nsSanePluginInstance::GetZoomX(PRUint16 * x) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomX()\n");
#endif

    NS_ASSERTION(x != NULL, "null x pointer");
    if (!x)
        return NS_ERROR_NULL_POINTER;

    *x = mZoom_box.x;
    
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomX(PRUint16 x) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomX()\n");
#endif

    if (x > (PRInt32) fPlatform.width)
        mZoom_box.x = (PRInt32) fPlatform.width;
    else
        mZoom_box.x = x;

    return PaintImage();
}


NS_IMETHODIMP
nsSanePluginInstance::GetZoomY(PRUint16 * y) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomY()\n");
#endif

    NS_ASSERTION(y != NULL, "null y pointer");
    if (!y)
        return NS_ERROR_NULL_POINTER;

    *y = mZoom_box.y;
    
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomY(PRUint16 y) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomY()\n");
#endif

    if (y > (PRInt32) fPlatform.height)
        mZoom_box.y = (PRInt32)fPlatform.height;
    else
        mZoom_box.y = y;
    
    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::GetZoomWidth(PRUint16 * width) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomWidth()\n");
#endif

    NS_ASSERTION(width != NULL, "null width pointer");
    if (!width)
        return NS_ERROR_NULL_POINTER;

    *width = mZoom_box.width;
    
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomWidth(PRUint16 width) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomWidth()\n");
#endif

    if (width + mZoom_box.x > (PRUint16)fPlatform.width)
        mZoom_box.width = (PRInt32) fPlatform.width - mZoom_box.x;
    else
        mZoom_box.width = width;

    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::GetZoomHeight(PRUint16 * height) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomHeight()\n");
#endif

    NS_ASSERTION(height != NULL, "null height pointer");
    if (!height)
        return NS_ERROR_NULL_POINTER;

    *height = mZoom_box.height;
    
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomHeight(PRUint16 height) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomHeight()\n");
#endif

    if (height + mZoom_box.y > (PRUint16) fPlatform.height)
        mZoom_box.height = fPlatform.height - mZoom_box.y;
    else
        mZoom_box.height = height;

    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::GetZoomLineWidth(PRInt32 * aZoomLineWidth) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomLineWidth()\n");
#endif

    NS_ASSERTION(aZoomLineWidth != NULL, "null width pointer");   
    if (!aZoomLineWidth)
        return NS_ERROR_NULL_POINTER;

    *aZoomLineWidth = mLineWidth;

    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomLineWidth(PRInt32 aZoomLineWidth) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomLineWidth()\n");
#endif

    if (aZoomLineWidth <= 0)
        return NS_ERROR_INVALID_ARG;

    mLineWidth = aZoomLineWidth;

    return PaintImage();
}

#define MAX_LINE_ATTRIBUTE_LENGTH 20

NS_IMETHODIMP
nsSanePluginInstance::GetZoomLineStyle(char ** aZoomLineStyle) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomLineStyle()\n");
#endif

    NS_ASSERTION(*aZoomLineStyle != NULL, "null width pointer");   
    if (!*aZoomLineStyle)
        return NS_ERROR_NULL_POINTER;

    *aZoomLineStyle = (char*) nsMemory::Alloc(MAX_LINE_ATTRIBUTE_LENGTH);
    if (! *aZoomLineStyle)
        return NS_ERROR_OUT_OF_MEMORY;
    
    if (mLineStyle == GDK_LINE_ON_OFF_DASH)
        PL_strcpy(*aZoomLineStyle, "dash");
    else if (mLineStyle == GDK_LINE_SOLID)
        PL_strcpy(*aZoomLineStyle, "solid");
    else if (mLineStyle == GDK_LINE_DOUBLE_DASH)
        PL_strcpy(*aZoomLineStyle, "double_dash");
    else
        PL_strcpy(*aZoomLineStyle, "unknown");

    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomLineStyle(const char * aZoomLineStyle) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomLineStyle()\n");
#endif

    NS_ASSERTION(aZoomLineStyle != NULL, "null width pointer");   
    if (!aZoomLineStyle)
        return NS_ERROR_NULL_POINTER;

    // Line Style
    if (PL_strcasecmp(aZoomLineStyle, "dash") == 0) 
        mLineStyle = GDK_LINE_ON_OFF_DASH;
    else if (PL_strcasecmp(aZoomLineStyle, "solid") == 0)
        mLineStyle = GDK_LINE_SOLID;
    else if (PL_strcasecmp(aZoomLineStyle, "double_dash") == 0)
        mLineStyle = GDK_LINE_DOUBLE_DASH;

    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::GetZoomCapStyle(char ** aZoomCapStyle) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomCapStyle()\n");
#endif

    NS_ASSERTION(*aZoomCapStyle != NULL, "null width pointer");   
    if (!*aZoomCapStyle)
        return NS_ERROR_NULL_POINTER;

    *aZoomCapStyle = (char*) nsMemory::Alloc(MAX_LINE_ATTRIBUTE_LENGTH);
    if (! *aZoomCapStyle)
        return NS_ERROR_OUT_OF_MEMORY;
    
    if (mCapStyle == GDK_CAP_ROUND)
        PL_strcpy(*aZoomCapStyle, "round");
    else if (mCapStyle == GDK_CAP_PROJECTING)
        PL_strcpy(*aZoomCapStyle, "projecting");
    else if (mCapStyle == GDK_CAP_BUTT)
        PL_strcpy(*aZoomCapStyle, "butt");
    else if (mCapStyle == GDK_CAP_NOT_LAST)
        PL_strcpy(*aZoomCapStyle, "not_last");
    else
        PL_strcpy(*aZoomCapStyle, "unknown");


    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomCapStyle(const char * aZoomCapStyle) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomCapStyle()\n");
#endif

    NS_ASSERTION(aZoomCapStyle != NULL, "null width pointer");   
    if (!aZoomCapStyle)
        return NS_ERROR_NULL_POINTER;

    // Cap Style
    if (PL_strcasecmp(aZoomCapStyle, "round") == 0) 
        mCapStyle = GDK_CAP_ROUND;
    else if (PL_strcasecmp(aZoomCapStyle, "projecting") == 0)
        mCapStyle = GDK_CAP_PROJECTING;
    else if (PL_strcasecmp(aZoomCapStyle, "butt") == 0)
        mCapStyle = GDK_CAP_BUTT;
    else if (PL_strcasecmp(aZoomCapStyle, "not_last") == 0)
        mCapStyle = GDK_CAP_NOT_LAST;

    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::GetZoomJoinStyle(char ** aZoomJoinStyle) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomJoinStyle()\n");
#endif

    NS_ASSERTION(*aZoomJoinStyle != NULL, "null width pointer");   
    if (!*aZoomJoinStyle)
        return NS_ERROR_NULL_POINTER;

    *aZoomJoinStyle = (char*) nsMemory::Alloc(MAX_LINE_ATTRIBUTE_LENGTH);
    if (! *aZoomJoinStyle)
        return NS_ERROR_OUT_OF_MEMORY;
    
    if (mJoinStyle == GDK_JOIN_MITER)
        PL_strcpy(*aZoomJoinStyle, "miter");
    else if (mJoinStyle == GDK_JOIN_ROUND)
        PL_strcpy(*aZoomJoinStyle, "round");
    else if (mJoinStyle == GDK_JOIN_BEVEL)
        PL_strcpy(*aZoomJoinStyle, "bevel");
    else
        PL_strcpy(*aZoomJoinStyle, "unknown");

    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomJoinStyle(const char * aZoomJoinStyle) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomJoinStyle()\n");
#endif

    NS_ASSERTION(aZoomJoinStyle != NULL, "null width pointer");   
    if (!aZoomJoinStyle)
        return NS_ERROR_NULL_POINTER;

    // Join Style
    if (PL_strcasecmp(aZoomJoinStyle, "miter") == 0) 
        mJoinStyle = GDK_JOIN_MITER;
    else if (PL_strcasecmp(aZoomJoinStyle, "round") == 0)
        mJoinStyle = GDK_JOIN_ROUND;
    else if (PL_strcasecmp(aZoomJoinStyle, "bevel") == 0)
        mJoinStyle = GDK_JOIN_BEVEL;

    return PaintImage();
}

NS_IMETHODIMP
nsSanePluginInstance::GetZoomBR_XChange(float * aZoomBR_XChange) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomBR_XChange()\n");
#endif

    *aZoomBR_XChange = mBottomRightXChange;
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomBR_XChange(float aZoomBR_XChange) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomBR_XChange()\n");
#endif

    // Read-Only
    return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsSanePluginInstance::GetZoomBR_YChange(float * aZoomBR_YChange) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomBR_YChange()\n");
#endif

    *aZoomBR_YChange = mBottomRightYChange;
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomBR_YChange(float aZoomBR_YChange) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomBR_YChange()\n");
#endif

    // Read-Only
    return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsSanePluginInstance::GetZoomTL_XChange(float * aZoomTL_XChange) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomTL_XChange()\n");
#endif

    *aZoomTL_XChange = mTopLeftXChange;
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomTL_XChange(float aZoomTL_XChange) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomTL_XChange()\n");
#endif

    // Read-Only
    return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsSanePluginInstance::GetZoomTL_YChange(float * aZoomTL_YChange) 
{
#ifdef DEBUG
    printf("nsSaneInstance::GetZoomTL_YChange()\n");
#endif

    *aZoomTL_YChange = mTopLeftYChange;
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetZoomTL_YChange(float aZoomTL_YChange) 
{
#ifdef DEBUG
    printf("nsSaneInstance::SetZoomTL_YChange()\n");
#endif

    // Read-Only
    return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsSanePluginInstance::GetQuality(PRInt32 * aQuality)
{
#ifdef DEBUG
    printf("nsSaneInstance::GetQuality()\n");
#endif

    NS_ASSERTION(aQuality != NULL, "null compression quality pointer");   
    if (!aQuality)
        return NS_ERROR_NULL_POINTER;    
    
    *aQuality = mCompQuality;
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetQuality(PRInt32 aQuality)
{
#ifdef DEBUG
    printf("nsSaneInstance::SetQuality()\n");
#endif

    NS_ASSERTION(aQuality > -1 && aQuality < 101, 
                 "Invalid compression quality");
    if (aQuality < 0 || aQuality > 100)
        return NS_ERROR_INVALID_ARG;

    mCompQuality = aQuality;
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::GetMethod(char ** aMethod)
{
#ifdef DEBUG
    printf("nsSaneInstance::GetMethod()\n");
#endif

    NS_ASSERTION(aMethod != NULL, "Null method pointer");
    if (!aMethod)
        return NS_ERROR_NULL_POINTER;

    *aMethod = (char*) nsMemory::Alloc(8);
    if (!*aMethod)
        return NS_ERROR_OUT_OF_MEMORY;

    switch (mCompMethod) {
    case JDCT_ISLOW:
        PL_strcpy(*aMethod, "SLOW");
        break;
    case JDCT_IFAST:
        PL_strcpy(*aMethod, "FAST");
        break;
    case JDCT_FLOAT:
        PL_strcpy(*aMethod, "FLOAT");
        break;
    default:
        PL_strcpy(*aMethod, "DEFAULT");
        
    }

    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetMethod(const char * aMethod)
{
#ifdef DEBUG
    printf("nsSaneInstance::SetMethod()\n");
#endif

    NS_ASSERTION(aMethod != NULL, "Null method pointer");
    if (!aMethod)
        return NS_ERROR_NULL_POINTER;

    if (PL_strcasecmp(aMethod, "SLOW") == 0)
        mCompMethod = JDCT_ISLOW;
    else if (PL_strcasecmp(aMethod, "FAST") == 0)
        mCompMethod = JDCT_IFAST;
    else if (PL_strcasecmp(aMethod, "FLOAT") == 0)
        mCompMethod = JDCT_FLOAT;
    else
        mCompMethod = JDCT_DEFAULT;

    return NS_OK;
}

// END of Image Preview Interface
///////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////
// Generic SANE Interface

NS_IMETHODIMP
nsSanePluginInstance::GetDeviceOptions(char ** aDeviceOptions)
{
#ifdef DEBUG
    printf("nsSaneInstance::GetDeviceOptions()\n");
#endif

    const SANE_Option_Descriptor * optdesc;
    SANE_Int string_size;
    char *range_string, *word_string;
    nsresult rv;

    NS_ASSERTION(aDeviceOptions != NULL, "null options pointer");   
    if (!aDeviceOptions)
        return NS_ERROR_NULL_POINTER;

    *aDeviceOptions = nsnull;

    rv = OpenSaneDeviceIF();
    if (rv != NS_OK)
        return rv;

    // Find out how many options exist for device
    optdesc = sane_get_option_descriptor(mSaneHandle, 0);
    if (!optdesc || optdesc->size == 0) {
        NS_ERROR("Error trying to get number of options for device!");
        return NS_ERROR_FAILURE;
    }

    if (optdesc->size == 1) { /* size includes "size" option */
        NS_ERROR("Hmm.. That's odd! No options for this device?");
        return NS_OK; // I guess it's possible?
    }

    // allocate string to contain data to return to caller
    string_size = ((optdesc->size - 1) * MAX_OPT_SIZE) + 1;
    *aDeviceOptions = (char*) nsMemory::Alloc(string_size);
    NS_ENSURE_TRUE(*aDeviceOptions, NS_ERROR_OUT_OF_MEMORY);

    // initialize options string
    for (int n=0; n<string_size; n++)
        (*aDeviceOptions)[n] = '\0';

    // step through each of the options and copy over relavent data
    int i = 1;
    while((optdesc = sane_get_option_descriptor(mSaneHandle, i)) != NULL) {
        i++;

        // don't return grouping options
        if (optdesc->type == SANE_TYPE_GROUP) 
            continue;
        
        /* 
         * WARNING! Need to replace ','s and ';'s with
         *          something else so that JavaScript parsing
         *          doesn't get hosed!
         */

        // NAME
        if (optdesc->name) 
            optioncat(aDeviceOptions, optdesc->name, ", ");
        else 
            strcat(*aDeviceOptions, "N/A, ");

        ////////////////////////////////////
        // TITLE
        if (optdesc->title)
            optioncat(aDeviceOptions, optdesc->title, ", ");
        else 
            PL_strcat(*aDeviceOptions, "N/A, ");

        ////////////////////////////////////
        // DESCRIPTION
        if (optdesc->desc) 
            optioncat(aDeviceOptions, optdesc->desc, ", ");
        else
            PL_strcat(*aDeviceOptions, "N/A, ");

        ////////////////////////////////////
        // Option Type
        switch (optdesc->type) {

        case SANE_TYPE_BOOL:
            PL_strcat(*aDeviceOptions, "BOOL, ");
            break;

        case SANE_TYPE_INT:
            PL_strcat(*aDeviceOptions, "INT, ");
            break;

        case SANE_TYPE_FIXED:
            PL_strcat(*aDeviceOptions, "FIXED, ");
            break;

        case SANE_TYPE_STRING:
            PL_strcat(*aDeviceOptions, "STRING, ");
            break;

        case SANE_TYPE_BUTTON:
            PL_strcat(*aDeviceOptions, "BUTTON, ");
            break;

        case SANE_TYPE_GROUP:
            PL_strcat(*aDeviceOptions, "GROUP, ");
            break;

        default:
            PL_strcat(*aDeviceOptions, "UNKNOWN, ");
        }

        ////////////////////////////////////////
        // UNIT
        switch (optdesc->unit) {
        case SANE_UNIT_NONE:
            PL_strcat(*aDeviceOptions, "NONE, ");
            break;

        case SANE_UNIT_PIXEL:
            PL_strcat(*aDeviceOptions, "PIXEL, ");
            break;

        case SANE_UNIT_BIT:
            PL_strcat(*aDeviceOptions, "BIT, ");
            break;

        case SANE_UNIT_MM:
            PL_strcat(*aDeviceOptions, "MM, ");
            break;

        case SANE_UNIT_DPI:
            PL_strcat(*aDeviceOptions, "PERCENT, ");
            break;

        case SANE_UNIT_MICROSECOND:
            PL_strcat(*aDeviceOptions, "MICROSECOND, ");
            break;

        default:
            PL_strcat(*aDeviceOptions, "UNKNOWN, ");
        }

        ////////////////////////////////////////
        // SIZE
        char * size_option = (char *)PR_MALLOC(64);
        if (!size_option)
            return NS_ERROR_OUT_OF_MEMORY;
        sprintf(size_option, "%i, ", optdesc->size);
        PL_strcat(*aDeviceOptions, size_option);
        PR_FREEIF(size_option);

        ////////////////////////////////////////
        // ACTIVE?
        if (SANE_OPTION_IS_ACTIVE(optdesc->cap))
            PL_strcat(*aDeviceOptions, "ACTIVE, ");
        else 
            PL_strcat(*aDeviceOptions, "UNACTIVE, ");

        ////////////////////////////////////////
        // SW SETTABLE?
        if (SANE_OPTION_IS_SETTABLE(optdesc->cap))
            PL_strcat(*aDeviceOptions, "SETTABLE, ");
        else 
            PL_strcat(*aDeviceOptions, "UNSETTABLE, ");

        ////////////////////////////////////////
        // Contraints Union
        switch (optdesc->constraint_type) {
        case SANE_CONSTRAINT_RANGE:
            range_string = (char *)PR_MALLOC(128);
            if (!range_string)
                return NS_ERROR_OUT_OF_MEMORY;
            sprintf(range_string, "RANGE, %i, %i, %i; ",
                    optdesc->constraint.range->min,
                    optdesc->constraint.range->max,
                    optdesc->constraint.range->quant);
            PL_strcat(*aDeviceOptions, range_string);
            PR_FREEIF(range_string);
            break;

        case SANE_CONSTRAINT_WORD_LIST:
            word_string = (char *) PR_MALLOC(64);
            if (!word_string)
                return NS_ERROR_OUT_OF_MEMORY;
            
            PL_strcat(*aDeviceOptions, "WORD_LIST");
            for (int word_index=1; 
                 word_index <= optdesc->constraint.word_list[0]; 
                 word_index++) {
                sprintf(word_string, ", %i", optdesc->constraint.word_list[i]);
                PL_strcat(*aDeviceOptions, word_string);
            }
            PL_strcat(*aDeviceOptions, ";");
            PR_FREEIF(word_string);
            break;

        case SANE_CONSTRAINT_STRING_LIST:
            PL_strcat(*aDeviceOptions, "STRING_LIST");
            for (int string_index=0; 
                 optdesc->constraint.string_list[string_index];
                 string_index++) {
                PL_strcat(*aDeviceOptions, ", ");
                PL_strcat(*aDeviceOptions, 
                          optdesc->constraint.string_list[string_index]);
            }
            PL_strcat(*aDeviceOptions, ";");
            break;

        default:
            // no constraints
            PL_strcat(*aDeviceOptions, "NONE;");
        }        
    }
    
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetDeviceOptions(const char * aDeviceOptions)
{
#ifdef DEBUG
    printf("nsSaneInstance::SetDeviceOptions()\n");
#endif

    NS_ERROR("DeviceOptions is read-only!\n");
    return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsSanePluginInstance::GetActiveDevice(char ** aActiveDevice)
{
#ifdef DEBUG
    printf("nsSaneInstance::GetActiveDevice()\n");
#endif

    NS_ASSERTION(aActiveDevice != NULL, "null device pointer");   
    if (!aActiveDevice)
        return NS_ERROR_NULL_POINTER;

    *aActiveDevice = nsnull;
    if (!mSaneDevice)
        return NS_OK;
    
    *aActiveDevice = (char*) nsMemory::Alloc(PL_strlen(mSaneDevice) + 1);
    NS_ENSURE_TRUE(*aActiveDevice, NS_ERROR_OUT_OF_MEMORY);
    PL_strcpy(*aActiveDevice, mSaneDevice);
    
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetActiveDevice(const char * aActiveDevice)
{
#ifdef DEBUG
    printf("nsSaneInstance::SetActiveDevice()\n");
#endif

    NS_ASSERTION(aActiveDevice, "null active device pointer");
    if (!aActiveDevice)
        return NS_ERROR_NULL_POINTER;

    PR_FREEIF (mSaneDevice);
    mSaneDevice = PL_strdup(aActiveDevice);

    if (mSaneOpen) {
        sane_close(mSaneHandle);
        sane_exit();
        sane_init(0,0);
        if (sane_open(mSaneDevice, &mSaneHandle) != SANE_STATUS_GOOD) {
            mSaneOpen = SANE_FALSE;
            NS_ERROR("Error trying to reopen sane device!\n");
            return NS_ERROR_FAILURE;
        }
            
    } else {
        if (sane_open(mSaneDevice, &mSaneHandle) != SANE_STATUS_GOOD) {
            NS_ERROR("Error trying to open closed sane device!\n");
            return NS_ERROR_FAILURE;
        }
        mSaneOpen = SANE_TRUE;
    }

    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::OpenSaneDeviceIF( void )
{
#ifdef DEBUG
    printf("nsSaneInstance::OpenSaneDeviceIF()\n");
#endif

    if (mSaneOpen) {
        return NS_OK;
    }

    if (!mSaneDevice) {
        NS_ERROR("Device name not set!");
        return NS_ERROR_FAILURE;
    }

    SANE_Status status = sane_open(mSaneDevice, &mSaneHandle);
    if (status != SANE_STATUS_GOOD) {
        NS_ERROR ("sane_open returned error code");
        return NS_ERROR_FAILURE;
    }

    mSaneOpen = SANE_TRUE;
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::GetImageParameters(char ** aImageParameters)
{
#ifdef DEBUG
    printf("nsSaneInstance::GetImageParameters()\n");
#endif

    SANE_Status status;
    SANE_Parameters params;
    nsresult rv;
    
    NS_ASSERTION(aImageParameters != NULL, "null parameters pointer");   
    if (!aImageParameters)
        return NS_ERROR_NULL_POINTER;

    *aImageParameters = nsnull;

    rv = OpenSaneDeviceIF();;
    if (rv != NS_OK)
        return rv;
    
    status = sane_get_parameters(mSaneHandle, &params);
    if (status != SANE_STATUS_GOOD) {
        sane_close(mSaneHandle);
        NS_ERROR("Failed to get parameters for sane device!");
        return NS_ERROR_FAILURE;
    }

#define MAX_PARAM_LEN 256
    *aImageParameters = (char *) nsMemory::Alloc(MAX_PARAM_LEN);
    if (!*aImageParameters)
        return NS_ERROR_OUT_OF_MEMORY;

    char * format_string;
    switch (params.format) {

    case SANE_FRAME_GRAY:
        format_string = PL_strdup("GRAY");
        break;

    case SANE_FRAME_RGB:
        format_string = PL_strdup("RGB");
        break;

    case SANE_FRAME_RED:
        format_string = PL_strdup("RED");
        break;

    case SANE_FRAME_GREEN:
        format_string = PL_strdup("GREEN");
        break;

    case SANE_FRAME_BLUE:
        format_string = PL_strdup("BLUE");
        break;

    default:
        format_string = PL_strdup("UNKNOWN");
    }

    sprintf(*aImageParameters, "%s, %s, %i, %i, %i, %i",
            format_string,
            params.last_frame ? "TRUE" : "FALSE",
            params.lines,
            params.depth,
            params.pixels_per_line,
            params.bytes_per_line);


    // cleanup
    PR_FREEIF(format_string);

    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetImageParameters(const char * aImageParameters)
{
#ifdef DEBUG
    printf("nsSaneInstance::SetImageParameters()\n");
#endif

    NS_ERROR("ImageParameters is read-only!\n");
    return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsSanePluginInstance::GetAvailableDevices(char ** aAvailableDevices)
{
#ifdef DEBUG
    printf("nsSaneInstance::GetAvailableDevices()\n");
#endif

    SANE_Status status;
    const SANE_Device ** device_list;
    int num, i;

    NS_ASSERTION(aAvailableDevices != NULL, "null parameters pointer");   
    if (!aAvailableDevices)
        return NS_ERROR_NULL_POINTER;

    *aAvailableDevices = nsnull;

    status = sane_get_devices(&device_list, SANE_TRUE);
    if (status != SANE_STATUS_GOOD)
        return NS_ERROR_FAILURE;

    // find out how many devices are available
    for ( num=0; device_list[num]; num++);

    // allocate return string
#define MAX_DEVICE_SIZE 256
    *aAvailableDevices = (char*) nsMemory::Alloc(num * MAX_DEVICE_SIZE);
    if (!*aAvailableDevices)
        return NS_ERROR_OUT_OF_MEMORY;

    for ( i=0; i < num * MAX_DEVICE_SIZE; i++)
        (*aAvailableDevices)[i] = '\0';

    for ( i=0; i<num; i++) {
        optioncat(aAvailableDevices, device_list[i]->name,   ", ");
        optioncat(aAvailableDevices, device_list[i]->vendor, ", ");
        optioncat(aAvailableDevices, device_list[i]->model,  ", ");
        optioncat(aAvailableDevices, device_list[i]->type,   "; ");
    }

    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetAvailableDevices(const char * aAvailableDevices)
{
#ifdef DEBUG
    printf("nsSaneInstance::SetAvailableDevices()\n");
#endif

    NS_ERROR("AvailableDevices is read-only!\n");
    return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsSanePluginInstance::ScanImage(void)
{
#ifdef DEBUG
    printf("nsSaneInstance::ScanImage()\n");
#endif

    mState = 1;
    mSuccess = 0;

    mScanThread = PR_CreateThread( PR_USER_THREAD, scanimage_thread_routine, 
                                  (void*)this, PR_PRIORITY_HIGH, 
                                  PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);

    if (mScanThread == NULL) {
        NS_ERROR("Could not create thread!");
        return NS_ERROR_FAILURE;
    }

    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetOption(const char * name, const char * value)
{
#ifdef DEBUG
    printf("nsSaneInstance::SetOption()\n");
#endif

    nsresult rv;
    SANE_Int optnum;
    const SANE_Option_Descriptor * optdesc;
    void * option_value;
    SANE_Status status;

    NS_ASSERTION(name != NULL && value != NULL, "null pointer");
    if (!name || !value)
        return NS_ERROR_NULL_POINTER;

    rv = OpenSaneDeviceIF();
    if (rv != NS_OK) 
        return rv;

    // find the option number
    for ( optnum=1; 
          ((optdesc = sane_get_option_descriptor(mSaneHandle, optnum)) != NULL)
              && (PL_strcasecmp(name, optdesc->name) != 0);
          optnum++ );
    if (!optdesc) {
        NS_ERROR("Option not found for sane device!");
        return NS_ERROR_INVALID_ARG;
    }

    // determine the value to pass to device
    if (optdesc->type == SANE_TYPE_BOOL) {
        SANE_Bool sbool;
        if (PL_strcasecmp(value, "TRUE") == 0) {
            sbool = SANE_TRUE;
        } else 
            sbool = SANE_FALSE;

        option_value = (void *) &sbool;

    } else if ( optdesc->type == SANE_TYPE_INT ||
                optdesc->type == SANE_TYPE_FIXED ) {
        SANE_Int sint;
        sscanf(value, "%i", &sint);

        option_value = (void *) &sint;

    } else if (optdesc->type == SANE_TYPE_STRING) {
        option_value = (void *) value;
    } else {
        option_value = NULL;
    }

    // set the option
    status = sane_control_option( mSaneHandle, optnum, 
                                  SANE_ACTION_SET_VALUE, option_value,
                                  NULL);
    if (status != SANE_STATUS_GOOD) {
        NS_ERROR("Error trying to set option!");
        return NS_ERROR_FAILURE;
    }

    return NS_OK;
}

int
nsSanePluginInstance::WritePNMHeader (int fd, SANE_Frame format, 
                                  int width, int height, int depth)
{
#ifdef DEBUG
    printf("nsSaneInstance::WritePNMHeader()\n");
#endif

    char b[32];

    switch (format) {
    case SANE_FRAME_RED:
    case SANE_FRAME_GREEN:
    case SANE_FRAME_BLUE:
    case SANE_FRAME_RGB:
        sprintf (b, "P6\n%d %d\n255\n", width, height);
        break;

    default:
        if (depth == 1)
            sprintf (b, "P4\n%d %d\n", width, height);
        else
            sprintf (b, "P5\n%d %d\n255\n", width, height);
        break;
    }

    return write(fd, b, PL_strlen(b));
}

// END of Generic SANE Interface
//////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// Scanner Status Interface

NS_IMETHODIMP
nsSanePluginInstance::GetSuccess(PRBool * aSuccess)
{
#ifdef DEBUG
    printf("nsSaneInstance::GetSuccess()\n");
#endif

    NS_PRECONDITION(aSuccess != nsnull, "null ptr");
    if (! aSuccess) {
        NS_ERROR("Null pointer passed to GetSuccess!\n");
        return NS_ERROR_NULL_POINTER;
    }

    *aSuccess = mSuccess;
    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetSuccess(PRBool aSuccess)
{
#ifdef DEBUG
    printf("nsSaneInstance::SetSuccess()\n");
#endif

    // This is a read-only flag.
    NS_ERROR("Success is a read-only flag!\n");
    return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSanePluginInstance::GetState(char ** aState)
{
#ifdef DEBUG
    printf("nsSaneInstance::GetState()\n");
#endif

    NS_PRECONDITION(aState != nsnull, "null ptr");
    if (! aState) {
        NS_ERROR("Null pointer passed to GetSuccess!\n");
        return NS_ERROR_NULL_POINTER;
    }

    *aState = (char*) nsMemory::Alloc(5);
    if (!*aState) {
        NS_ERROR("Unable to allocate State string!\n");
        return NS_ERROR_OUT_OF_MEMORY;
    }

    if (mState == 0)
        PL_strcpy(*aState, "IDLE");
    else
        PL_strcpy(*aState, "BUSY");

    return NS_OK;
}

NS_IMETHODIMP
nsSanePluginInstance::SetState(const char * aState)
{
#ifdef DEBUG
    printf("nsSaneInstance::SetState()\n");
#endif

    // This is a read-only flag.
    NS_ERROR("Success is a read-only flag!\n");
    return NS_ERROR_FAILURE;
}

// END of Scanner Status Interface
//////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// snPluginStreamListener Methods

nsSanePluginStreamListener::nsSanePluginStreamListener(nsSanePluginInstance* inst)
{
#ifdef DEBUG
    printf("nsSanePluginStreamListener::nsSanePluginStreamListener()\n");
#endif

    PR_AtomicIncrement(&gPluginObjectCount);

    mPlugInst = inst;
}

nsSanePluginStreamListener::~nsSanePluginStreamListener(void)
{
#ifdef DEBUG
    printf("nsSanePluginStreamListener::~nsSanePluginStreamListener()\n");
#endif

    PR_AtomicDecrement(&gPluginObjectCount);
}


// This macro produces a simple version of QueryInterface, AddRef and Release.
// See the nsISupports.h header file for details.
NS_IMPL_ISUPPORTS1(nsSanePluginStreamListener, nsIPluginStreamListener)

NS_METHOD
nsSanePluginStreamListener::OnStartBinding( nsIPluginStreamInfo* pluginInfo )
{
#ifdef DEBUG
    printf("nsSanePluginStreamListener::OnStartBinding()\n");
#endif

    return NS_OK;
}

NS_METHOD
nsSanePluginStreamListener::OnDataAvailable( nsIPluginStreamInfo* pluginInfo, 
                                             nsIInputStream* input, 
                                             PRUint32 length )
{
#ifdef DEBUG
    printf("nsSanePluginStreamListener::OnDataAvailable()\n");
#endif

    // This plugin doesn't support 
    // streaming input data.
    return NS_OK;
}


NS_METHOD
nsSanePluginStreamListener::OnFileAvailable( nsIPluginStreamInfo* pluginInfo, 
                                             const char* fileName )
{
#ifdef DEBUG
    printf("nsSanePluginStreamListener::OnFileAvailable()\n");
#endif

    return NS_OK;
}

NS_METHOD
nsSanePluginStreamListener::OnStopBinding( nsIPluginStreamInfo* pluginInfo,
                                           nsresult status )
{
#ifdef DEBUG
    printf("nsSanePluginStreamListener::OnStopBinding()\n");
#endif

    return NS_OK;
}

NS_METHOD
nsSanePluginStreamListener::OnNotify( const char* url, nsresult status )
{
#ifdef DEBUG
    printf("nsSanePluginStreamListener::OnNotify()\n");
#endif

    return NS_OK;
}

NS_METHOD
nsSanePluginStreamListener::GetStreamType(nsPluginStreamType *result)
{
#ifdef DEBUG
    printf("nsSanePluginStreamListener::GetStreamType()\n");
#endif

    *result = nsPluginStreamType_Normal;
    return NS_OK;
}

// End snPluginStreamListener methods
//////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
// Helper Global Fuctions

gboolean draw (GtkWidget *widget, GdkEventExpose *event, gpointer data) {

    nsSanePluginInstance * pthis = (nsSanePluginInstance *)data;

    pthis->PaintImage();
    return TRUE;
}

nsresult optioncat ( char ** dest, const char * src, const char * ending )
{
    char * p;
    int i;

    NS_ASSERTION(dest != NULL, "null option string pointer");
    if (!dest)
        return NS_ERROR_NULL_POINTER;

    NS_ASSERTION(src != NULL, "null option string pointer");
    if (!src)
        return NS_ERROR_NULL_POINTER;

    NS_ASSERTION(ending != NULL, "null option string pointer");
    if (!ending)
        return NS_ERROR_NULL_POINTER;

    // ensure that src string doesn't contain any ','s or ';'s
    p = PL_strdup(src);
    for ( i=0; p[i]; i++ ) {
        if (p[i] == ',')
            p[i] = '~';  // Replace ','s with '~'s
        else if (p[i] == ';')
            p[i] = '|';  // Replace ';'s with '|'s

    }


    PL_strcat(*dest, p);
    PL_strcat(*dest, ending);
    PR_FREEIF(p);

    return NS_OK;
}

void
my_jpeg_error_exit (j_common_ptr cinfo)
{
    emptr errmgr;

    errmgr = (emptr) cinfo->err;
    cinfo->err->output_message(cinfo);
    siglongjmp(errmgr->setjmp_buffer, 1);
    return;
}

unsigned char      *
jpeg_file_to_rgb (FILE * f, int *w, int *h)
{
  struct jpeg_decompress_struct cinfo;
  struct my_JPEG_error_mgr jerr;
  unsigned char      *data, *line[16], *ptr;
  int                 x, y, i;

  cinfo.err = jpeg_std_error(&(jerr.pub));
  jerr.pub.error_exit = my_jpeg_error_exit;

  /* error handler to longjmp to, we want to preserve signals */
  if (sigsetjmp(jerr.setjmp_buffer, 1)) {
    
    /* Whoops there was a jpeg error */
    jpeg_destroy_decompress(&cinfo);
    return NULL;
  }

  jpeg_create_decompress(&cinfo);
  jpeg_stdio_src(&cinfo, f);
  jpeg_read_header(&cinfo, TRUE);
  cinfo.do_fancy_upsampling = FALSE;
  cinfo.do_block_smoothing = FALSE;

  cinfo.scale_num = 2;
  cinfo.scale_denom = 1;
  jpeg_start_decompress(&cinfo);
  *w = cinfo.output_width;
  *h = cinfo.output_height;
  data = (unsigned char *)PR_Malloc(*w ** h * 3);
  if (!data) {
    NS_ERROR("jpeg_file_to_rgb: Error trying to allocate buffer!\n");
    jpeg_destroy_decompress(&cinfo);
    return NULL;
  }
  ptr = data;

  if (cinfo.rec_outbuf_height > 16) {
      NS_ERROR("ERROR: JPEG uses line buffers > 16. Cannot load.\n");
      return NULL;
  }

  if (cinfo.output_components == 3) {
    for (y = 0; y < *h; y += cinfo.rec_outbuf_height) {
      for (i = 0; i < cinfo.rec_outbuf_height; i++) {
	line[i] = ptr;
	ptr += *w * 3;
      }

      jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height);
    }
  } else if (cinfo.output_components == 1) {
    for (i = 0; i < cinfo.rec_outbuf_height; i++) {
      if ((line[i] = (unsigned char *)PR_Malloc(*w)) == NULL) {
	int                 t = 0;
	
	NS_ERROR("jpeg_file_to_rgb: Error tying to allocate line!\n");
	
	for (t = 0; t < i; t++)
	  PR_FREEIF(line[t]);
	jpeg_destroy_decompress(&cinfo);
	return NULL;
      }
    }

    for (y = 0; y < *h; y += cinfo.rec_outbuf_height) {
      jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height);
      for (i = 0; i < cinfo.rec_outbuf_height; i++) {
	for (x = 0; x < *w; x++) {
	  *ptr++ = line[i][x];
	  *ptr++ = line[i][x];
	  *ptr++ = line[i][x];
	}
      }
    }

    for (i = 0; i < cinfo.rec_outbuf_height; i++)
      PR_FREEIF(line[i]);
  }

  jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);

  return data;
}

// Caller must free memory
unsigned char *
scale_image(unsigned char *rgb_data, int rgb_width, int rgb_height,
	    int w, int h)
{
  int                 x, y, *xarray;
  unsigned char     **yarray, *ptr, *ptr2, *ptr22, *new_data;
  int                 l, r, m, pos, inc, w3;

  new_data = (unsigned char *) PR_Malloc( w * h * 3);
  if (!new_data) {
    NS_ERROR("ERROR: Cannot allocate image buffer\n");
    return NULL;
  }

  xarray = (int *)PR_Malloc(sizeof(int) * w);
  if (!xarray) {
    NS_ERROR("ERROR: Cannot allocate X co-ord buffer\n");
    PR_FREEIF(new_data);
    return NULL;
  }

  yarray = (unsigned char **)PR_Malloc(sizeof(unsigned char *) * h);
  if (!yarray) {
    NS_ERROR("ERROR: Cannot allocate Y co-ord buffer\n");
    PR_FREEIF(new_data);
    PR_FREEIF(xarray);
    return NULL;
  }
  
  ptr22 = rgb_data;
  w3 = rgb_width * 3;
  inc = 0;
  l = 0;
  r = 0;
  m = w - l - r;

  inc = (rgb_width << 16) / m;
  pos = 0;

  for (x = l; x < l + m; x++) {
    xarray[x] = (pos >> 16) + (pos >> 16) + (pos >> 16);
    pos += inc;
  }

  pos = (rgb_width - r) << 16;
  for (x = w - r; x < w; x++) {
    xarray[x] = (pos >> 16) + (pos >> 16) + (pos >> 16);
    pos += 0x10000;
  }

  l = 0;
  r = 0;
  m = h - l - r;

  if (m > 0)
    inc = (rgb_height << 16) / m;

  pos = 0;

  for (x = l; x < l + m; x++) {
    yarray[x] = ptr22 + ((pos >> 16) * w3);
    pos += inc;
  }

  pos = (rgb_height - r) << 16;
  for (x = h - r; x < h; x++) {
    yarray[x] = ptr22 + ((pos >> 16) * w3);
    pos += 0x10000;
  }

  ptr = new_data;
  for (y = 0; y < h; y++) {
    for (x = 0; x < w; x++) {
      ptr2 = yarray[y] + xarray[x];
      *ptr++ = (int)*ptr2++;
      *ptr++ = (int)*ptr2++;
      *ptr++ = (int)*ptr2;
    }
  }

  PR_FREEIF(xarray);
  PR_FREEIF(yarray);

  return new_data;
}


// Caller must free memory
unsigned char *
crop_image(unsigned char *rgb_data, int *rgb_width, int *rgb_height,
	   gint x, gint y, gint w, gint h)
{
  unsigned char      *data;
  int                 xx, yy, w3, w4;
  unsigned char      *ptr1, *ptr2;

  if (!rgb_data)
    return NULL;

  if (x < 0)
    {
      w += x;
      x = 0;
    }
  if (y < 0)
    {
      h += y;
      y = 0;
    }
  if (x >= *rgb_width)
    return NULL;
  if (y >= *rgb_height)
    return NULL;
  if (w <= 0)
    return NULL;
  if (h <= 0)
    return NULL;
  if (x + w > *rgb_width)
    w = *rgb_width - x;
  if (y + h > *rgb_height)
    h = *rgb_height - y;
  if (w <= 0)
    return NULL;
  if (h <= 0)
    return NULL;

  w3 = *rgb_width * 3;
  w4 = (*rgb_width - w) * 3;
  data = (unsigned char *)PR_Malloc(w * h * 3);
  if (data == NULL)
    return NULL;
  ptr1 = rgb_data + (y * w3) + (x * 3);
  ptr2 = data;
  for (yy = 0; yy < h; yy++)
    {
      for (xx = 0; xx < w; xx++)
	{
	  *ptr2++ = *ptr1++;
	  *ptr2++ = *ptr1++;
	  *ptr2++ = *ptr1++;
	}
      ptr1 += w4;
    }
  
  *rgb_width = w;
  *rgb_height = h;

  return data;
}

void PR_CALLBACK scanimage_thread_routine( void * arg )
{
    nsSanePluginInstance * pthis = (nsSanePluginInstance *)arg;

    nsresult rv;
    SANE_Status status;
    SANE_Parameters param;
    int len;
    FILE * out;
    SANE_Byte * buf;
    JSAMPROW row_pointer[1];
    char *fname;

    // JPEG stuff
    struct jpeg_compress_struct cinfo;
	struct jpeg_error_mgr jerr;

	// Error handling
    jerr.error_exit = my_jpeg_error_exit; // fatal errors
	cinfo.err = jpeg_std_error(&jerr);

	jpeg_create_compress(&cinfo);

    rv = pthis->OpenSaneDeviceIF();
    if (rv != NS_OK)
        goto error_exit;

    // open image file
    fname = pthis->GetImageFilename();
    if (!fname)
        goto error_exit;
    out = fopen(fname, "wb");
    PR_FREEIF(fname);
    if (out == NULL) {
        NS_ERROR("Unable to open mImageFilename!\n");
        sane_cancel(pthis->mSaneHandle);
        goto error_exit;       
    }
    jpeg_stdio_dest(&cinfo, out);

    status = sane_start(pthis->mSaneHandle);
    if (status != SANE_STATUS_GOOD) {
        NS_ERROR("Error trying to start sane device!");
        goto error_exit;
    }

    status = sane_get_parameters(pthis->mSaneHandle, &param);
    if (status != SANE_STATUS_GOOD) {
        NS_ERROR("Error trying to get image parameters!\n");
        sane_cancel(pthis->mSaneHandle);
        goto error_exit;
    }
    cinfo.image_width = param.pixels_per_line;
    cinfo.image_height = param.lines;

    switch (param.format) {
    case SANE_FRAME_RED:
    case SANE_FRAME_GREEN:
    case SANE_FRAME_BLUE:
        // NOT Supported!
        NS_ERROR("Multiframe devices not supported!\n");
        sane_cancel(pthis->mSaneHandle);
        goto error_exit;
        break;
                
    case SANE_FRAME_RGB:
        cinfo.input_components = 3;
        cinfo.in_color_space = JCS_RGB;
        break;

    case SANE_FRAME_GRAY:
        cinfo.input_components = 1;
        cinfo.in_color_space = JCS_GRAYSCALE;
        break;
    }
    jpeg_set_defaults(&cinfo); 
    cinfo.dct_method = pthis->mCompMethod; 
    jpeg_set_quality(&cinfo, pthis->mCompQuality, FALSE);
    jpeg_start_compress(&cinfo, TRUE);

    buf = (SANE_Byte *)PR_MALLOC(param.bytes_per_line);
    if (!buf) {
        NS_ERROR("Error trying to allocate buffer!\n");
        jpeg_destroy_compress(&cinfo);
        fclose(out);
        sane_cancel(pthis->mSaneHandle);
        goto error_exit;
    }

    int total;
    while(1) {
        total = 0;
        while (total < param.bytes_per_line) {
            // Read in data from sane device
            status = sane_read(pthis->mSaneHandle, buf + total, 
                               param.bytes_per_line - total, 
                               &len);
            if (status != SANE_STATUS_GOOD) {
                if (status == SANE_STATUS_EOF)
                    // done with this frame
                    goto finished_scan;
                else {
                    NS_ERROR("Error trying to read from sane device!\n");
                    PR_FREEIF(buf);
                    jpeg_destroy_compress(&cinfo);
                    fclose(out);
                    sane_cancel(pthis->mSaneHandle);
                    goto error_exit;
                }
            }
            total += len;
        }

        row_pointer[0] = (JSAMPLE *) buf;
        jpeg_write_scanlines(&cinfo, row_pointer, 1); 

    } // end while

    // cleanup
 finished_scan:
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    PR_FREEIF(buf);
    sane_cancel(pthis->mSaneHandle);
    fclose(out);
    pthis->SetSuccess(PR_TRUE); // allow caller to check for success

 error_exit:

    ////////////////////////////////////////////////
    // Call onScanComplete callback in the UI thread

    nsCOMPtr<nsIEventQueue> eventQ;

    // Get the event queue of the current thread...
    nsCOMPtr<nsIEventQueueService> eventQService = 
             do_GetService(kEventQueueService, &rv);
    if (NS_FAILED(rv)) {
        NS_ERROR("Unable to get event queue service!\n");
        return;
    }

    rv = eventQService->GetThreadEventQueue(pthis->mUIThread,
                                            getter_AddRefs(eventQ));
    if (NS_FAILED(rv)) {
        NS_ERROR("Unable to get thread queue!\n");
        return;
    }

    PLEvent *event = new PLEvent;
    if (!event) {
        NS_ERROR("Unable to create new event!\n");
        return;
    }

    // ThreadHandleEvent will now execute in the UI thread.
    PL_InitEvent(event, 
                 pthis,
                 (PLHandleEventProc)  ThreadedHandleEvent,
                 (PLDestroyEventProc) ThreadedDestroyEvent);

    if (NS_FAILED(eventQ->PostEvent(event))) {
        NS_ERROR("Error trying to post event!\n");
        PL_DestroyEvent(event);
        return;
    }
}

void PR_CALLBACK ThreadedHandleEvent(PLEvent * event)
{
    nsSanePluginInstance *pthis = (nsSanePluginInstance *)event->owner;    
   
    // If this isn't the UI's thread then we are hosed!
    if (PR_FALSE == pthis->IsUIThread()) {
        NS_ERROR("Attempt to execute event from the wrong thread!\n");
        return;
    }

    pthis->SetState(0); // no longer scanning
    pthis->Restore();  // repaint image

    // Notify user that routine is finished
    pthis->DoScanCompleteCallback();
}

void PR_CALLBACK ThreadedDestroyEvent(PLEvent* aEvent)
{
    delete aEvent;
}

void store_filename(GtkFileSelection *selector, gpointer user_data) 
{
    nsSanePluginInstance *pthis = (nsSanePluginInstance *) user_data;    
    gchar *cmd;
    GtkWidget * fs;
    char * orig_filename;
    char * dest_filename;

    if (!user_data || !selector)
        return;

    fs = pthis->GetFileSelection();
    orig_filename = pthis->GetImageFilename();
    if ( !fs || !orig_filename )
        return;

    dest_filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION(fs));
    if ( !dest_filename )
        return;

#ifdef DEBUG
    printf("Saving %s to %s\n", orig_filename, dest_filename);
#endif

    /*
     * I'm just doing a simple 'cp' command with the shell.
     * This probobly should be done in a NSPR safe kind
     * of way.
     */

    // cp /tmp/nsSanePlugin.###.jpg /path/to/save/to.jpg
    cmd = (gchar *)PR_Malloc(PL_strlen(orig_filename) + 
                             PL_strlen(dest_filename) + 6);
    if (!cmd)
        return;
        
    sprintf(cmd, "cp %s %s", orig_filename, dest_filename);
    system(cmd);

    PR_FREEIF(cmd);
}

// END of Helper Global Functions
//////////////////////////////////////////////////////////////////////////////



















