[Alexandria-list] userland cuecut support
Christopher Cyll
ccyll at wso.williams.edu
Mon Feb 7 00:29:44 EST 2005
Hi,
First of all, Alexandria is excellent. I cataloged my whole collection
this weekend!
One of the big draws for me was that Alexandria supported CueCat
(since I didn't want to type all the ISBNs in by hand). Unfortunately,
I run Linux 2.6 which means the CueCat driver wasn't an option for me.
There didn't seem to any other way to do this (I hope I didn't miss
anything), so I put together a patch that implements userland CueCat
scanning for Alexandria. In addition to letting me to use the CueCat
with Alexandria under Linux 2.6, it would also allow it work for other
operating systems that don't necessarily have a driver.
I wondered if there was any interest in this patch?
Here a few gotchas with it, though:
First it's against 0.4.0, not CVS. I'd be willing to redo this,
though.
Second, I've tried to write it in a way so that interpreters for other
scanners were easy to drop in. I just did it the first way that came
to mind, but if there's a better Ruby idiom for this, I'd be happy to
switch to it.
Lastly, I wonder if this is the optimal UI for the feature? I've added
a scanner radio button below the ISBN option, with a drop down box for
selecting scanner type and a text field for receiving scanner
data. However, it occurs to me that a better interface might be to
change the ISBN field to a more generic number/code label and then
have a drop down box which defaulted to ISBN, but could be changed to
CueCat, or other methods. Or maybe the right way to do it, is to take
the length limit off the ISBN field and just let it accept all kinds
of data and automagically do the right thing. What do you think?
I'd be happy to work with you to get this patch in if you're
interested, but I understand that userland interpretation of scanner
code might not be the direction you want to head in (though I think
the cross platform capability is a huge win).
Additionally, I noticed that when adding a book manually it requires
an ISBN. What's the rational behind this? I have a thesis, some
conference proceedings, and some just really old books that lack a
number. I'd love to be able to make manual entries for them, but
Alexandria's insistence on an ISBN has me at something of a loss. Is
there an alternate way to do this?
Anyways, great software and let me know if there's interest in the
patch!
Topher Cyll
-------------- next part --------------
diff -rup alexandria-0.4.0/data/alexandria/glade/new_book_dialog.glade alexandria-0.4.0-toph2/data/alexandria/glade/new_book_dialog.glade
--- alexandria-0.4.0/data/alexandria/glade/new_book_dialog.glade 2004-11-05 13:01:48.000000000 -0800
+++ alexandria-0.4.0-toph2/data/alexandria/glade/new_book_dialog.glade 2005-02-06 20:57:01.000000000 -0800
@@ -2,7 +2,6 @@
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
-<requires lib="gnome"/>
<widget class="GtkDialog" id="new_book_dialog">
<property name="border_width">8</property>
@@ -73,7 +72,7 @@
<widget class="GtkTable" id="table1">
<property name="border_width">6</property>
<property name="visible">True</property>
- <property name="n_rows">4</property>
+ <property name="n_rows">5</property>
<property name="n_columns">2</property>
<property name="homogeneous">False</property>
<property name="row_spacing">6</property>
@@ -119,8 +118,8 @@
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
@@ -150,8 +149,8 @@
<packing>
<property name="left_attach">0</property>
<property name="right_attach">2</property>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
<property name="x_options">fill</property>
</packing>
</child>
@@ -203,7 +202,7 @@ by keyword</property>
<property name="max_length">0</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
- <property name="invisible_char" translatable="yes">*</property>
+ <property name="invisible_char">*</property>
<property name="activates_default">False</property>
<property name="width_chars">30</property>
<signal name="changed" handler="on_changed" last_modification_time="Mon, 14 Jun 2004 16:24:17 GMT"/>
@@ -239,8 +238,8 @@ by keyword</property>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
<property name="x_options">fill</property>
<property name="y_options">fill</property>
</packing>
@@ -263,8 +262,8 @@ by keyword</property>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
<property name="y_padding">12</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
@@ -293,7 +292,7 @@ by keyword</property>
<property name="max_length">18</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
- <property name="invisible_char" translatable="yes">*</property>
+ <property name="invisible_char">*</property>
<property name="activates_default">False</property>
<property name="width_chars">13</property>
<signal name="changed" handler="on_changed" last_modification_time="Thu, 18 Mar 2004 23:45:37 GMT"/>
@@ -341,12 +340,104 @@ by keyword</property>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
<property name="x_options">expand|shrink|fill</property>
<property name="y_options"></property>
</packing>
</child>
+
+ <child>
+ <widget class="GtkRadioButton" id="scanner_radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">S_canner:</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">isbn_radiobutton</property>
+ <signal name="toggled" handler="on_criterion_toggled" last_modification_time="Mon, 14 Jun 2004 15:26:35 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkEventBox" id="eventbox_combo_scanner">
+ <property name="visible">True</property>
+ <property name="visible_window">False</property>
+ <property name="above_child">True</property>
+ <signal name="button_press_event" handler="on_clicked" last_modification_time="Sun, 06 Feb 2005 07:29:28 GMT"/>
+
+ <child>
+ <widget class="GtkComboBox" id="combo_scanner">
+ <property name="visible">True</property>
+ <property name="items" translatable="yes"></property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEventBox" id="eventbox_entry_scanner">
+ <property name="visible">True</property>
+ <property name="visible_window">False</property>
+ <property name="above_child">True</property>
+ <signal name="button_press_event" handler="on_clicked" last_modification_time="Sun, 06 Feb 2005 07:29:42 GMT"/>
+
+ <child>
+ <widget class="GtkEntry" id="entry_scanner">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <signal name="changed" handler="on_changed" last_modification_time="Thu, 18 Mar 2004 23:45:37 GMT"/>
+ <signal name="activate" handler="on_add" last_modification_time="Sat, 19 Jun 2004 17:33:18 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
</widget>
<packing>
<property name="padding">0</property>
Only in alexandria-0.4.0-toph2/lib/alexandria: scanners.rb
diff -rup alexandria-0.4.0/lib/alexandria/ui/new_book_dialog.rb alexandria-0.4.0-toph2/lib/alexandria/ui/new_book_dialog.rb
--- alexandria-0.4.0/lib/alexandria/ui/new_book_dialog.rb 2004-11-05 13:01:48.000000000 -0800
+++ alexandria-0.4.0-toph2/lib/alexandria/ui/new_book_dialog.rb 2005-02-06 20:56:39.000000000 -0800
@@ -59,6 +59,10 @@ module UI
@treeview_results.append_column(col)
@entry_isbn.grab_focus
@combo_search.active = 0
+ Alexandria::Scanners.keys.sort.each { |key|
+ @combo_scanner.append_text(key)
+ }
+ @combo_scanner.active = 0
if File.exist?(Preferences.instance.cuecat_device)
@cuecat_image.pixbuf = Icons::CUECAT
@@ -70,20 +74,35 @@ module UI
def on_criterion_toggled(item)
return unless item.active?
- if is_isbn = item == @isbn_radiobutton
+
+ is_isbn = item == @isbn_radiobutton
+ is_scanner = item == @scanner_radiobutton
+ is_search = item == @title_radiobutton
+
+ changed = nil
+ if is_isbn
@latest_size = @new_book_dialog.size
- @new_book_dialog.resizable = false
- else
+ elsif is_scanner
+ changed = @entry_scanner
+ elsif is_search
@new_book_dialog.resizable = true
@new_book_dialog.resize(*@latest_size) unless @latest_size.nil?
+ changed = @entry_search
end
- @entry_isbn.sensitive = is_isbn
- @combo_search.sensitive = !is_isbn
- @entry_search.sensitive = !is_isbn
- @button_find.sensitive = !is_isbn
- @scrolledwindow.visible = !is_isbn
- on_changed(is_isbn ? @entry_isbn : @entry_search)
- unless is_isbn
+
+ unless is_search
+ @new_book_dialog.resizable = false
+ changed = @entry_isbn
+ end
+
+ @entry_isbn.sensitive = is_isbn
+ @entry_scanner.sensitive = is_scanner
+ @entry_search.sensitive = is_search
+ @button_find.sensitive = is_search
+ @scrolledwindow.visible = is_search
+
+ on_changed(changed)
+ if is_search
@button_add.sensitive =
@treeview_results.selection.count_selected_rows > 0
end
@@ -91,7 +110,12 @@ module UI
def on_changed(entry)
ok = !entry.text.strip.empty?
- (entry == @entry_isbn ? @button_add : @button_find).sensitive = ok
+
+ if entry == @entry_isbn || entry == @entry_scanner
+ @button_add.sensitive = ok
+ else
+ @button_find.sensitive = ok
+ end
end
def on_find
@@ -146,16 +170,26 @@ module UI
books_to_add = []
if @isbn_radiobutton.active?
+ isbn = @entry_isbn.text
+ elsif @scanner_radiobutton.active?
+ # FIXME
+ names = Alexandria::Scanners.keys.sort
+ name = names[@combo_scanner.active]
+ func = Alexandria::Scanners[name]
+ isbn = func.call(@entry_scanner.text)
+ end
+
+ if isbn
# Perform the ISBN search via the providers.
- isbn = begin
- Library.canonicalise_isbn(@entry_isbn.text)
+ cannonized_isbn = begin
+ Library.canonicalise_isbn(isbn)
rescue
raise _("Couldn't validate the EAN/ISBN you " +
"provided. Make sure it is written " +
"correcty, and try again.")
end
- assert_not_exist(library, @entry_isbn.text)
- books_to_add << Alexandria::BookProviders.isbn_search(isbn)
+ assert_not_exist(library, isbn)
+ books_to_add << Alexandria::BookProviders.isbn_search(cannonized_isbn)
else
@treeview_results.selection.selected_each do |model, path,
iter|
@@ -202,23 +236,47 @@ module UI
if event.event_type == Gdk::Event::BUTTON_PRESS and
event.button == 1
- radio, target_widget, box2, box3 = case widget
+ radio, target_widget, others = case widget
when @eventbox_entry_search
[@title_radiobutton, @entry_search,
- @eventbox_combo_search, @eventbox_entry_isbn]
+ [@eventbox_combo_search,
+ @eventbox_entry_scanner,
+ @eventbox_combo_scanner,
+ @eventbox_entry_isbn]]
when @eventbox_combo_search
[@title_radiobutton, @combo_search,
- @eventbox_entry_search, @eventbox_entry_isbn]
+ [@eventbox_entry_search,
+ @eventbox_entry_scanner,
+ @eventbox_combo_scanner,
+ @eventbox_entry_isbn]]
+
+ when @eventbox_entry_scanner
+ [@scanner_radiobutton, @entry_scanner,
+ [@eventbox_entry_search,
+ @eventbox_combo_search,
+ @eventbox_combo_scanner,
+ @eventbox_entry_isbn]]
+
+ when @eventbox_combo_scanner
+ [@scanner_radiobutton, @combo_scanner,
+ [@eventbox_entry_search,
+ @eventbox_combo_search,
+ @eventbox_entry_scanner,
+ @eventbox_entry_isbn]]
when @eventbox_entry_isbn
- [@isbn_radiobutton, @entry_isbn,
- @eventbox_entry_search, @eventbox_combo_search]
+ [@isbn_radiobutton, @entry_isbn,
+ [@eventbox_entry_search,
+ @eventbox_combo_search,
+ @eventbox_entry_scanner,
+ @eventbox_combo_scanner]]
end
+
radio.active = true
target_widget.grab_focus
widget.above_child = false
- box2.above_child = box3.above_child = true
+ others.each {|other| other.above_child = true}
end
end
diff -rup alexandria-0.4.0/lib/alexandria.rb alexandria-0.4.0-toph2/lib/alexandria.rb
--- alexandria-0.4.0/lib/alexandria.rb 2004-11-05 13:01:48.000000000 -0800
+++ alexandria-0.4.0-toph2/lib/alexandria.rb 2005-02-06 20:55:49.000000000 -0800
@@ -55,3 +55,4 @@ require 'alexandria/library'
require 'alexandria/book_providers'
require 'alexandria/preferences'
require 'alexandria/ui'
+require 'alexandria/scanners'
-------------- next part --------------
module Alexandria
class CueCat
def translate_cuecat(data)
data.chomp!
fields = data.split('.')
fields.shift # First part is gibberish
fields.shift # Second part is serial number
type, code = fields.map {|field| decode_field(field) }
if type == 'IB5':
type = 'IBN'
code = code[0, 13]
end
return code if type == 'IBN'
raise "Don't know how to handle type #{type} (barcode: #{code})"
end
def decode_field (encoded)
seq = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-';
chars = encoded.split(//)
values = chars.map {|c| seq.index(c) }
padding = pad(values)
result = calc(values)
result = result[0, result.length - padding]
return result
end
def calc (values)
result = ''
while values.length > 0
num = ((values[0] << 6 | values[1]) << 6 | values[2]) << 6 | values[3]
result += ((num >> 16) ^ 67).chr
result += ((num >> 8 & 255) ^ 67).chr
result += ((num & 255) ^ 67).chr
values = values[4, values.length]
end
return result
end
def pad (array)
length = array.length % 4
if length != 0
raise "Error parsing CueCat input" if length == 1
length = 4 - length
length.times { array.push(0) }
end
return length
end
end
Scanners = {'CueCat' => CueCat.new.method(:translate_cuecat)}
end
More information about the Alexandria-list
mailing list