alexandria-book-collection-manager 0.6.9.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (293) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rubocop.yml +68 -0
  4. data/.rubocop_todo.yml +200 -0
  5. data/CHANGELOG.md +23 -0
  6. data/COPYING +339 -0
  7. data/ChangeLog.0 +3598 -0
  8. data/Gemfile +9 -0
  9. data/INSTALL.rdoc +156 -0
  10. data/PACKAGING +36 -0
  11. data/README.md +88 -0
  12. data/RELEASE_CHECKLIST +18 -0
  13. data/Rakefile +264 -0
  14. data/TODO +24 -0
  15. data/alexandria-book-collection-manager.gemspec +35 -0
  16. data/alexandria.desktop.in +10 -0
  17. data/bin/alexandria +82 -0
  18. data/doc/AUTHORS +60 -0
  19. data/doc/BUGS +31 -0
  20. data/doc/FAQ +369 -0
  21. data/doc/HACKING +19 -0
  22. data/doc/NEWS +341 -0
  23. data/doc/alexandria.1 +120 -0
  24. data/doc/cuecat_support.rdoc +67 -0
  25. data/lib/alexandria.rb +85 -0
  26. data/lib/alexandria/about.rb +82 -0
  27. data/lib/alexandria/book_providers.rb +411 -0
  28. data/lib/alexandria/book_providers/adlibris.rb +235 -0
  29. data/lib/alexandria/book_providers/amazon_aws.rb +261 -0
  30. data/lib/alexandria/book_providers/amazon_ecs_util.rb +405 -0
  31. data/lib/alexandria/book_providers/barnes_and_noble.rb +229 -0
  32. data/lib/alexandria/book_providers/bol_it.rb +162 -0
  33. data/lib/alexandria/book_providers/deastore.rb +277 -0
  34. data/lib/alexandria/book_providers/douban.rb +135 -0
  35. data/lib/alexandria/book_providers/ibs_it.rb +149 -0
  36. data/lib/alexandria/book_providers/mcu.rb +177 -0
  37. data/lib/alexandria/book_providers/proxis.rb +205 -0
  38. data/lib/alexandria/book_providers/pseudomarc.rb +185 -0
  39. data/lib/alexandria/book_providers/renaud.rb +142 -0
  40. data/lib/alexandria/book_providers/siciliano.rb +271 -0
  41. data/lib/alexandria/book_providers/thalia.rb +197 -0
  42. data/lib/alexandria/book_providers/web.rb +59 -0
  43. data/lib/alexandria/book_providers/webster_it.rb +173 -0
  44. data/lib/alexandria/book_providers/worldcat.rb +251 -0
  45. data/lib/alexandria/book_providers/z3950.rb +422 -0
  46. data/lib/alexandria/config.rb +8 -0
  47. data/lib/alexandria/console.rb +31 -0
  48. data/lib/alexandria/execution_queue.rb +96 -0
  49. data/lib/alexandria/export_library.rb +536 -0
  50. data/lib/alexandria/import_library.rb +316 -0
  51. data/lib/alexandria/import_library_csv.rb +270 -0
  52. data/lib/alexandria/logging.rb +159 -0
  53. data/lib/alexandria/models/book.rb +72 -0
  54. data/lib/alexandria/models/library.rb +714 -0
  55. data/lib/alexandria/net.rb +53 -0
  56. data/lib/alexandria/preferences.rb +324 -0
  57. data/lib/alexandria/scanners.rb +42 -0
  58. data/lib/alexandria/scanners/cuecat.rb +118 -0
  59. data/lib/alexandria/scanners/keyboard.rb +57 -0
  60. data/lib/alexandria/smart_library.rb +525 -0
  61. data/lib/alexandria/ui.rb +53 -0
  62. data/lib/alexandria/ui/builder_base.rb +43 -0
  63. data/lib/alexandria/ui/callbacks.rb +389 -0
  64. data/lib/alexandria/ui/completion_models.rb +228 -0
  65. data/lib/alexandria/ui/dialogs/about_dialog.rb +59 -0
  66. data/lib/alexandria/ui/dialogs/acquire_dialog.rb +640 -0
  67. data/lib/alexandria/ui/dialogs/alert_dialog.rb +68 -0
  68. data/lib/alexandria/ui/dialogs/bad_isbns_dialog.rb +43 -0
  69. data/lib/alexandria/ui/dialogs/barcode_animation.rb +159 -0
  70. data/lib/alexandria/ui/dialogs/book_properties_dialog.rb +210 -0
  71. data/lib/alexandria/ui/dialogs/book_properties_dialog_base.rb +432 -0
  72. data/lib/alexandria/ui/dialogs/export_dialog.rb +172 -0
  73. data/lib/alexandria/ui/dialogs/import_dialog.rb +205 -0
  74. data/lib/alexandria/ui/dialogs/misc_dialogs.rb +85 -0
  75. data/lib/alexandria/ui/dialogs/new_book_dialog.rb +639 -0
  76. data/lib/alexandria/ui/dialogs/new_book_dialog_manual.rb +153 -0
  77. data/lib/alexandria/ui/dialogs/new_smart_library_dialog.rb +67 -0
  78. data/lib/alexandria/ui/dialogs/preferences_dialog.rb +587 -0
  79. data/lib/alexandria/ui/dialogs/smart_library_properties_dialog.rb +56 -0
  80. data/lib/alexandria/ui/dialogs/smart_library_properties_dialog_base.rb +432 -0
  81. data/lib/alexandria/ui/dndable.rb +81 -0
  82. data/lib/alexandria/ui/gtk_thread_help.rb +88 -0
  83. data/lib/alexandria/ui/icons.rb +100 -0
  84. data/lib/alexandria/ui/iconview.rb +91 -0
  85. data/lib/alexandria/ui/iconview_tooltips.rb +162 -0
  86. data/lib/alexandria/ui/init.rb +93 -0
  87. data/lib/alexandria/ui/libraries_combo.rb +75 -0
  88. data/lib/alexandria/ui/listview.rb +310 -0
  89. data/lib/alexandria/ui/main_app.rb +67 -0
  90. data/lib/alexandria/ui/multi_drag_treeview.rb +150 -0
  91. data/lib/alexandria/ui/sidepane.rb +194 -0
  92. data/lib/alexandria/ui/sound.rb +107 -0
  93. data/lib/alexandria/ui/ui_manager.rb +1308 -0
  94. data/lib/alexandria/undo_manager.rb +78 -0
  95. data/lib/alexandria/utils.rb +30 -0
  96. data/lib/alexandria/version.rb +24 -0
  97. data/lib/alexandria/web_themes.rb +75 -0
  98. data/misc/sounds/README +15 -0
  99. data/misc/sounds/bad_scan.csd +62 -0
  100. data/misc/sounds/good_scan.csd +61 -0
  101. data/misc/sounds/scanning.csd +46 -0
  102. data/po/ChangeLog +488 -0
  103. data/po/Makefile +44 -0
  104. data/po/README +29 -0
  105. data/po/commit-po +72 -0
  106. data/po/cs.po +1437 -0
  107. data/po/cy.po +1521 -0
  108. data/po/de.po +1400 -0
  109. data/po/el.po +1379 -0
  110. data/po/es.po +1376 -0
  111. data/po/fr.po +1420 -0
  112. data/po/ga.po +1359 -0
  113. data/po/gl.po +1397 -0
  114. data/po/it.po +1406 -0
  115. data/po/ja.po +1355 -0
  116. data/po/mk.po +1373 -0
  117. data/po/nb.po +1386 -0
  118. data/po/nl.po +1405 -0
  119. data/po/pl.po +1373 -0
  120. data/po/pt.po +1398 -0
  121. data/po/pt_BR.po +1409 -0
  122. data/po/ru.po +1372 -0
  123. data/po/sk.po +1380 -0
  124. data/po/sv.po +1402 -0
  125. data/po/uk.po +1423 -0
  126. data/po/zh_TW.po +1394 -0
  127. data/schemas/alexandria.schemas +300 -0
  128. data/share/alexandria/glade/acquire_dialog__builder.glade +201 -0
  129. data/share/alexandria/glade/book_properties_dialog__builder.glade +910 -0
  130. data/share/alexandria/glade/main_app__builder.glade +229 -0
  131. data/share/alexandria/glade/new_book_dialog__builder.glade +379 -0
  132. data/share/alexandria/glade/preferences_dialog__builder.glade +733 -0
  133. data/share/alexandria/icons/alexandria.png +0 -0
  134. data/share/alexandria/icons/alexandria_small.png +0 -0
  135. data/share/alexandria/icons/book.png +0 -0
  136. data/share/alexandria/icons/book_icon.png +0 -0
  137. data/share/alexandria/icons/book_small.png +0 -0
  138. data/share/alexandria/icons/cuecat.png +0 -0
  139. data/share/alexandria/icons/cuecat_inactive.png +0 -0
  140. data/share/alexandria/icons/favorite_tag.png +0 -0
  141. data/share/alexandria/icons/less.png +0 -0
  142. data/share/alexandria/icons/library.png +0 -0
  143. data/share/alexandria/icons/library_small.png +0 -0
  144. data/share/alexandria/icons/lookup.png +0 -0
  145. data/share/alexandria/icons/more.png +0 -0
  146. data/share/alexandria/icons/no_cover.png +0 -0
  147. data/share/alexandria/icons/smart_library.png +0 -0
  148. data/share/alexandria/icons/smart_library_small.png +0 -0
  149. data/share/alexandria/icons/star_set.png +0 -0
  150. data/share/alexandria/icons/star_unset.png +0 -0
  151. data/share/alexandria/icons/view_as_icons.png +0 -0
  152. data/share/alexandria/icons/view_as_list.png +0 -0
  153. data/share/alexandria/ui/menus.xml +91 -0
  154. data/share/alexandria/ui/popups.xml +91 -0
  155. data/share/alexandria/web-themes/clean/clean.css +85 -0
  156. data/share/alexandria/web-themes/clean/preview.jpg +0 -0
  157. data/share/alexandria/web-themes/list/list.css +105 -0
  158. data/share/alexandria/web-themes/list/preview.jpg +0 -0
  159. data/share/app-icon/16x16/alexandria.png +0 -0
  160. data/share/app-icon/16x16/alexandria.svg +263 -0
  161. data/share/app-icon/22x22/alexandria.png +0 -0
  162. data/share/app-icon/22x22/alexandria.svg +465 -0
  163. data/share/app-icon/24x24/alexandria.png +0 -0
  164. data/share/app-icon/32x32/alexandria.png +0 -0
  165. data/share/app-icon/32x32/alexandria.svg +813 -0
  166. data/share/app-icon/32x32/alexandria.xpm +241 -0
  167. data/share/app-icon/48x48/alexandria.png +0 -0
  168. data/share/app-icon/scalable/alexandria.svg +700 -0
  169. data/share/gnome/help/alexandria/C/about.xml +44 -0
  170. data/share/gnome/help/alexandria/C/adding-books.xml +339 -0
  171. data/share/gnome/help/alexandria/C/alexandria.xml +185 -0
  172. data/share/gnome/help/alexandria/C/bugs.xml +18 -0
  173. data/share/gnome/help/alexandria/C/editing-book-properties.xml +124 -0
  174. data/share/gnome/help/alexandria/C/exporting.xml +81 -0
  175. data/share/gnome/help/alexandria/C/figures/adding_books_acquire_from_scanner_process.png +0 -0
  176. data/share/gnome/help/alexandria/C/figures/adding_books_add_by_isbn.png +0 -0
  177. data/share/gnome/help/alexandria/C/figures/adding_books_isbn_import.png +0 -0
  178. data/share/gnome/help/alexandria/C/figures/adding_books_manual_details.png +0 -0
  179. data/share/gnome/help/alexandria/C/figures/adding_books_rename_library_after_import.png +0 -0
  180. data/share/gnome/help/alexandria/C/figures/adding_books_search_results.png +0 -0
  181. data/share/gnome/help/alexandria/C/figures/editing_book_properties_info.png +0 -0
  182. data/share/gnome/help/alexandria/C/figures/editing_book_properties_loaning.png +0 -0
  183. data/share/gnome/help/alexandria/C/figures/exporting_information_html.png +0 -0
  184. data/share/gnome/help/alexandria/C/figures/getting_started_first_launched.png +0 -0
  185. data/share/gnome/help/alexandria/C/figures/searching_filtering_views_list_view.png +0 -0
  186. data/share/gnome/help/alexandria/C/figures/searching_filtering_views_list_view_search.png +0 -0
  187. data/share/gnome/help/alexandria/C/figures/settings_providers_new_z3950.png +0 -0
  188. data/share/gnome/help/alexandria/C/figures/smart_libraries_new_smart_library.png +0 -0
  189. data/share/gnome/help/alexandria/C/figures/working_with_libraries_library_pane.png +0 -0
  190. data/share/gnome/help/alexandria/C/getting-started.xml +154 -0
  191. data/share/gnome/help/alexandria/C/gnu-fdl-1.2.xml +543 -0
  192. data/share/gnome/help/alexandria/C/introduction.xml +142 -0
  193. data/share/gnome/help/alexandria/C/searching.xml +90 -0
  194. data/share/gnome/help/alexandria/C/settings.xml +140 -0
  195. data/share/gnome/help/alexandria/C/smart-libraries.xml +160 -0
  196. data/share/gnome/help/alexandria/C/working-with-libraries.xml +76 -0
  197. data/share/gnome/help/alexandria/ChangeLog +99 -0
  198. data/share/gnome/help/alexandria/fr/alexandria.xml +2292 -0
  199. data/share/gnome/help/alexandria/fr/figures/alexandria_add_button.png +0 -0
  200. data/share/gnome/help/alexandria/fr/figures/alexandria_add_by_isbn_1.png +0 -0
  201. data/share/gnome/help/alexandria/fr/figures/alexandria_add_by_search_1.png +0 -0
  202. data/share/gnome/help/alexandria/fr/figures/alexandria_add_manually.png +0 -0
  203. data/share/gnome/help/alexandria/fr/figures/alexandria_add_z3950.png +0 -0
  204. data/share/gnome/help/alexandria/fr/figures/alexandria_close_button.png +0 -0
  205. data/share/gnome/help/alexandria/fr/figures/alexandria_edit_info.png +0 -0
  206. data/share/gnome/help/alexandria/fr/figures/alexandria_export_web_page.png +0 -0
  207. data/share/gnome/help/alexandria/fr/figures/alexandria_importing.png +0 -0
  208. data/share/gnome/help/alexandria/fr/figures/alexandria_library_pane.png +0 -0
  209. data/share/gnome/help/alexandria/fr/figures/alexandria_list_view.png +0 -0
  210. data/share/gnome/help/alexandria/fr/figures/alexandria_list_view_search.png +0 -0
  211. data/share/gnome/help/alexandria/fr/figures/alexandria_loaning.png +0 -0
  212. data/share/gnome/help/alexandria/fr/figures/alexandria_main_window.png +0 -0
  213. data/share/gnome/help/alexandria/fr/figures/alexandria_remove_button.png +0 -0
  214. data/share/gnome/help/alexandria/ja/about.xml +33 -0
  215. data/share/gnome/help/alexandria/ja/adding-books.xml +314 -0
  216. data/share/gnome/help/alexandria/ja/alexandria.xml +172 -0
  217. data/share/gnome/help/alexandria/ja/bugs.xml +11 -0
  218. data/share/gnome/help/alexandria/ja/editing-book-properties.xml +100 -0
  219. data/share/gnome/help/alexandria/ja/exporting.xml +98 -0
  220. data/share/gnome/help/alexandria/ja/figures/adding_books_acquire_from_scanner_process.png +0 -0
  221. data/share/gnome/help/alexandria/ja/figures/adding_books_add_by_isbn.png +0 -0
  222. data/share/gnome/help/alexandria/ja/figures/adding_books_isbn_import.png +0 -0
  223. data/share/gnome/help/alexandria/ja/figures/adding_books_manual_details.png +0 -0
  224. data/share/gnome/help/alexandria/ja/figures/adding_books_rename_library_after_import.png +0 -0
  225. data/share/gnome/help/alexandria/ja/figures/adding_books_search_results.png +0 -0
  226. data/share/gnome/help/alexandria/ja/figures/editing_book_properties_info.png +0 -0
  227. data/share/gnome/help/alexandria/ja/figures/editing_book_properties_loaning.png +0 -0
  228. data/share/gnome/help/alexandria/ja/figures/exporting_information_html.png +0 -0
  229. data/share/gnome/help/alexandria/ja/figures/getting_started_first_launched.png +0 -0
  230. data/share/gnome/help/alexandria/ja/figures/searching_filtering_views_list_view.png +0 -0
  231. data/share/gnome/help/alexandria/ja/figures/searching_filtering_views_list_view_search.png +0 -0
  232. data/share/gnome/help/alexandria/ja/figures/settings_providers_new_z3950.png +0 -0
  233. data/share/gnome/help/alexandria/ja/figures/smart_libraries_new_smart_library.png +0 -0
  234. data/share/gnome/help/alexandria/ja/figures/working_with_libraries_library_pane.png +0 -0
  235. data/share/gnome/help/alexandria/ja/getting-started.xml +144 -0
  236. data/share/gnome/help/alexandria/ja/gnu-fdl-1.2.xml +541 -0
  237. data/share/gnome/help/alexandria/ja/introduction.xml +134 -0
  238. data/share/gnome/help/alexandria/ja/searching.xml +104 -0
  239. data/share/gnome/help/alexandria/ja/settings.xml +129 -0
  240. data/share/gnome/help/alexandria/ja/smart-libraries.xml +140 -0
  241. data/share/gnome/help/alexandria/ja/working-with-libraries.xml +88 -0
  242. data/share/menu/alexandria +7 -0
  243. data/share/omf/alexandria/alexandria-C.omf.in +27 -0
  244. data/share/omf/alexandria/alexandria-fr.omf.in +30 -0
  245. data/share/sounds/alexandria/bad_scan.ogg +0 -0
  246. data/share/sounds/alexandria/bad_scan.wav +0 -0
  247. data/share/sounds/alexandria/good_scan.ogg +0 -0
  248. data/share/sounds/alexandria/good_scan.wav +0 -0
  249. data/share/sounds/alexandria/scanning.ogg +0 -0
  250. data/share/sounds/alexandria/scanning.wav +0 -0
  251. data/spec/alexandria/library_spec.rb +205 -0
  252. data/spec/alexandria/preferences_spec.rb +22 -0
  253. data/spec/alexandria/scanners/cuecat_spec.rb +68 -0
  254. data/spec/alexandria/smart_library_spec.rb +22 -0
  255. data/spec/alexandria/ui/dialogs_spec.rb +90 -0
  256. data/spec/alexandria/ui/iconview_spec.rb +27 -0
  257. data/spec/alexandria/ui/listview_spec.rb +28 -0
  258. data/spec/alexandria/ui/main_app_spec.rb +48 -0
  259. data/spec/alexandria/ui/sidepane_spec.rb +27 -0
  260. data/spec/alexandria/ui/ui_manager_spec.rb +26 -0
  261. data/spec/alexandria/ui/ui_utilities_spec.rb +60 -0
  262. data/spec/alexandria/utilities_spec.rb +50 -0
  263. data/spec/data/libraries/0.6.1-noisbn/My Library/0201398257.yaml +10 -0
  264. data/spec/data/libraries/0.6.1-noisbn/My Library/1565920007.yaml +10 -0
  265. data/spec/data/libraries/0.6.1/My Library/0192812173.yaml +9 -0
  266. data/spec/data/libraries/0.6.1/My Library/0201398257.cover +0 -0
  267. data/spec/data/libraries/0.6.1/My Library/0201398257.yaml +10 -0
  268. data/spec/data/libraries/0.6.1/My Library/1565920007.yaml +10 -0
  269. data/spec/data/libraries/0.6.2/My Library/9780140266146.cover +0 -0
  270. data/spec/data/libraries/0.6.2/My Library/9780140266146.yaml +16 -0
  271. data/spec/data/libraries/0.6.2/My Library/9780140278781.cover +0 -0
  272. data/spec/data/libraries/0.6.2/My Library/9780140278781.yaml +21 -0
  273. data/spec/data/libraries/0.6.2/My Library/9780571147168.cover +0 -0
  274. data/spec/data/libraries/0.6.2/My Library/9780571147168.yaml +20 -0
  275. data/spec/data/libraries/0.6.2/My Library/9780575079038.cover +0 -0
  276. data/spec/data/libraries/0.6.2/My Library/9780575079038.yaml +20 -0
  277. data/spec/data/libraries/0.6.2/My Library/9780755322800.cover +0 -0
  278. data/spec/data/libraries/0.6.2/My Library/9780755322800.yaml +20 -0
  279. data/spec/spec_helper.rb +46 -0
  280. data/tasks/rdoc.rake +6 -0
  281. data/tasks/setup.rb +30 -0
  282. data/tasks/spec.rake +29 -0
  283. data/tasks/test.rake +38 -0
  284. data/test/application_test.rb +39 -0
  285. data/test/book_test.rb +34 -0
  286. data/test/data/isbns.txt +3 -0
  287. data/test/isbn_test.rb +68 -0
  288. data/test/providers_test.rb +254 -0
  289. data/test/test_helper.rb +42 -0
  290. data/util/rake/fileinstall.rb +313 -0
  291. data/util/rake/gettextgenerate.rb +158 -0
  292. data/util/rake/omfgenerate.rb +79 -0
  293. metadata +452 -0
