00001 """ Copyright (c) Abel Deuring 2006 <adeuring@gmx.net> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. provides PyGTK based widgets which control sane backends """ import sys, os import threading import sane import gtk, gobject import Config, I18n, Curve t = I18n.get_translation('sane-backends') if t: T2_ = t.gettext else: T2_ = lambda x: x t2 = I18n.get_translation('eikazo') if t2: _ = t2.gettext else: _ = lambda x: x # strings needing translations in the eikazo domain, but which # should _not_ be replaced dynamically def N_(x): return x sane.init() # these constants should be defined in _sane.c ... SANE_TYPE_BOOL = 0 SANE_TYPE_INT = 1 SANE_TYPE_FIXED = 2 SANE_TYPE_STRING = 3 SANE_TYPE_BUTTON = 4 SANE_TYPE_GROUP = 5 SANE_UNIT_NONE = 0 SANE_UNIT_PIXEL = 1 SANE_UNIT_BIT = 2 SANE_UNIT_MM = 3 SANE_UNIT_DPI = 4 SANE_UNIT_PERCENT = 5 SANE_UNIT_MICROSECOND = 6 class EikazoWidgetError(Exception): def __init__(self, value): self.value = value def __str__(self): return self.value class SaneWidgetConfig(Config.ConfigAware): def __init__(self, config): Config.ConfigAware.__init__(self, config) self.section = 'device' self.delayedConfigValue = None self.readConfig() def readConfig(self): opts = self.device.getOptions() opt = [x for x in opts if x[9] == self.optname][0] opttype = opt[4] optcap = opt[7] opt = self.device._device.opt[self.optname] # FIXME: check for arrays! # FIXME: can a button be settable?? if opttype in (SANE_TYPE_BOOL, SANE_TYPE_BUTTON): val = self.config.getboolean(self.section, self.optname) elif opttype == SANE_TYPE_INT: # float and int options max change from backend to backend # so let's read floats in any case val = self.config.getfloat(self.section, self.optname) if val != None: val = int(val) elif opttype == SANE_TYPE_FIXED: val = self.config.getfloat(self.section, self.optname) elif opttype == SANE_TYPE_STRING: val = self.config.get(self.section, self.optname) if val != None: if opt.is_settable(): if opt.is_active(): # we may get an error, if we load a config that # has been saved for device 1, and open the config # for device 2. In this case, the value of an option # may not be valid for device 2. # Example: The source option may have values # like 'ADF' or 'ADF Front', but the selected scanner # does not have an ADF installed, or a value # with the same or a similar meaning is different: # 'ADF' vs. 'Automatic Document Feeder'. # Silently ignore these errors try: self.device._device.__setattr__(self.optname, val) except sane._sane.error, errval: if str(errval) != 'Invalid argument': raise else: # FIXME: must be updated in Reload!!! self.delayedConfigValue = val self.Reload() def writeConfig(self): opts = self.device.getOptions() opt = [x for x in opts if x[9] == self.optname][0] opttype = opt[4] optcap = opt[7] opt = self.device._device.opt[self.optname] # FIXME check for arrays! if optcap & opt.is_settable(): if opt.is_active(): val = self.device._device.__getattr__(self.optname) elif self.delayedConfigValue != None: val = self.delayedConfigValue else: # inactive option: we'll use the value stored # in in the GUI widget val = self.guiValue() self.config.set(self.section, self.optname, str(val)) class DeviceSelectionDialog(gtk.Dialog): def __init__(self, title=N_("Select a Scanner"), parent=None, flags=0): self.devs = sane.get_devices() #print self.devs #print [x[1:4] + x[:1] for x in self.devs] gtk.Dialog.__init__(self, title, parent, flags, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT)) self.sel = gtk.combo_box_new_text() #print self.sel.get_property("model") for dev in self.devs: self.sel.append_text('%s %s (%s)' % (dev[1:3] + dev[:1])) self.sel.set_active(0) self.vbox.pack_start(self.sel) self.sel.show() def getSelectedDeviceName(self): return self.devs[self.GetSelection()][0] def run(self): """ returns None, if "cancel" has been clicked, or the Sane device name of the selected device """ res = gtk.Dialog.run(self) if res == gtk.RESPONSE_ACCEPT: return self.devs[self.sel.get_active()][0] return None 00173 def getDeviceName(parent=None): """ show a DeviceSelectionDialog and return None or a Sane device name """ d = DeviceSelectionDialog(parent=parent) res = d.run() d.destroy() return res 00182 def getDevice(parent=None): """ Convenience function. If no device is availabale, return 0 If exactly one device is available, return a SaneDevice instance for this scanner If more devices are available, open a device selection dialog, and return a SaneDevie instance for the selected device, or None, it the user aborts the selection """ devs = sane.get_devices() l = len(devs) if not l: return 0 if l == 1: return SaneDevice(devs[0][0]) name = getDeviceName(parent) if name: return SaneDevice(name) return None 00203 class SaneDevice(gobject.GObject): """ manages communication between a Sane device and WXWidgets. All widgets used to control a canner should be created via method of this class """ def __init__(self, devicename): gobject.GObject.__init__(self) self._device = sane.open(devicename) self._widgets = {} self._devicelock = threading.RLock() self._tooltips = gtk.Tooltips() self.mainthread = threading.currentThread() self.devicename = devicename # a few very common Sane options may be read and set directly # This makes the synchronization with higher level widgets like # a preview window easier _deviceattrnames = ('tl_x', 'br_x', 'tl_y', 'br_y') # When the device is scanning, we may not be able to access # the options. Some Sane backends return an error in this case. # Hence we cache the most important options _cachedattr = {} def __getattr__(self, key): if key in self._deviceattrnames: if self._devicelock.acquire(0): try: # FIXME: # at least with the test backend, this raises from time # to time _sane.error: Invalid argument. No # idea why... This is not a good workaround... res = self._device.__getattr__(key) self._cachedattr[key] = res except sane._sane.error: res = self._cachedattr[key] self._devicelock.release() return res return self._cachedattr[key] d = self.__dict__ if key in d.keys(): return d[key] raise AttributeError, "no such attribute %s" % key def __setattr__(self, key, value): if key in self._deviceattrnames: if self._devicelock.acquire(0): self._device.__setattr__(key, value) self._cachedattr[key] = self._device.__getattr__(key) self._devicelock.release() else: # FIXME: should be impossible to change options during # scans. The affected widgets should be disabled # during scans. raise EikazoWidgetError("can't acquire device lock") if self.__dict__['_widgets'].has_key(key): for w in self.__dict__['_widgets'][key]: w.Reload() return self.__dict__[key] = value def _registerWidget(self, name, widget): if self._widgets.has_key(name): self._widgets[name].append(widget) else: self._widgets[name] = [widget] 00269 def widgets(self, optname): """ return the widgets controlling the option optname """ return self._widgets.get(optname, []) 00274 def getOptions(self): """ a trivial wrapper for Sane.SaneDev.get_options. Returns a list of tuples describing all the available options. A tuple consists of: - [0] the option number (integer) - [1] name (string) - [2] title (string) - [3] description (string) - [4] type integer, with the following meaning: 0 SANE_TYPE_BOOL 1 SANE_TYPE_INT 2 SANE_TYPE_FIXED 3 SANE_TYPE_STRING 4 SANE_TYPE_BUTTON 5 SANE_TYPE_GROUP (see Sane API doc, section 4.2.9.4) - [5] unit integer, with the following meaning: 0 SANE_UNIT_NONE 1 SANE_UNIT_PIXEL 2 SANE_UNIT_BIT 3 SANE_UNIT_MM 4 SANE_UNIT_DPI 5 SANE_UNIT_PERCENT 6 SANE_UNIT_MICROSECOND (see Sane API doc, section 4.2.9.5) - [6] size integer, with the following meaning: for SANE_TYPE_STRING options: max length of the string for SANE_TYPE_INT, SANE_TYPE_FIXED: "vector length": number of option values, mulptplied by sizeof(SANE_Word) this should be "scaled down" to the "real" vector length for SANE_TYPE_BOOL options: must be sizeof(SANE_Word) for SANE_TYPE_BUTTON, SANE_TYPE_GROUP: not used - [7] cap integer bitset: 1 SANE_CAP_SOFT_SELECT, The option value can be set by a call to sane_control_option() 2 SANE_CAP_HARD_SELECT. The option value can be set by user-intervention (e.g., by flipping a switch). The user-interface should prompt the user to execute the appropriate action to set such an option. This capability is mutually exclusive with SANE_CAP_SOFT_SELECT (either one of them can be set, but not both simultaneously). 4 SANE_CAP_SOFT_DETECT The option value can be detected by software. If SANE_CAP_SOFT_SELECT is set, this capability must be set. If SANE_CAP_HARD_SELECT is set, this capability may or may not be set. If this capability is set but neither SANE_CAP_SOFT_SELECT nor SANE_CAP_HARD_SELECT are, then there is no way to control the option. That is, the option provides read-out of the current value only. 8 SANE_CAP_EMULATED. If set, this capability indicates that an option is not directly supported by the device and is instead emulated in the backend. A sophisticated frontend may elect to use its own (presumably better) emulation in lieu of an emulated option. 16 SANE_CAP_AUTOMATIC. If set, this capability indicates that the backend (or the device) is capable to picking a reasonable option value automatically. For such options, it is possible to select automatic operation by calling sane_control_option() with an action value of SANE_ACTION_SET_AUTO. 32 SANE_CAP_INACTIVE. If set, this capability indicates that the option is not currently active (e.g., because it's meaningful only if another option is set to some other value). 64 SANE_CAP_ADVANCED. If set, this capability indicates that the option should be considered an ``advanced user option.'' A frontend typically displays such options in a less conspicuous way than regular options (e.g., a command line interface may list such options last or a graphical interface may make them available in a seperate ``advanced settings'' dialog). - [8] constraint None, if the option has no constraint tuple of length 3 for SANE_CONSTRAINT_RANGE: min, max, quant int values for SANE_TYPE_INT options float values for SANE_TYPE_FIXED options list of numeric or string values. numeric values are ints for SANE_TYPE_INT options and floats for SANE_TYPE_FIXED options - [9] python name The is the same as name, except that '-' characters are replaced by '_' """ res = self._device.get_options() res = [list(x) for x in res] # The Sharp backend for example does not provide option names # for group options, only titles. [x.append(x[1] and x[1].replace('-', '_') or x[1]) for x in res] return [tuple(x) for x in res] 00370 def getGroupedOptionNames(self): """ return a list of tuples ('group name', ['group member',...]) """ optlist = self.getOptions() if optlist[0][4] == SANE_TYPE_GROUP: groupname = optlist[0][2] optlist.pop(0) else: groupname = '' res = [] l = [] for opt in optlist: if opt[4] == SANE_TYPE_GROUP: res.append((groupname, l)) groupname = opt[2] l = [] else: l.append(opt[-1]) res.append((groupname, l)) return res 00392 def getOptionNames(self): """ return the list of available device options """ return self._device.optlist def _showOptInfo(self, opt, optname): print "-----------------------" print "opt name", optname opt = self._device.opt[optname] print "index", opt.index print "type", opt.type print "unit", opt.unit print "size", opt.size print "cap", opt.cap print "constraint", opt.constraint print "enabled", opt.is_active() 00409 def createOptionWidget(self, optname, config, optwidget='default', **kw): """ return an appropriate widget for a device option. optnum is the number of the option as returned by GetOptions() """ opt = self._device.opt[optname] if opt.type == SANE_TYPE_BOOL: if opt.is_settable(): res = CheckButton(optname, self, config, **kw) else: # we can have the funny case that an option has the capability # SANE_CAP_HARD_SELECT, but not SANE_CAP_SOFT_DETECT, # i.e., that is_settable() returns false.This # means that the option is set by hardware, and can't be # read by software. In other words, it is completely # useless for a program ;) This is true for test backend, # option bool_hard_select. We'll simply ignore such an option. if opt.cap & sane._sane.CAP_SOFT_DETECT == 0: return None res = BoolDisplay(optname, self, **kw) self._registerWidget(optname, res) return res elif opt.type in (SANE_TYPE_FIXED, SANE_TYPE_INT): # slider for range constraints; choicebox for list constraints, # textfield no non-constraint values # FIXME: check for arrays!!! # check, if we have a single value or an array. The value # of opt.size is platform dependent (quote from from the Sane # API doc: " The size must be a positive integer multiple of # the size of a SANE_Word. The option value is a vector of length # size/sizeof(SANE_Word)", so we'll use the Python type constr = opt.constraint # FIXME: the sane module returns the option size as specified # by the backend, i.e., for SANE_Word, SANE_Int in multiples # of sizeof(SANE_Word). This is not of much value in Python: # here, we don't know the value of sizeof(SANE_Word). # we need a function in the C extension that tells us the # size. if 0: if opt.size > 1: if type(constr) == type(()): print "xxx cCURVES DISABLED" return None res = SaneCurve(optname, self, config, **kw) elif type(constr) == type([]): # FIXME: might be reasonable to build a table of # choiceboxes for shorter lists print "can't build a widget for a float/integer list with selection constraint" return None else: # hrmm. What to do here? print "can't build a widget for a float/integer list without constraint" return None elif type(constr) == type(()): minValue = constr[0] maxValue = constr[1] if optwidget in ('default', 'hscale'): res = HScale(optname, self, config, **kw) elif optwidget == 'labeled hscale': res = HScalePanel(optname, self, config, **kw) elif optwidget == 'vscale': res = VScale(optname, self, config, **kw) elif optwidget == 'labeled vscale': res = VScalePanel(optname, self, config, **kw) elif optwidget == 'number field': res = NumberField(optname, self, config, **kw) elif optwidget == 'labeled number field': res = LabeledNumberField(optname, self, config, **kw) else: raise EikazoWidgetError( 'invalid widget type for integer or fixed widget: %s %s' \ % (opt.name, optwidget)) elif type(constr) == type([]): if optwidget in ('default', 'choice'): res = Choice(optname, self, config, **kw) elif optwidget == 'labeled choice': res = LabeledChoice(optname, self, config, **kw) else: raise EikazoWidgetError( 'invalid widget type for integer or fixed widget: %s %s' \ % (opt.name, optwidget)) else: res = NumberField(optname, self, config, **kw) self._registerWidget(optname, res) return res elif opt.type == SANE_TYPE_STRING: # right now, we'll support only selection lists, i.e., # the constraint type must be a list constr = opt.constraint if type(constr) == type([]): if optwidget in ('default', 'choice'): res = Choice(optname, self, config, **kw) elif optwidget == 'labeled choice': res = LabeledChoice(optname, self, config, **kw) else: raise EikazoWidgetError( 'invalid widget type for integer or fixed widget: %s %s' \ % (opt.name, optwidget)) self._registerWidget(optname, res) return res res = StringOption(optname, self, config, **kw) self._registerWidget(optname, res) return res elif opt.type == SANE_TYPE_BUTTON: res = Button(optname, self, **kw) self._registerWidget(optname, res) return res elif opt.type == SANE_TYPE_GROUP: # this is a "layout hint"; should be handled externally return None # self._showOptInfo(opt, optname) raise EikazoWidgetError("unknown option type %i" % opt.type) def reloadOptions(self): # reload device options. Happens for example, if the scan mode # changes from bi-level to gray scale. For gray scale, setting # the threshold is disabled by most backends for wlist in self._widgets.values(): for w in wlist: w.Reload() self.geometryChanged() 00534 def enable_options(self, value): """ enable or disable the option widgets. Must be called before starting a scan and after the end of a scan. Many backends refuse to allow the change of options during a scan. """ if threading.currentThread() == self.mainthread: self._enable_options(value) else: gtk.gdk.threads_enter() self._enable_options(value) gtk.gdk.threads_leave() def _enable_options(self, value): for wlist in self._widgets.values(): for w in wlist: w.enable_option(value) 00553 def getMaxScanArea(self): """ return the max size of the scan area (tlx, brx, tly, bry) The units are millemeters, if the resolution can be determined. Otherwise, the default unit is returned """ res = getattr(self, '_scanarea', None) if res: return res found = 0 resol = None for opt in self.getOptions(): if opt[1] == 'tl-x': found |= 1 tlx = opt if found == 31: break if opt[1] == 'tl-y': found |= 2 tly = opt if found == 31: break if opt[1] == 'br-x': found |= 4 brx = opt if found == 31: break if opt[1] == 'br-y': found |= 8 bry = opt if found == 31: break elif opt[1] == 'resolution': found |= 16 resol = opt if found == 31: break if (found & 15) != 15: raise EikazoWidgetError("Sane backend does not provide scan window size") if resol: resol = self._device.resolution self._scanarea = (_millimeters(_getMinConstraint(tlx), tlx, resol), _millimeters(_getMaxConstraint(brx), brx, resol), _millimeters(_getMinConstraint(tly), tly, resol), _millimeters(_getMaxConstraint(bry), bry, resol)) return self._scanarea def geometryChanged(self): self.emit("sane-geometry") def reloadParams(self): self.emit("sane-reload-params") 00601 def adf_mode(self): """ check, if an ADF is available and selected returns or True or False """ # FIXME: is the list of possible strings which "indicate" an # ADF complete or not?? # FIXME: If we have an ADF scanner without a "source" option, # or an inactive "source" option, False will be returned. # That's not, what we want... I don't see another way to detect # an ADF, and if it is enabled, except from the 'source' option... # A candidate from this case is for example the fi-5110 # PROBLEM: We can't read inactive options... # grepping the Sane backend sources, the following ways exist # to select and detect an ADF: # # option source has one of the values: # 'ADF' (avision, hp, microtek2, sp15c) # 'ADF Rear' (avision) # 'ADF Duplex' (avision, fujitsu) # 'ADF Front' (fujitsu) # 'ADF Back' (fujitsu) # 'Automatic Document Feeder' (bh, epson, mustek, nec, pixma, # sharp, umax) # 'Document Feeder' (snapscan) # 'Filmstrip' (microtek2) # I'm not 100% sure about Filmstrip, but it could make sense # to treat it similary to an ADF # # bool option 'adf': artec, ibm # bool option 'noadf': canon # string option 'feeder-mode', value 'All Pages': matsushita # # FIXME: The backends plustek, ma1509, matsushita seem to support # ADFs too, but I could not figure out, how these can be detected # The plustek backend perhaps uses the device type string # 'USB sheet-fed scanner', and the matsushita backend uses the # device type string 'sheetfed scanner'. # The ma1509 describes itself as a 'flatbed scanner', so there # seems to be no way to dicover, if an ADF is used or installed... optnames = self.getOptionNames() try: if 'source' in optnames: source = self._device.source for test in ('ADF', 'Document Feeder'): if source.find(test) >= 0: return True except AttributeError, errval: if str(errval) == 'Inactive option: source': return False raise if 'adf' in optnames: try: return self._device.adf except AttributeError, errval: if str(errval) == 'Inactive option: adf': return False raise elif 'noadf' in optnames: try: return not self._device.noadf except AttributeError, errval: if str(errval) == 'Inactive option: noadf': return False raise elif 'feeder_mode' in optnames: try: return self._device.feeder_mode == 'All Pages' except AttributeError, errval: if str(errval) == 'Inactive option: adf': return False raise return False 00678 def duplex_mode(self): """ returns true, if the scanner is in ADF mode and if duplex scans are enabled (if possible) """ if not self.adf_mode(): return False optnames = self.getOptionNames() if 'source' in optnames: # avision, fujitsu try: return self._device.source == 'ADF Duplex' except AttributeError, errval: if str(errval) == 'Inactive option: source': return False raise elif 'duplex' in optnames: # bh try: return self._device.duplex except AttributeError, errval: if str(errval) == 'Inactive option: duplex': return False raise elif 'adf_mode' in optnames: # epson try: return self._device.adf_mode == 'Duplex' except AttributeError, errval: if str(errval) == 'Inactive option: adf_mode': return False raise return False gobject.signal_new("sane-geometry", SaneDevice, gobject.SIGNAL_RUN_FIRST | gobject.SIGNAL_ACTION, gobject.TYPE_NONE, ()) gobject.signal_new("sane-reload-params", SaneDevice, gobject.SIGNAL_RUN_FIRST | gobject.SIGNAL_ACTION, gobject.TYPE_NONE, ()) def _millimeters(val, opt, resol): if opt[5] == SANE_UNIT_MM: return val elif opt[5] == SANE_UNIT_PIXEL and resolOpt: # well, we don't have a warranty that the scanner provides the # resolution in DPI units, but everthing else would be really pointless.. return 25.4 * val / resol return val def _getMinConstraint(opt): constr = opt[8] if type(constr) == type(()): return constr[0] elif type(constr) == type([]): return min(constr) raise EikazoWidgetError("can't retrieve constraint data for option %s" % opt[1]) def _getMaxConstraint(opt): constr = opt[8] if type(constr) == type(()): return constr[1] elif type(constr) == type([]): return max(constr) raise EikazoWidgetError("can't retrieve constraint data for option %s" % opt[1]) 00747 class DeviceWidget: """ common stuff for the device widgets """ def __init__(self, optname, device): self.optname = optname self.device = device self.tLabel = None 00755 def titleWidget(self): """ return a gtk.Label with the title of the option """ if not self.tLabel: opt = self.device._device.opt[self.optname] self.tLabel = gtk.Label(T2_(opt.title)) return self.tLabel def show(self): raise EikazoWidgetError("class %s: DeviceWidget.show must be overloaded" % \ self.__class__.__name__) def _show(self): if self.tLabel: self.tLabel.show() def hide(self): raise EikazoWidgetError("DeviceWidget.hide must be overloaded") def _hide(self): if self.tLabel: self.tLabel.hide() 00778 class BoolDisplay(DeviceWidget, gtk.HBox): """ for options that can't be set via software. This includes purely "informal" data and options that can be set by a switch or button on the scanner. FIXME For now, we simply display the text "[on]" or "[off]". Something like a green/red LED image would look much nicer, on the other hand, a _pure_ red/green signel is not very useful for many colorblind persons... """ # no need to store config data def __init__(self, optname, device, **kw): gtk.HBox.__init__(self, **kw) DeviceWidget.__init__(self, optname, device) opt = device._device.opt[optname] self.display = gtk.Label(_("[off]")) self.pack_start(self.display, expand=False, fill=False, padding=5) self.display.show() label = gtk.Label(T2_(opt.title)) self.pack_start(label, expand=False, fill=False) label.show() # FIXME: need to add an eventbox, in order to get a tooltip?? #if opt.desc: # device._tooltips.set_tip(self.display, opt.desc) self.Reload() def Reload(self): if self.device._device.opt[self.optname].is_active(): if self.device._device.__getattr__(self.optname): self.display.set_text(_("[on]")) else: self.display.set_text(_("[off]")) def enable_option(self, value): pass def show(self): self._show() gtk.HBox.show(self) def hide(self): self._hide() gtk.HBox.hide(self) class CheckButton(DeviceWidget, gtk.CheckButton, SaneWidgetConfig): def __init__(self, optname, device, config, **kw): opt = device._device.opt[optname] gtk.CheckButton.__init__(self, label=T2_(opt.title), **kw) DeviceWidget.__init__(self, optname, device) self.set_sensitive(opt.is_active()) if opt.is_active(): self.set_active(device._device.__getattr__(optname)) if opt.desc: device._tooltips.set_tip(self, T2_(opt.desc)) self.connect("toggled", _DeviceEvent(device, self, optname)) SaneWidgetConfig.__init__(self, config) def Reload(self): opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) if opt.is_active(): if self.delayedConfigValue: self.device._device.__setattr__(self.optname, self.delayedConfigValue) self.delayedConfigValue = None self.set_active(self.device._device.__getattr__(self.optname)) def ChangeDeviceValue(self): self.device._device.__setattr__(self.optname, self.get_active()) def enable_option(self, value): if not value: self.set_sensitive(False) else: opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) def guiValue(self): return self.get_active() def show(self): self._show() gtk.CheckButton.show(self) def hide(self): self._hide() gtk.CheckButton.hide(self) class _DeviceEvent: def __init__(self, device, widget, optname): self.optname = optname self.device = device self.widget = widget def __call__(self, e): wlist = self.device._widgets[self.optname] self.widget.ChangeDeviceValue() for w in wlist: if w != self.widget: w.Reload() if self.device._device.last_opt & sane.INFO_RELOAD_OPTIONS: self.device.reloadOptions() # Not all backends set the reload params flag, when # scan size or resolution are changed. if self.optname in ('tl_x', 'tl_y', 'br_x', 'br_y', 'resolution', 'y-resolution') or \ self.device._device.last_opt & sane.INFO_RELOAD_PARAMS: self.device.reloadParams() _geometryOptions = ('tl_x', 'tl_y', 'br_x', 'br_y') 00888 class _SaneScale(DeviceWidget, SaneWidgetConfig): """mixin for HScale and VScale. Does the "real stuff": connects the gtk.Adjustment instance and implements the communication with the Sane device """ def __init__(self, optname, device, config, **kw): DeviceWidget.__init__(self, optname, device) opt = device._device.opt[optname] constr = opt.constraint if opt.is_active(): value = device._device.__getattr__(optname) else: value = constr[0] # min value if opt.desc: device._tooltips.set_tip(self, T2_(opt.desc)) self.adj = gtk.Adjustment(value, constr[0], constr[1], constr[2], **kw) self.set_adjustment(self.adj) self.set_sensitive(opt.is_active()) self.adj.connect("value-changed", _DeviceEvent(device, self, optname)) SaneWidgetConfig.__init__(self, config) def Reload(self): opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) if opt.is_active(): if self.delayedConfigValue: self.device._device.__setattr__(self.optname, self.delayedConfigValue) self.delayedConfigValue = None v = self.device._device.__getattr__(self.optname) self.adj.set_value(v) def ChangeDeviceValue(self): opt = self.device._device.opt[self.optname] constr = opt.constraint v = self.adj.get_value() if opt.type == SANE_TYPE_INT: v = int(round(v)) if v < constr[0]: v = constr[0] elif v > constr[1]: v = constr[1] self.device._device.__setattr__(self.optname, v) # the step constraint may be violated; the backend should fix this # for us self.adj.set_value(self.device._device.__getattr__(self.optname)) #print "opt", self.optname, self.device._device, v, self.device._device.__getattr__(self.optname) if self.optname in _geometryOptions: self.device.geometryChanged() def enable_option(self, value): if not value: self.set_sensitive(False) else: opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) def guiValue(self): return self.adj.get_value() class HScalePanel(gtk.VBox): def __init__(self, optname, device, config, **kw): gtk.VBox.__init__(self, **kw) opt = device._device.opt[optname] label = gtk.Label(T2_(opt.title)) self.add(label) label.show() self.scale = HScale(optname, device, config, **kw) self.add(self.scale) self.scale.show() def Reload(self): self.scale.Reload() def ChangeDeviceValue(self): self.scale.ChangeDeviceValue() def enable_option(self, value): self.scale.enable_option(value) class HScale(_SaneScale, gtk.HScale): def __init__(self, optname, device, config, **kw): gtk.HScale.__init__(self, **kw) _SaneScale.__init__(self, optname, device, config, **kw) def show(self): self._show() gtk.HScale.show(self) def hide(self): self._hide() gtk.HScale.hide(self) class VScale(_SaneScale, gtk.VScale): def __init__(self, optname, device, config, **kw): gtk.VScale.__init__(self, **kw) _SaneScale.__init__(self, optname, device, config, **kw) def show(self): self._show() gtk.VScale.show(self) def hide(self): self._hide() gtk.VScale.hide(self) class LabeledNumberField(gtk.VBox): def __init__(self, optname, device, config, **kw): gtk.VBox.__init__(self, **kw) opt = device._device.opt[optname] label = gtk.Label(T2_(opt.title)) self.add(label) label.show() self.string = NumberField(optname, device, config, **kw) self.add(self.string) self.string.show() def Reload(self): self.string.Reload() def ChangeDeviceValue(self): self.string.ChangeDeviceValue() def enable_option(self, value): self.string.enable_option(value) 01015 class NumberField(DeviceWidget, gtk.SpinButton, SaneWidgetConfig): """ used for integers and Sane_Fixed. """ def __init__(self, optname, device, config, **kw): gtk.SpinButton.__init__(self, **kw) DeviceWidget.__init__(self, optname, device) opt = device._device.opt[optname] if opt.is_active(): value = device._device.__getattr__(optname) else: value = 0 self.set_sensitive(opt.is_active()) self.set_numeric(True) if opt.desc: device._tooltips.set_tip(self, T2_(opt.desc)) constr = opt.constraint if type(constr) == type(()): adj = gtk.Adjustment(value, constr[0], constr[1], constr[2], **kw) else: if opt.type == SANE_TYPE_INT: adj = gtk.Adjustment(value, -2**31, 2**31-1, 1, 10, 10) else: # must be float # FIXME: seems to be buggy for values >= 32768 adj = gtk.Adjustment(value, -100000.0, 100000.0, 1.0, 10.0, 10.0) digits = 0 if opt.type == SANE_TYPE_FIXED: # FIXME: one digit is an absolutely arbitrary choice... # Would perhaps be better to use a simple gtk.Entry field digits = 1 self.configure(adj, 1, digits) self.connect("value-changed", _DeviceEvent(device, self, optname)) SaneWidgetConfig.__init__(self, config) def Reload(self): opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) if opt.is_active(): if self.delayedConfigValue: self.device._device.__setattr__(self.optname, self.delayedConfigValue) self.delayedConfigValue = None self.set_value(self.device._device.__getattr__(self.optname)) def ChangeDeviceValue(self): # FIXME: check for constraints!! opt = self.device._device.opt[self.optname] constr = opt.constraint v = self.get_value() if opt.type == SANE_TYPE_INT: v = int(round(v)) if constr: if v < constr[0]: v = constr[0] elif v > constr[1]: v = constr[1] self.device._device.__setattr__(self.optname, v) # the step constraint may be violated; the backend should fix this # for us self.set_value(self.device._device.__getattr__(self.optname)) if self.optname in _geometryOptions: self.device.geometryChanged() def enable_option(self, value): if not value: self.set_sensitive(False) else: opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) def guiValue(self): return self.get_value() def show(self): self._show() gtk.SpinButton.show(self) def hide(self): self._hide() gtk.SpinButton.hide(self) class LabeledChoice(gtk.VBox): def __init__(self, optname, device, config, **kw): gtk.VBox.__init__(self, **kw) opt = device._device.opt[optname] label = gtk.Label(T2_(opt.title)) self.add(label) label.show() self.choice = Choice(optname, device, config, **kw) self.add(self.choice) self.choice.show() def Reload(self): self.choice.Reload() def ChangeDeviceValue(self): self.choice.ChangeDeviceValue() def enable_option(self, value): self.choice.enable_option(value) class Choice(DeviceWidget, gtk.ComboBox, SaneWidgetConfig): def __init__(self, optname, device, config, **kw): DeviceWidget.__init__(self, optname, device) opt = device._device.opt[optname] if opt.type == SANE_TYPE_INT: store = gtk.ListStore(gobject.TYPE_INT) elif opt.type == SANE_TYPE_FIXED: store = gtk.ListStore(gobject.TYPE_FLOAT) elif opt.type == SANE_TYPE_STRING: store = gtk.ListStore(gobject.TYPE_STRING) else: raise EikazoWidgetError("can't handle creation of comboboxes for type %s" % opt.type) gtk.ComboBox.__init__(self, store, **kw) constr = opt.constraint for s in constr: store.append([T2_(s)]) cell = gtk.CellRendererText() self.pack_start(cell, True) self.add_attribute(cell, "text", 0) self.set_sensitive(opt.is_active()) if opt.is_active(): value = device._device.__getattr__(optname) pos = constr.index(value) self.set_active(pos) if opt.desc: # FIXME: tooltips don't show up. Need to add an eventbox? device._tooltips.set_tip(self, T2_(opt.desc)) self.constraint = constr # FIXME: can this change for some backends?? self.set_sensitive(opt.is_active()) self.connect("changed", _DeviceEvent(device, self, optname)) SaneWidgetConfig.__init__(self, config) def Reload(self): opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) if opt.is_active(): if self.delayedConfigValue: self.device._device.__setattr__(self.optname, self.delayedConfigValue) self.delayedConfigValue = None value = self.device._device.__getattr__(self.optname) pos = opt.constraint.index(value) self.set_active(pos) def ChangeDeviceValue(self): i = self.get_active() self.device._device.__setattr__(self.optname, self.constraint[i]) def enable_option(self, value): if not value: self.set_sensitive(False) else: opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) def guiValue(self): i = self.get_active() return self.constraint[i] def show(self): self._show() gtk.ComboBox.show(self) def hide(self): self._hide() gtk.ComboBox.hide(self) class LabeledStringOption(gtk.VBox): def __init__(self, optname, device, config, **kw): gtk.VBox.__init__(self, **kw) opt = device._device.opt[optname] label = gtk.Label(T2_(opt.title)) self.add(label) label.show() self.string = StringOption(optname, device, config, **kw) self.add(self.string) self.string.show() def Reload(self): self.string.Reload() def ChangeDeviceValue(self): self.string.ChangeDeviceValue() def enable_option(self, value): self.string.enable_option(value) class StringOption(DeviceWidget, gtk.Entry, SaneWidgetConfig): def __init__(self, optname, device, config, **kw): DeviceWidget.__init__(self, optname, device) opt = device._device.opt[optname] gtk.Entry.__init__(self, opt.size-1) if opt.is_active(): value = device._device.__getattr__(optname) else: value = '' self.set_text(value) self.set_sensitive(opt.is_active()) self.optname = optname self.device = device if opt.desc: device._tooltips.set_tip(self, T2_(opt.desc)) self.connect("changed", _DeviceEvent(device, self, optname)) SaneWidgetConfig.__init__(self, config) def Reload(self): opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) if opt.is_active(): if self.delayedConfigValue: self.device._device.__setattr__(self.optname, self.delayedConfigValue) self.delayedConfigValue = None self.set_text(self.device._device.__getattr__(self.optname)) def ChangeDeviceValue(self): self.device._device.__setattr__(self.optname, self.get_text()) def enable_option(self, value): if not value: self.set_sensitive(False) else: opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) def guiValue(self): return self.get_text() def show(self): self._show() gtk.Entry.show(self) def hide(self): self._hide() gtk.Entry.hide(self) class Button(DeviceWidget, gtk.Button): # no need to store/load config data def __init__(self, optname, device, **kw): DeviceWidget.__init__(self, optname, device) opt = device._device.opt[optname] gtk.Button.__init__(self, label=T2_(opt.title), **kw) self.optname = optname self.device = device self.set_sensitive(opt.is_active()) if opt.desc: device._tooltips.set_tip(self, T2_(opt.desc)) self.connect("clicked", _DeviceEvent(device, self, optname)) def Reload(self): opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) def ChangeDeviceValue(self): self.device._device.__setattr__(self.optname, 1) def enable_option(self, value): if not value: self.set_sensitive(False) else: opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) def titleWidget(self): """ no need for a "real" label: the button provides its own explanatory text """ if not self.tLabel: self.tLabel = gtk.Label("") return self.tLabel def show(self): self._show() gtk.CheckButton.show(self) def hide(self): self._hide() gtk.CheckButton.hide(self) class xxxCurve(DeviceWidget, gtk.Curve, SaneWidgetConfig): def __init__(self, optname, device, config, **kw): print "xxx init Curve 1" DeviceWidget.__init__(self, optname, device) opt = device._device.opt[optname] gtk.Curve.__init__(self) print "xxx init Curve 2" if type(opt.constraint) != type(()): raise EikazoWidgetError("Wigdets.Curve requires a min/man constraint option") self.optname = optname self.device = device print "xxx init Curve 3" self.set_sensitive(opt.is_active()) if opt.desc: device._tooltips.set_tip(self, T2_(opt.desc)) print "xxx init Curve 4" self.set_range(0, opt.size, opt.constraint[0], opt.constraint[1]) print "xxx init Curve 5" #xxx self.set_curve_type(gtk.CURVE_TYPE_LINEAR) print "xxx init Curve 6" SaneWidgetConfig.__init__(self, config) print "xxx init Curve finished" class SaneCurve(DeviceWidget, Curve.Curve, SaneWidgetConfig): def __init__(self, optname, device, config, **kw): opt = device._device.opt[optname] if type(opt.constraint) != type(()): raise EikazoWidgetError("Wigdets.Curve requires a min/man constraint option") DeviceWidget.__init__(self, optname, device) Curve.Curve.__init__(self, 0, opt.size-1, opt.constraint[0], opt.constraint [1]) self.optname = optname self.device = device self.set_sensitive(opt.is_active()) if opt.desc: device._tooltips.set_tip(self, T2_(opt.desc)) #xxx self.curve.set_range(0, opt.size, opt.constraint[0], opt.constraint[1]) SaneWidgetConfig.__init__(self, config) def Reload(self): opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) def ChangeDeviceValue(self): self.device._device.__setattr__(self.optname, self.gamma.get_vector()) def enable_option(self, value): if not value: self.set_sensitive(False) else: opt = self.device._device.opt[self.optname] self.set_sensitive(opt.is_active()) def show(self): self._show() gtk.CheckButton.show(self) def hide(self): self._hide() gtk.CheckButton.hide(self)