@@ -0,0 +1,205 @@
1
+ # Copyright (C) 2004-2006 Laurent Sansonetti
2
+ # Modifications Copyright (C) 2011 Matijs van Zuijlen
3
+ #
4
+ # Alexandria is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License as
6
+ # published by the Free Software Foundation; either version 2 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # Alexandria is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public
15
+ # License along with Alexandria; see the file COPYING. If not,
16
+ # write to the Free Software Foundation, Inc., 51 Franklin Street,
17
+ # Fifth Floor, Boston, MA 02110-1301 USA.
18
+
19
+ require 'thread'
20
+
21
+ class Alexandria::ImportFilter
22
+ def to_filefilter
23
+ filefilter = Gtk::FileFilter.new
24
+ filefilter.name = name
25
+ patterns.each { |x| filefilter.add_pattern(x) }
26
+ filefilter
27
+ end
28
+ end
29
+
30
+ module Alexandria
31
+ module UI
32
+ class SkipEntryDialog < AlertDialog
33
+ include GetText
34
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
35
+
36
+ def initialize(parent, message)
37
+ super(parent, _('Error while importing'),
38
+ Gtk::Stock::DIALOG_QUESTION,
39
+ [[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
40
+ [_('_Continue'), Gtk::Dialog::RESPONSE_OK]],
41
+ message)
42
+ puts "Opened SkipEntryDialog #{inspect}" if $DEBUG
43
+ self.default_response = Gtk::Dialog::RESPONSE_CANCEL
44
+ show_all and @response = run
45
+ destroy
46
+ end
47
+
48
+ def continue?
49
+ @response == Gtk::Dialog::RESPONSE_OK
50
+ end
51
+ end
52
+
53
+ class ImportDialog < Gtk::FileChooserDialog
54
+ include GetText
55
+ include Logging
56
+
57
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
58
+
59
+ FILTERS = Alexandria::ImportFilter.all
60
+
61
+ def initialize(parent, &on_accept_cb)
62
+ super()
63
+ puts 'ImportDialog opened.' if $DEBUG
64
+ @destroyed = false
65
+ self.title = _('Import a Library')
66
+ self.action = Gtk::FileChooser::ACTION_OPEN
67
+ self.transient_for = parent
68
+ # self.deletable = false
69
+ running = false
70
+ add_button(Gtk::Stock::HELP, Gtk::Dialog::RESPONSE_HELP)
71
+ add_button(Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL)
72
+ import_button = add_button(_('_Import'),
73
+ Gtk::Dialog::RESPONSE_ACCEPT)
74
+ import_button.sensitive = false
75
+
76
+ signal_connect('destroy') {
77
+ if running
78
+ @destroyed = true
79
+
80
+ else
81
+ destroy
82
+ end
83
+ # self.destroy unless running
84
+ }
85
+
86
+ filters = {}
87
+ FILTERS.each do |filter|
88
+ filefilter = filter.to_filefilter
89
+ add_filter(filefilter)
90
+ puts "Added ImportFilter #{filefilter} -- #{filefilter.name}" if $DEBUG
91
+ filters[filefilter] = filter
92
+ end
93
+
94
+ signal_connect('selection_changed') do
95
+ import_button.sensitive =
96
+ filename and File.file?(filename)
97
+ end
98
+
99
+ # before adding the (hidden) progress bar, we must re-set the
100
+ # packing of the button box (currently packed at the end),
101
+ # because the progressbar will be *after* the button box.
102
+ buttonbox = vbox.children.last
103
+ options = vbox.query_child_packing(buttonbox)
104
+ options[-1] = Gtk::PACK_START
105
+ vbox.set_child_packing(buttonbox, *options)
106
+ vbox.reorder_child(buttonbox, 1)
107
+
108
+ pbar = Gtk::ProgressBar.new
109
+ pbar.show_text = true
110
+ vbox.pack_start(pbar, false)
111
+
112
+ on_progress = proc do |fraction|
113
+ begin
114
+ pbar.show unless pbar.visible?
115
+ pbar.fraction = fraction
116
+ rescue
117
+ # TODO check if destroyed instead...
118
+ end
119
+ end
120
+
121
+ on_error = proc do |message|
122
+ SkipEntryDialog.new(parent, message).continue?
123
+ end
124
+
125
+ exec_queue = ExecutionQueue.new
126
+
127
+ while !@destroyed and
128
+ (response = run) != Gtk::Dialog::RESPONSE_CANCEL and
129
+ response != Gtk::Dialog::RESPONSE_DELETE_EVENT
130
+
131
+ if response == Gtk::Dialog::RESPONSE_HELP
132
+ Alexandria::UI.display_help(self, 'import-library')
133
+ next
134
+ end
135
+ file = File.basename(filename, '.*')
136
+ base = GLib.locale_to_utf8(file)
137
+ new_library_name = Library.generate_new_name(
138
+ Libraries.instance.all_libraries,
139
+ base)
140
+
141
+ filter = filters[self.filter]
142
+ puts "Going forward with filter: #{filter.name}" if $DEBUG
143
+ self.sensitive = false
144
+
145
+ filter.on_iterate do |n, total|
146
+ unless @destroyed
147
+ # convert to percents
148
+ coeff = total / 100.0
149
+ percent = n / coeff
150
+ # fraction between 0 and 1
151
+ fraction = percent / 100
152
+ puts "#{inspect} Percentage: #{fraction}" if $DEBUG
153
+ exec_queue.call(on_progress, fraction)
154
+ end
155
+ end
156
+
157
+ not_cancelled = true
158
+ filter.on_error do |message|
159
+ not_cancelled = exec_queue.sync_call(on_error, message)
160
+ puts "#{inspect} cancel state: #{not_cancelled}" if $DEBUG
161
+ end
162
+
163
+ library = nil
164
+ @bad_isbns = nil
165
+ @failed_isbns = nil
166
+ thread = Thread.start do
167
+ begin
168
+ library, @bad_isbns, @failed_isbns = filter.invoke(new_library_name,
169
+ filename)
170
+ rescue => ex
171
+ trace = ex.backtrace.join("\n> ")
172
+ log.error { "Import failed: #{ex.message} #{trace}" }
173
+ end
174
+ end
175
+
176
+ while thread.alive? and !@destroyed
177
+ # puts "Thread #{thread} still alive."
178
+ running = true
179
+ exec_queue.iterate
180
+ Gtk.main_iteration_do(false)
181
+ end
182
+
183
+ unless @destroyed
184
+ if library
185
+ on_accept_cb.call(library, @bad_isbns, @failed_isbns)
186
+ break
187
+ elsif not_cancelled
188
+ puts "Raising ErrorDialog because not_cancelled is #{not_cancelled}" if $DEBUG
189
+ ErrorDialog.new(parent,
190
+ _("Couldn't import the library"),
191
+ _('The format of the file you ' \
192
+ 'provided is unknown. Please ' \
193
+ 'retry with another file.'))
194
+ end
195
+ pbar.hide
196
+ self.sensitive = true
197
+ end
198
+ end
199
+ unless @destroyed
200
+ destroy
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,85 @@
1
+ # Copyright (C) 2007 Joseph Method
2
+ #
3
+ # Alexandria is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU General Public License as
5
+ # published by the Free Software Foundation; either version 2 of the
6
+ # License, or (at your option) any later version.
7
+ #
8
+ # Alexandria is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public
14
+ # License along with Alexandria; see the file COPYING. If not,
15
+ # write to the Free Software Foundation, Inc., 51 Franklin Street,
16
+ # Fifth Floor, Boston, MA 02110-1301 USA.
17
+
18
+ module Alexandria
19
+ module UI
20
+ class ConflictWhileCopyingDialog < AlertDialog
21
+ include GetText
22
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
23
+
24
+ def initialize(parent, library, book)
25
+ super(parent,
26
+ _("The book '%s' already exists in '%s'. Would you like " \
27
+ 'to replace it?') % [book.title, library.name],
28
+ Gtk::Stock::DIALOG_QUESTION,
29
+ [[_('_Skip'), Gtk::Dialog::RESPONSE_CANCEL],
30
+ [_('_Replace'), Gtk::Dialog::RESPONSE_OK]],
31
+ _('If you replace the existing book, its contents will ' \
32
+ 'be overwritten.'))
33
+ self.default_response = Gtk::Dialog::RESPONSE_CANCEL
34
+ show_all and @response = run
35
+ destroy
36
+ end
37
+
38
+ def replace?
39
+ @response == Gtk::Dialog::RESPONSE_OK
40
+ end
41
+ end
42
+
43
+ class ReallyDeleteDialog < AlertDialog
44
+ include GetText
45
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
46
+
47
+ def initialize(parent, library, books = nil)
48
+ # Deleting a library.
49
+ if books.nil?
50
+ message = _("Are you sure you want to delete '%s'?") % library.name
51
+ description = if library.is_a?(SmartLibrary) or library.empty?
52
+ nil
53
+ else
54
+ n_('If you continue, %d book will be deleted.',
55
+ 'If you continue, %d books will be deleted.',
56
+ library.size) % library.size
57
+ end
58
+ # Deleting books.
59
+ else
60
+ message = if books.length == 1
61
+ _("Are you sure you want to delete '%s' " \
62
+ "from '%s'?") % [books.first.title, library.name]
63
+ else
64
+ _('Are you sure you want to delete the ' \
65
+ "selected books from '%s'?") % library.name
66
+ end
67
+ description = nil
68
+ end
69
+
70
+ super(parent, message, Gtk::Stock::DIALOG_QUESTION,
71
+ [[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
72
+ [Gtk::Stock::DELETE, Gtk::Dialog::RESPONSE_OK]],
73
+ description)
74
+
75
+ self.default_response = Gtk::Dialog::RESPONSE_CANCEL
76
+ show_all and @response = run
77
+ destroy
78
+ end
79
+
80
+ def ok?
81
+ @response == Gtk::Dialog::RESPONSE_OK
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,639 @@
1
+ # Copyright (C) 2004-2006 Laurent Sansonetti
2
+ # Modifications Copyright (C) 2011 Matijs van Zuijlen
3
+ #
4
+ # Alexandria is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License as
6
+ # published by the Free Software Foundation; either version 2 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # Alexandria is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public
15
+ # License along with Alexandria; see the file COPYING. If not,
16
+ # write to the Free Software Foundation, Inc., 51 Franklin Street,
17
+ # Fifth Floor, Boston, MA 02110-1301 USA.
18
+
19
+ require 'gdk_pixbuf2'
20
+
21
+ module Alexandria
22
+ class DuplicateBookException < NameError
23
+ end
24
+
25
+ module UI
26
+ class KeepBadISBNDialog < AlertDialog
27
+ include GetText
28
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
29
+
30
+ def initialize(parent, book)
31
+ super(parent, _("Invalid ISBN '%s'") % book.isbn,
32
+ Gtk::Stock::DIALOG_QUESTION,
33
+ [[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
34
+ [_('_Keep'), Gtk::Dialog::RESPONSE_OK]],
35
+ _("The book titled '%s' has an invalid ISBN, but still " \
36
+ 'exists in the providers libraries. Do you want to ' \
37
+ 'keep the book but change the ISBN or cancel the add?') % book.title)
38
+ self.default_response = Gtk::Dialog::RESPONSE_OK
39
+ show_all and @response = run
40
+ destroy
41
+ end
42
+
43
+ def keep?
44
+ @response == Gtk::Dialog::RESPONSE_OK
45
+ end
46
+ end
47
+
48
+ class NewBookDialog < BuilderBase
49
+ include Logging
50
+ include GetText
51
+ extend GetText
52
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
53
+
54
+ def initialize(parent, selected_library = nil, &block)
55
+ super('new_book_dialog__builder.glade', widget_names)
56
+ log.info { 'New Book Dialog' }
57
+ @new_book_dialog.transient_for = @parent = parent
58
+ @block = block
59
+ @destroyed = false
60
+ @selected_library = selected_library
61
+ setup_dialog_gui
62
+ @progressbar.hide
63
+ end
64
+
65
+ def widget_names
66
+ [:liststore1, :liststore2, :new_book_dialog, :dialog_vbox1,
67
+ :dialog_action_area1, :button_help, :button_cancel,
68
+ :button_add, :table1, :keep_open, :combo_libraries,
69
+ :cellrenderertext1, :eventbox_entry_isbn, :entry_isbn,
70
+ :label3, :hbox2, :eventbox_combo_search, :combo_search,
71
+ :cellrenderertext2, :eventbox_entry_search, :entry_search,
72
+ :button_find, :scrolledwindow, :treeview_results,
73
+ :title_radiobutton, :isbn_radiobutton, :progressbar]
74
+ end
75
+
76
+ def setup_dialog_gui
77
+ libraries = Libraries.instance.all_regular_libraries
78
+ if @selected_library.is_a?(SmartLibrary)
79
+ @selected_library = libraries.first
80
+ end
81
+ @combo_libraries.populate_with_libraries(libraries,
82
+ @selected_library)
83
+
84
+ @treeview_results.model = Gtk::ListStore.new(String, String,
85
+ Gdk::Pixbuf)
86
+ @treeview_results.selection.mode = Gtk::SELECTION_MULTIPLE
87
+ @treeview_results.selection.signal_connect('changed') do
88
+ @button_add.sensitive = true
89
+ end
90
+
91
+ renderer = Gtk::CellRendererPixbuf.new
92
+ col = Gtk::TreeViewColumn.new('', renderer)
93
+ col.set_cell_data_func(renderer) do |_column, cell, _model, iter|
94
+ pixbuf = iter[2]
95
+ max_height = 25
96
+
97
+ if pixbuf.height > max_height
98
+ new_width = pixbuf.width * (max_height.to_f / pixbuf.height)
99
+ pixbuf = pixbuf.scale(new_width, max_height)
100
+ end
101
+
102
+ cell.pixbuf = pixbuf
103
+ end
104
+ @treeview_results.append_column(col)
105
+
106
+ col = Gtk::TreeViewColumn.new('', Gtk::CellRendererText.new,
107
+ text: 0)
108
+ @treeview_results.append_column(col)
109
+
110
+ @combo_search.active = 0
111
+
112
+ # Re-select the last selected criterion.
113
+ # TODO let's do this from a Gconf setting instead, maybe?
114
+ begin
115
+ @title_radiobutton.active = @@last_criterion_was_not_isbn
116
+ rescue NameError
117
+ log.debug { 'initialize @@last_criterion_was_not_isbn as false' }
118
+ @@last_criterion_was_not_isbn = false
119
+ end
120
+
121
+ if @@last_criterion_was_not_isbn
122
+ @entry_search.grab_focus
123
+ else
124
+ @entry_isbn.grab_focus
125
+ end
126
+
127
+ @find_thread = nil
128
+ @image_thread = nil
129
+
130
+ @new_book_dialog.signal_connect('destroy') {
131
+ @new_book_dialog.destroy
132
+ @destroyed = true
133
+ }
134
+ end
135
+
136
+ def on_criterion_toggled(item)
137
+ log.debug { 'on_criterion_toggled' }
138
+ return unless item.active?
139
+
140
+ # There used to be a strange effect here (pre SVN r1022).
141
+ # When item is first toggled to "Search" the entry_search
142
+ # field was unselectable. One used to have to click the dialog
143
+ # title bar to be able to focus it again. Putting the GUI
144
+ # modifications in an Gtk.idle_add block fixed the problem.
145
+
146
+ is_isbn = item == @isbn_radiobutton
147
+ if is_isbn
148
+ Gtk.idle_add do
149
+ @latest_size = @new_book_dialog.size
150
+ @new_book_dialog.resizable = false
151
+ @entry_isbn.grab_focus
152
+ false
153
+ end
154
+ else
155
+ Gtk.idle_add do
156
+ @new_book_dialog.resizable = true
157
+ @new_book_dialog.resize(*@latest_size) unless @latest_size.nil?
158
+ @entry_search.grab_focus
159
+ false
160
+ end
161
+ end
162
+ @entry_isbn.sensitive = is_isbn
163
+ @combo_search.sensitive = !is_isbn
164
+ @entry_search.sensitive = !is_isbn
165
+ @button_find.sensitive = !is_isbn
166
+ @scrolledwindow.visible = !is_isbn
167
+ on_changed(is_isbn ? @entry_isbn : @entry_search)
168
+ unless is_isbn
169
+ @button_add.sensitive =
170
+ @treeview_results.selection.count_selected_rows > 0
171
+ end
172
+
173
+ # Remember the last criterion selected (so that we can re-select
174
+ # it when the dialog opens again).
175
+ @@last_criterion_was_not_isbn = !is_isbn
176
+
177
+ # @new_book_dialog.present # attempted fix, bring dialog to foreground
178
+ end
179
+
180
+ def on_changed(entry)
181
+ ok = !entry.text.strip.empty?
182
+ decode_cuecat?(@entry_isbn) if entry == @entry_isbn
183
+ (entry == @entry_isbn ? @button_add : @button_find).sensitive = ok
184
+ end
185
+
186
+ def image_error_dialog(error)
187
+ ErrorDialog.new(
188
+ @parent,
189
+ _('A problem occurred while downloading images'),
190
+ error)
191
+ end
192
+
193
+ def get_images_async
194
+ log.info { 'get_images_async' }
195
+ @images = {}
196
+ @image_error = nil
197
+ @image_thread = Thread.new do
198
+ log.info { "New @image_thread #{Thread.current}" }
199
+ begin
200
+ @results.each_with_index do |result, i|
201
+ uri = result[1]
202
+ if uri
203
+ if URI.parse(uri).scheme.nil?
204
+ File.open(uri, 'r') do |io|
205
+ @images[i] = io.read
206
+ end
207
+ else
208
+ @images[i] = URI.parse(uri).read
209
+ end
210
+ end
211
+ end
212
+ rescue => e
213
+ @image_error = e.message
214
+ end
215
+ end
216
+
217
+ Gtk.timeout_add(100) do
218
+ if @image_error
219
+ image_error_dialog(@image_error)
220
+ else
221
+ @images.each_pair do |key, value|
222
+ begin
223
+ loader = Gdk::PixbufLoader.new
224
+ loader.last_write(value)
225
+ pixbuf = loader.pixbuf
226
+
227
+ if pixbuf.width > 1
228
+ iter = @treeview_results.model.get_iter(key.to_s)
229
+ unless @treeview_results.model.iter_is_valid?(iter)
230
+ raise 'Iter is invalid! %s' % iter
231
+ end
232
+ iter[2] = pixbuf # I bet you this is it!
233
+ end
234
+
235
+ @images.delete(key)
236
+ rescue => e
237
+ image_error_dialog(e.message)
238
+ end
239
+ end
240
+ end
241
+
242
+ # Stop if the image download thread has stopped.
243
+ if @image_thread.alive?
244
+ log.info { "@image_thread (#{@image_thread}) still alive." }
245
+ true
246
+ else
247
+ log.info { "@image_thread (#{@image_thread}) asleep now." }
248
+ false
249
+ end
250
+ end
251
+ end
252
+
253
+ def on_find
254
+ log.info { 'on_find' }
255
+ mode = case @combo_search.active
256
+ when 0
257
+ BookProviders::SEARCH_BY_TITLE
258
+ when 1
259
+ BookProviders::SEARCH_BY_AUTHORS
260
+ when 2
261
+ BookProviders::SEARCH_BY_KEYWORD
262
+ end
263
+
264
+ # @progressbar.show
265
+ # progress_pulsing = Gtk.timeout_add(100) do
266
+ # if @destroyed
267
+ # false
268
+ # else
269
+ # @progressbar.pulse
270
+ # true
271
+ # end
272
+ # end
273
+
274
+ criterion = @entry_search.text.strip
275
+ @treeview_results.model.clear
276
+ log.info { 'TreeStore Model: %s columns; ref_counts: %s' %
277
+ [@treeview_results.model.n_columns, @treeview_results.model.ref_count] }
278
+
279
+ @find_error = nil
280
+ @results = nil
281
+
282
+ @find_thread.kill if @find_thread
283
+ @image_thread.kill if @image_thread
284
+
285
+ notify_start_add_by_isbn
286
+ Gtk.idle_add do
287
+ @find_thread = Thread.new do
288
+ log.info { "New @find_thread #{Thread.current}" }
289
+ begin
290
+ Alexandria::BookProviders.instance.add_observer(self)
291
+ @results = Alexandria::BookProviders.search(criterion, mode)
292
+
293
+ log.info { "got #{@results.length} results" }
294
+ rescue => e
295
+ @find_error = e.message
296
+ ensure
297
+ Alexandria::BookProviders.instance.delete_observer(self)
298
+ # notify_end_add_by_isbn
299
+ end
300
+ end
301
+ false
302
+ end
303
+
304
+ Gtk.timeout_add(100) do
305
+ # This block copies results into the tree view, or shows an
306
+ # error if the search failed.
307
+
308
+ # Err... continue == false if @find_error
309
+ continue = if @find_error
310
+ ErrorDialog.new(@parent,
311
+ _('Unable to find matches for your search'),
312
+ @find_error)
313
+ false
314
+ elsif @results
315
+ log.info { "Got results: #{@results[0]}..." }
316
+ @results.each do |book, _cover|
317
+ s = _('%s, by %s') % [book.title,
318
+ book.authors.join(', ')]
319
+ similar_books = @results.find { |book2, _cover2|
320
+ book.title == book2.title and
321
+ book.authors == book2.authors
322
+ }
323
+ if similar_books.length > 1
324
+ s += " (#{book.edition}, #{book.publisher})"
325
+ end
326
+ log.info { 'Copying %s into tree view.' % book.title }
327
+ iter = @treeview_results.model.append
328
+ iter[0] = s
329
+ iter[1] = book.ident
330
+ iter[2] = Icons::BOOK
331
+ end
332
+
333
+ # Kick off the image download thread.
334
+ if @find_thread.alive?
335
+ log.info { "@find_thread (#{@find_thread}) still alive." }
336
+ true
337
+ else
338
+ log.info { "@find_thread (#{@find_thread}) asleep now." }
339
+ # Not really async now.
340
+ get_images_async
341
+ false # continue == false if you get to here. Stop timeout_add.
342
+ end
343
+ else
344
+ # Stop if the book find thread has stopped.
345
+ @find_thread.alive?
346
+ end
347
+ # continue == false if @find_error OR if results are returned
348
+ # timeout_add ends if continue is false!
349
+
350
+ unless continue
351
+ unless @find_thread.alive? # This happens after find_thread is done
352
+ unless @destroyed
353
+ # Gtk.timeout_remove(progress_pulsing)
354
+ # @progressbar.hide
355
+ notify_end_add_by_isbn
356
+ @button_add.sensitive = false
357
+ end
358
+ end
359
+ end
360
+
361
+ continue # timeout_add loop condition
362
+ end
363
+ end
364
+
365
+ def decode_cuecat?(entry) # srsly?
366
+ if entry.text =~ /^\..*?\..*?\.(.*?)\.$/
367
+ tmp = Regexp.last_match[1].tr('a-zA-Z0-9+-', ' -_')
368
+ tmp = ((32 + tmp.length * 3 / 4).to_i.chr << tmp).unpack('u')[0]
369
+ tmp.chomp!("\000")
370
+ entry.text = tmp.gsub!(/./) { |c| (c[0] ^ 67).chr }
371
+ if entry.text.count('^ -~') > 0
372
+ entry.text = 'Bad scan result'
373
+ end
374
+ end
375
+ end
376
+
377
+ def on_results_button_press_event(_widget, event)
378
+ # double left click
379
+ if event.event_type == Gdk::Event::BUTTON2_PRESS and
380
+ event.button == 1
381
+
382
+ on_add
383
+ end
384
+ end
385
+
386
+ def add_single_book_by_isbn(library, is_new)
387
+ # Perform the ISBN search via the providers.
388
+ isbn = begin
389
+ Library.canonicalise_isbn(@entry_isbn.text)
390
+ rescue
391
+ raise _("Couldn't validate the EAN/ISBN you " \
392
+ 'provided. Make sure it is written ' \
393
+ 'correctly, and try again.')
394
+ end
395
+ assert_not_exist(library, @entry_isbn.text)
396
+ @button_add.sensitive = false
397
+ notify_start_add_by_isbn
398
+ Gtk.idle_add do
399
+
400
+ @find_thread = Thread.new do
401
+ log.info { "New @find_thread #{Thread.current}" }
402
+ begin
403
+ # MAJOR HACK, add this again...
404
+ Alexandria::BookProviders.instance.add_observer(self)
405
+ book, cover_url = Alexandria::BookProviders.isbn_search(isbn)
406
+ # prov = FakeBookProviders.new()
407
+ # prov.add_observer(self)
408
+ # book, cover_url = prov.isbn_search(isbn)
409
+
410
+ notify_end_add_by_isbn
411
+
412
+ if book
413
+
414
+ puts "adding book #{book} to library"
415
+ add_book_to_library(library, book, cover_url)
416
+ @entry_isbn.text = ''
417
+
418
+ post_addition([book], library, is_new)
419
+ else
420
+ post_addition([], library, is_new)
421
+ end
422
+ rescue => e
423
+ unless e.is_a? Alexandria::BookProviders::NoResultsError
424
+ puts e.message
425
+ puts e.backtrace.join("\n> ")
426
+ end
427
+ @find_error = e.message
428
+ @button_add.sensitive = true
429
+ notify_end_add_by_isbn
430
+ ensure
431
+ Alexandria::BookProviders.instance.delete_observer(self)
432
+ notify_end_add_by_isbn
433
+ end
434
+ end
435
+
436
+ false
437
+ end
438
+ end
439
+
440
+ def add_selected_books(library, _is_new)
441
+ books_to_add = []
442
+ @treeview_results.selection.selected_each do |_model, _path, iter|
443
+ @results.each do |book, cover|
444
+ next unless book.ident == iter[1]
445
+ begin
446
+ next unless assert_not_exist(library, book.isbn)
447
+ rescue Alexandria::Library::NoISBNError
448
+ puts 'noisbn'
449
+ book.isbn = book.saved_ident = nil
450
+ books_to_add << [book, cover]
451
+ next
452
+ rescue Alexandria::Library::InvalidISBNError
453
+ puts "invalidisbn #{book.isbn}"
454
+ next unless
455
+ KeepBadISBNDialog.new(@parent, book).keep?
456
+ book.isbn = book.saved_ident = nil
457
+ end
458
+ books_to_add << [book, cover]
459
+ end
460
+
461
+ end
462
+ books_to_add.each do |book, cover_uri|
463
+ add_book_to_library(library, book, cover_uri)
464
+ end
465
+ books_to_add.map(&:first) # array of Books only
466
+ end
467
+
468
+ def add_book_to_library(library, book, cover_uri)
469
+ unless cover_uri.nil?
470
+ library.save_cover(book, cover_uri)
471
+ end
472
+ library << book
473
+ library.save(book)
474
+ end
475
+
476
+ def post_addition(books, library, is_new_library)
477
+ puts "post_addition #{books.size}"
478
+ return if books.empty?
479
+
480
+ # books, a 1d array of Alexandria::Book
481
+ @block.call(books, library, is_new_library)
482
+
483
+ if @keep_open.active?
484
+ # TODO reset and clear fields
485
+ if @@last_criterion_was_not_isbn
486
+ @entry_search.select_region(0, -1) # select all, ready to delete
487
+ @treeview_results.model.clear
488
+ @entry_search.grab_focus
489
+ else
490
+ @button_add.sensitive = true #
491
+ @entry_isbn.text = '' # blank ISBN field
492
+ @entry_isbn.grab_focus
493
+ end
494
+
495
+ else
496
+ # Now we can destroy the dialog and go back to the main
497
+ # application.
498
+ @new_book_dialog.destroy
499
+ end
500
+ end
501
+
502
+ def on_add
503
+ return unless @button_add.sensitive?
504
+ @find_thread.kill if @find_thread
505
+ @image_thread.kill if @image_thread
506
+
507
+ begin
508
+ libraries = Libraries.instance.all_libraries
509
+ library, is_new_library =
510
+ @combo_libraries.selection_from_libraries(libraries)
511
+
512
+ # book_was_added = false
513
+
514
+ if @isbn_radiobutton.active?
515
+ add_single_book_by_isbn(library, is_new_library)
516
+ else
517
+ books = add_selected_books(library, is_new_library)
518
+ post_addition(books, library, is_new_library)
519
+ end
520
+
521
+ # Do not destroy if there is no addition.
522
+ # return unless book_was_added
523
+
524
+ rescue => e
525
+ ErrorDialog.new(@parent, _("Couldn't add the book"), e.message)
526
+ end
527
+ # books_to_add
528
+ end
529
+
530
+ def on_cancel
531
+ @find_thread.kill if @find_thread
532
+ @image_thread.kill if @image_thread
533
+ @new_book_dialog.destroy
534
+ end
535
+
536
+ def notify_start_add_by_isbn
537
+ main_progress_bar = MainApp.instance.appbar.children.first
538
+ main_progress_bar.visible = true
539
+ @progress_pulsing = Gtk.timeout_add(100) do
540
+ if @destroyed
541
+ false
542
+ else
543
+ main_progress_bar.pulse
544
+ true
545
+ end
546
+ end
547
+ end
548
+
549
+ def notify_end_add_by_isbn
550
+ MainApp.instance.appbar.children.first.visible = false
551
+ Gtk.timeout_remove(@progress_pulsing)
552
+ end
553
+
554
+ def update(status, provider)
555
+ Gtk.queue do
556
+ messages = {
557
+ searching: _("Searching Provider '%s'..."),
558
+ error: _("Error while Searching Provider '%s'"),
559
+ not_found: _("Not Found at Provider '%s'"),
560
+ found: _("Found at Provider '%s'")
561
+ }
562
+ message = messages[status] % provider
563
+ log.debug { "update message : #{message}" }
564
+
565
+ # @parent.appbar.status = message
566
+ MainApp.instance.appbar.status = message # HACKish
567
+ # false
568
+ end
569
+ end
570
+
571
+ def on_focus
572
+ if @isbn_radiobutton.active? and @entry_isbn.text.strip.empty?
573
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
574
+ if (text = clipboard.wait_for_text)
575
+ if Library.valid_isbn?(text) or Library.valid_ean?(text) or
576
+ Library.valid_upc?(text)
577
+ Gtk.idle_add do
578
+
579
+ @entry_isbn.text = text
580
+ @entry_isbn.grab_focus
581
+ @entry_isbn.select_region(0, -1) # select all...
582
+ # @button_add.grab_focus
583
+ false
584
+ end
585
+ log.debug { "Setting ISBN field to #{text}" }
586
+ puts text # required, strangely, to prevent GUI strangeness
587
+ # above last checked with ruby-gnome2 0.17.1 2009-12-09
588
+ # if this puts is commented out, the cursor disappears
589
+ # from the @entry_isbn box... weird, ne? - CathalMagus
590
+ end
591
+ end
592
+ end
593
+ end
594
+
595
+ def on_clicked(widget, event)
596
+ if event.event_type == Gdk::Event::BUTTON_PRESS and
597
+ event.button == 1
598
+
599
+ radio, target_widget, box2, box3 = case widget
600
+ when @eventbox_entry_search
601
+ [@title_radiobutton, @entry_search,
602
+ @eventbox_combo_search, @eventbox_entry_isbn]
603
+
604
+ when @eventbox_combo_search
605
+ [@title_radiobutton, @combo_search,
606
+ @eventbox_entry_search, @eventbox_entry_isbn]
607
+
608
+ when @eventbox_entry_isbn
609
+ [@isbn_radiobutton, @entry_isbn,
610
+ @eventbox_entry_search, @eventbox_combo_search]
611
+ end
612
+ radio.active = true
613
+ target_widget.grab_focus
614
+ widget.above_child = false
615
+ box2.above_child = box3.above_child = true
616
+ end
617
+ end
618
+
619
+ def on_help
620
+ Alexandria::UI.display_help(@preferences_dialog, 'add-book-by-isbn')
621
+ end
622
+
623
+ #######
624
+ private
625
+ #######
626
+
627
+ def assert_not_exist(library, isbn)
628
+ # Check that the book doesn't already exist in the library.
629
+ isbn13 = Library.canonicalise_ean(isbn)
630
+ puts isbn13
631
+ if (book = library.find { |bk| bk.isbn == isbn13 })
632
+ raise DuplicateBookException, _("'%s' already exists in '%s' (titled '%s').") % \
633
+ [isbn, library.name, book.title.sub('&', '&amp;')]
634
+ end
635
+ true
636
+ end
637
+ end
638
+ end
639
+ end