alexandria-book-collection-manager 0.7.10 → 0.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +5 -1
- data/.github/workflows/ruby.yml +33 -19
- data/.rubocop.yml +12 -3
- data/.rubocop_todo.yml +30 -29
- data/CHANGELOG.md +64 -25
- data/Gemfile +0 -6
- data/Rakefile +2 -2
- data/alexandria-book-collection-manager.gemspec +20 -17
- data/bin/rake +28 -0
- data/bin/rspec +28 -0
- data/doc/dependency_decisions.yml +32 -26
- data/{bin → exe}/alexandria +1 -3
- data/lib/alexandria/about.rb +1 -0
- data/lib/alexandria/book_providers/bl_provider.rb +4 -6
- data/lib/alexandria/book_providers/{douban.rb → douban_provider.rb} +1 -1
- data/lib/alexandria/book_providers/loc_provider.rb +2 -6
- data/lib/alexandria/book_providers/sbn_provider.rb +2 -12
- data/lib/alexandria/book_providers/thalia_provider.rb +5 -6
- data/lib/alexandria/book_providers/{web.rb → website_based_provider.rb} +20 -1
- data/lib/alexandria/book_providers/{worldcat.rb → world_cat_provider.rb} +3 -4
- data/lib/alexandria/book_providers/z3950_provider.rb +24 -26
- data/lib/alexandria/book_providers.rb +14 -10
- data/lib/alexandria/config.rb +2 -2
- data/lib/alexandria/console.rb +12 -10
- data/lib/alexandria/export_format.rb +3 -2
- data/lib/alexandria/export_library.rb +31 -36
- data/lib/alexandria/import_library.rb +3 -4
- data/lib/alexandria/import_library_csv.rb +2 -2
- data/lib/alexandria/library_collection.rb +1 -1
- data/lib/alexandria/library_store.rb +19 -14
- data/lib/alexandria/logging.rb +22 -21
- data/lib/alexandria/models/book.rb +1 -2
- data/lib/alexandria/models/library.rb +5 -6
- data/lib/alexandria/preferences.rb +7 -19
- data/lib/alexandria/pseudo_marc_parser.rb +1 -1
- data/lib/alexandria/scanners/cue_cat.rb +5 -9
- data/lib/alexandria/scanners/{keyboard.rb → keyboard_wedge.rb} +3 -3
- data/lib/alexandria/scanners.rb +2 -2
- data/lib/alexandria/smart_library.rb +7 -3
- data/lib/alexandria/ui/acquire_dialog.rb +42 -45
- data/lib/alexandria/ui/alert_dialog.rb +1 -1
- data/lib/alexandria/ui/barcode_animation.rb +3 -3
- data/lib/alexandria/ui/book_properties_dialog.rb +9 -9
- data/lib/alexandria/ui/book_properties_dialog_base.rb +10 -11
- data/lib/alexandria/ui/builder_base.rb +1 -1
- data/lib/alexandria/ui/callbacks.rb +8 -7
- data/lib/alexandria/ui/confirm_erase_dialog.rb +1 -0
- data/lib/alexandria/ui/conflict_while_copying_dialog.rb +1 -0
- data/lib/alexandria/ui/export_dialog.rb +1 -0
- data/lib/alexandria/ui/{iconview.rb → icon_view_manager.rb} +1 -0
- data/lib/alexandria/ui/iconview_tooltips.rb +1 -1
- data/lib/alexandria/ui/keep_bad_isbn_dialog.rb +1 -0
- data/lib/alexandria/ui/libraries_combo.rb +1 -0
- data/lib/alexandria/ui/listview.rb +2 -0
- data/lib/alexandria/ui/main_app.rb +3 -1
- data/lib/alexandria/ui/multi_drag_treeview.rb +0 -2
- data/lib/alexandria/ui/new_book_dialog.rb +15 -20
- data/lib/alexandria/ui/new_book_dialog_manual.rb +7 -7
- data/lib/alexandria/ui/new_provider_dialog.rb +1 -0
- data/lib/alexandria/ui/new_smart_library_dialog.rb +2 -1
- data/lib/alexandria/ui/preferences_dialog.rb +4 -4
- data/lib/alexandria/ui/provider_preferences_dialog.rb +1 -0
- data/lib/alexandria/ui/really_delete_dialog.rb +1 -0
- data/lib/alexandria/ui/sidepane_manager.rb +49 -48
- data/lib/alexandria/ui/skip_entry_dialog.rb +1 -0
- data/lib/alexandria/ui/smart_library_properties_dialog.rb +1 -0
- data/lib/alexandria/ui/smart_library_properties_dialog_base.rb +2 -1
- data/lib/alexandria/ui/{sound.rb → sound_effects_player.rb} +3 -0
- data/lib/alexandria/ui/ui_manager.rb +192 -141
- data/lib/alexandria/ui.rb +1 -0
- data/lib/alexandria/version.rb +1 -1
- data/po/Makefile +1 -1
- data/po/it.po +64 -82
- data/spec/alexandria/book_providers/bl_provider_spec.rb +10 -1
- data/spec/alexandria/book_providers/douban_provider_spec.rb +17 -0
- data/spec/alexandria/book_providers/loc_provider_spec.rb +8 -0
- data/spec/alexandria/book_providers/sbn_provider_spec.rb +9 -1
- data/spec/alexandria/book_providers/thalia_provider_spec.rb +8 -0
- data/spec/alexandria/book_providers/world_cat_provider_spec.rb +8 -0
- data/spec/alexandria/book_providers/z3950_provider_spec.rb +22 -0
- data/spec/alexandria/console_spec.rb +1 -1
- data/spec/alexandria/export_library_spec.rb +57 -11
- data/spec/alexandria/library_collection_spec.rb +24 -0
- data/spec/alexandria/library_spec.rb +2 -1
- data/spec/alexandria/library_store_spec.rb +32 -0
- data/spec/alexandria/scanners/keyboard_wedge_spec.rb +47 -0
- data/spec/alexandria/ui/about_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/acquire_dialog_spec.rb +8 -3
- data/spec/alexandria/ui/alert_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/bad_isbns_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/book_properties_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/confirm_erase_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/conflict_while_copying_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/error_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/export_dialog_spec.rb +3 -3
- data/spec/alexandria/ui/{iconview_spec.rb → icon_view_manager_spec.rb} +1 -1
- data/spec/alexandria/ui/keep_bad_isbn_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/main_app_spec.rb +0 -2
- data/spec/alexandria/ui/new_book_dialog_manual_spec.rb +1 -1
- data/spec/alexandria/ui/new_book_dialog_spec.rb +6 -3
- data/spec/alexandria/ui/preferences_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/provider_preferences_dialog_spec.rb +22 -7
- data/spec/alexandria/ui/really_delete_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/sidepane_manager_spec.rb +1 -1
- data/spec/alexandria/ui/skip_entry_dialog_spec.rb +1 -1
- data/spec/alexandria/ui/ui_manager_spec.rb +2 -2
- data/spec/end_to_end/basic_run_spec.rb +2 -1
- data/spec/spec_helper.rb +4 -2
- data/tasks/setup.rb +1 -1
- data/util/rake/fileinstall.rb +5 -6
- data/util/rake/omfgenerate.rb +1 -1
- metadata +93 -61
- /data/spec/alexandria/ui/{sound_spec.rb → sound_effects_player_spec.rb} +0 -0
|
@@ -11,6 +11,7 @@ module Alexandria
|
|
|
11
11
|
class SidepaneManager
|
|
12
12
|
include Logging
|
|
13
13
|
include GetText
|
|
14
|
+
|
|
14
15
|
attr_accessor :library_listview
|
|
15
16
|
|
|
16
17
|
def initialize(library_listview, parent)
|
|
@@ -50,7 +51,7 @@ module Alexandria
|
|
|
50
51
|
|
|
51
52
|
if (match = contains_illegal_character(new_text))
|
|
52
53
|
if match.instance_of? MatchData
|
|
53
|
-
chars = match[1].gsub(
|
|
54
|
+
chars = match[1].gsub("&", "&")
|
|
54
55
|
ErrorDialog.new(@main_app, _("Invalid library name '%s'") % new_text,
|
|
55
56
|
_("The name provided contains the " \
|
|
56
57
|
"disallowed character <b>%s</b>") % chars).display
|
|
@@ -128,71 +129,71 @@ module Alexandria
|
|
|
128
129
|
|
|
129
130
|
@library_listview
|
|
130
131
|
.signal_connect("drag-motion") do |_widget, drag_context, x, y, time, _data|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
path = nil
|
|
140
|
-
else
|
|
141
|
-
iter = @library_listview.model.get_iter(path)
|
|
142
|
-
if iter[3] # separator?
|
|
132
|
+
log.debug { "drag-motion" }
|
|
133
|
+
|
|
134
|
+
path, column, =
|
|
135
|
+
@library_listview.get_path_at_pos(x, y)
|
|
136
|
+
|
|
137
|
+
if path
|
|
138
|
+
# Refuse drags from/to smart libraries.
|
|
139
|
+
if @parent.selected_library.is_a?(SmartLibrary)
|
|
143
140
|
path = nil
|
|
144
141
|
else
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
iter = @library_listview.model.get_iter(path)
|
|
143
|
+
if iter[3] # separator?
|
|
144
|
+
path = nil
|
|
145
|
+
else
|
|
146
|
+
library = @libraries.all_libraries.find do |lib|
|
|
147
|
+
lib.name == iter[1]
|
|
148
|
+
end
|
|
149
|
+
path = nil if library.is_a?(SmartLibrary)
|
|
147
150
|
end
|
|
148
|
-
path = nil if library.is_a?(SmartLibrary)
|
|
149
151
|
end
|
|
150
152
|
end
|
|
151
|
-
end
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
@library_listview.set_drag_dest_row(path, :into_or_after)
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
Gdk.drag_status(drag_context,
|
|
157
|
+
path ? drag_context.suggested_action : 0,
|
|
158
|
+
time)
|
|
158
159
|
end
|
|
159
160
|
|
|
160
161
|
@library_listview
|
|
161
162
|
.signal_connect("drag-drop") do |widget, drag_context, _x, _y, time, _data|
|
|
162
|
-
|
|
163
|
+
log.debug { "drag-drop" }
|
|
163
164
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
widget.drag_get_data(drag_context,
|
|
166
|
+
drag_context.targets.first,
|
|
167
|
+
time)
|
|
168
|
+
true
|
|
168
169
|
end
|
|
169
170
|
|
|
170
171
|
@library_listview
|
|
171
172
|
.signal_connect("drag-data-received") do |_, drag_context, x, y, data, _, _|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
173
|
+
log.debug { "drag-data-received" }
|
|
174
|
+
|
|
175
|
+
success = false
|
|
176
|
+
# FIXME: Ruby-GNOME2 should make comparison work without needing to
|
|
177
|
+
# call #name.
|
|
178
|
+
if data.data_type.name == Gdk::Selection::TYPE_STRING.name
|
|
179
|
+
success, path =
|
|
180
|
+
@library_listview.get_dest_row_at_pos(x, y)
|
|
181
|
+
|
|
182
|
+
if success
|
|
183
|
+
iter = @library_listview.model.get_iter(path)
|
|
184
|
+
library = @libraries.all_libraries.find do |lib|
|
|
185
|
+
lib.name == iter[1]
|
|
186
|
+
end
|
|
187
|
+
@parent.move_selected_books_to_library(library)
|
|
188
|
+
success = true
|
|
185
189
|
end
|
|
186
|
-
@parent.move_selected_books_to_library(library)
|
|
187
|
-
success = true
|
|
188
190
|
end
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
end
|
|
191
|
+
begin
|
|
192
|
+
drag_context.finish(success: success, delete: false)
|
|
193
|
+
rescue StandardError => ex
|
|
194
|
+
log.error { "drag_context.finish failed: #{ex}" }
|
|
195
|
+
raise
|
|
196
|
+
end
|
|
196
197
|
end
|
|
197
198
|
end
|
|
198
199
|
end
|
|
@@ -13,6 +13,7 @@ module Alexandria
|
|
|
13
13
|
include Logging
|
|
14
14
|
include CalendarPopup
|
|
15
15
|
include GetText
|
|
16
|
+
|
|
16
17
|
GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
|
|
17
18
|
|
|
18
19
|
attr_reader :predicate_operator_rule, :dialog
|
|
@@ -77,7 +78,7 @@ module Alexandria
|
|
|
77
78
|
new_rule.value = nil
|
|
78
79
|
end
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
private
|
|
81
82
|
|
|
82
83
|
attr_reader :smart_library_rules
|
|
83
84
|
|
|
@@ -28,7 +28,10 @@ module Alexandria
|
|
|
28
28
|
module UI
|
|
29
29
|
## Uses Ruby/GStreamer to play Ogg/Vorbis sound effects
|
|
30
30
|
class SoundEffectsPlayer
|
|
31
|
+
include Logging
|
|
32
|
+
|
|
31
33
|
def initialize
|
|
34
|
+
log.info { "Setting up SoundEffectsPlayer" }
|
|
32
35
|
@sounds_dir = Alexandria::Config::SOUNDS_DIR
|
|
33
36
|
@ogg_vorbis_pipeline = Gst::Pipeline.new
|
|
34
37
|
set_up_pipeline
|
|
@@ -11,6 +11,7 @@ require "alexandria/library_sort_order"
|
|
|
11
11
|
|
|
12
12
|
module Alexandria
|
|
13
13
|
module UI
|
|
14
|
+
# rubocop:disable Metrics/ClassLength
|
|
14
15
|
class UIManager < BuilderBase
|
|
15
16
|
attr_accessor :main_app, :actiongroup, :appbar, :prefs, :listview, :iconview,
|
|
16
17
|
:listview_model, :iconview_model, :filtered_model
|
|
@@ -18,6 +19,7 @@ module Alexandria
|
|
|
18
19
|
|
|
19
20
|
include Logging
|
|
20
21
|
include GetText
|
|
22
|
+
|
|
21
23
|
GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
|
|
22
24
|
|
|
23
25
|
def initialize(parent)
|
|
@@ -237,58 +239,36 @@ module Alexandria
|
|
|
237
239
|
end
|
|
238
240
|
end
|
|
239
241
|
|
|
242
|
+
LIST_STORE_COLUMNS = [
|
|
243
|
+
GdkPixbuf::Pixbuf, # COVER_LIST
|
|
244
|
+
GdkPixbuf::Pixbuf, # COVER_ICON
|
|
245
|
+
String, # TITLE
|
|
246
|
+
String, # TITLE_REDUCED
|
|
247
|
+
String, # AUTHORS
|
|
248
|
+
String, # ISBN
|
|
249
|
+
String, # PUBLISHER
|
|
250
|
+
String, # PUBLISH_DATE
|
|
251
|
+
String, # EDITION
|
|
252
|
+
Integer, # RATING
|
|
253
|
+
String, # IDENT
|
|
254
|
+
String, # NOTES
|
|
255
|
+
TrueClass, # REDD
|
|
256
|
+
TrueClass, # OWN
|
|
257
|
+
TrueClass, # WANT
|
|
258
|
+
String, # TAGS
|
|
259
|
+
String # LOANED TO
|
|
260
|
+
].freeze
|
|
261
|
+
|
|
240
262
|
def setup_active_model
|
|
241
263
|
log.debug { "setting up active model" }
|
|
242
264
|
# The active model.
|
|
243
265
|
|
|
244
|
-
|
|
245
|
-
GdkPixbuf::Pixbuf, # COVER_LIST
|
|
246
|
-
GdkPixbuf::Pixbuf, # COVER_ICON
|
|
247
|
-
String, # TITLE
|
|
248
|
-
String, # TITLE_REDUCED
|
|
249
|
-
String, # AUTHORS
|
|
250
|
-
String, # ISBN
|
|
251
|
-
String, # PUBLISHER
|
|
252
|
-
String, # PUBLISH_DATE
|
|
253
|
-
String, # EDITION
|
|
254
|
-
Integer, # RATING
|
|
255
|
-
String, # IDENT
|
|
256
|
-
String, # NOTES
|
|
257
|
-
TrueClass, # REDD
|
|
258
|
-
TrueClass, # OWN
|
|
259
|
-
TrueClass, # WANT
|
|
260
|
-
String, # TAGS
|
|
261
|
-
String # LOANED TO
|
|
262
|
-
]
|
|
263
|
-
|
|
264
|
-
@model = Gtk::ListStore.new(*list)
|
|
266
|
+
@model = Gtk::ListStore.new(*LIST_STORE_COLUMNS)
|
|
265
267
|
|
|
266
268
|
# Filter books according to the search toolbar widgets.
|
|
267
269
|
@filtered_model = Gtk::TreeModelFilter.new(@model)
|
|
268
270
|
@filtered_model.set_visible_func do |_model, iter|
|
|
269
|
-
|
|
270
|
-
@filter_books_mode ||= 0
|
|
271
|
-
filter = @filter_entry.text
|
|
272
|
-
if filter.empty?
|
|
273
|
-
true
|
|
274
|
-
else
|
|
275
|
-
data = case @filter_books_mode
|
|
276
|
-
when 0
|
|
277
|
-
(iter[Columns::TITLE] || "") +
|
|
278
|
-
(iter[Columns::AUTHORS] || "") +
|
|
279
|
-
(iter[Columns::ISBN] || "") +
|
|
280
|
-
(iter[Columns::PUBLISHER] || "") +
|
|
281
|
-
(iter[Columns::NOTES] || "") +
|
|
282
|
-
(iter[Columns::TAGS] || "")
|
|
283
|
-
when 2 then iter[Columns::TITLE]
|
|
284
|
-
when 3 then iter[Columns::AUTHORS]
|
|
285
|
-
when 4 then iter[Columns::ISBN]
|
|
286
|
-
when 5 then iter[Columns::PUBLISHER]
|
|
287
|
-
when 6 then iter[Columns::NOTES]
|
|
288
|
-
when 7 then iter[Columns::TAGS]
|
|
289
|
-
end
|
|
290
|
-
!data.nil? && data.downcase.include?(filter.downcase)
|
|
291
|
-
end
|
|
271
|
+
filter_iter(iter, @filter_entry.text)
|
|
292
272
|
end
|
|
293
273
|
|
|
294
274
|
# Give filter entry the initial keyboard focus.
|
|
@@ -296,6 +276,30 @@ module Alexandria
|
|
|
296
276
|
log.debug { "done setting up active model" }
|
|
297
277
|
end
|
|
298
278
|
|
|
279
|
+
def filter_iter(iter, filter)
|
|
280
|
+
if filter.empty?
|
|
281
|
+
true
|
|
282
|
+
else
|
|
283
|
+
@filter_books_mode ||= 0
|
|
284
|
+
data = case @filter_books_mode
|
|
285
|
+
when 0
|
|
286
|
+
(iter[Columns::TITLE] || "") +
|
|
287
|
+
(iter[Columns::AUTHORS] || "") +
|
|
288
|
+
(iter[Columns::ISBN] || "") +
|
|
289
|
+
(iter[Columns::PUBLISHER] || "") +
|
|
290
|
+
(iter[Columns::NOTES] || "") +
|
|
291
|
+
(iter[Columns::TAGS] || "")
|
|
292
|
+
when 2 then iter[Columns::TITLE]
|
|
293
|
+
when 3 then iter[Columns::AUTHORS]
|
|
294
|
+
when 4 then iter[Columns::ISBN]
|
|
295
|
+
when 5 then iter[Columns::PUBLISHER]
|
|
296
|
+
when 6 then iter[Columns::NOTES]
|
|
297
|
+
when 7 then iter[Columns::TAGS]
|
|
298
|
+
end
|
|
299
|
+
!data.nil? && data.downcase.include?(filter.downcase)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
299
303
|
def on_library_button_press_event(widget, event)
|
|
300
304
|
log.debug { "library_button_press_event" }
|
|
301
305
|
|
|
@@ -449,55 +453,56 @@ module Alexandria
|
|
|
449
453
|
|
|
450
454
|
# Focus is the wrong idiom here.
|
|
451
455
|
unless @clicking_on_sidepane || (@main_app.focus == @library_listview)
|
|
452
|
-
|
|
456
|
+
sensizite_action_group_items(books, library)
|
|
457
|
+
sensitize_provider_actions(books)
|
|
458
|
+
end
|
|
459
|
+
@clicking_on_sidepane = false
|
|
460
|
+
end
|
|
453
461
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
@actiongroup["OnlineInformation"].sensitive = \
|
|
464
|
-
books.length == 1
|
|
465
|
-
@actiongroup["SelectAll"].sensitive = \
|
|
466
|
-
books.length < library.length
|
|
467
|
-
|
|
468
|
-
@actiongroup["Delete"].sensitive = \
|
|
469
|
-
@actiongroup["DeselectAll"].sensitive = \
|
|
470
|
-
@actiongroup["Move"].sensitive =
|
|
471
|
-
@actiongroup["SetRating"].sensitive = !books.empty?
|
|
472
|
-
|
|
473
|
-
log.debug do
|
|
474
|
-
"on_books_selection_changed Delete: #{@actiongroup['Delete'].sensitive?}"
|
|
475
|
-
end
|
|
462
|
+
def sensizite_action_group_items(books, library)
|
|
463
|
+
log.debug { "Currently focused widget: #{@main_app.focus.inspect}" }
|
|
464
|
+
log.debug { "#{@library_listview} : #{@library_popup} : #{@listview}" }
|
|
465
|
+
log.debug do
|
|
466
|
+
"@library_listview: #{@library_listview.has_focus?} " \
|
|
467
|
+
"or @library_popup:#{@library_popup.has_focus?}"
|
|
468
|
+
end
|
|
469
|
+
log.debug { "@library_listview does *NOT* have focus" }
|
|
470
|
+
log.debug { "Books are empty: #{books.empty?}" }
|
|
476
471
|
|
|
472
|
+
@actiongroup["Properties"].sensitive = @actiongroup["OnlineInformation"].sensitive =
|
|
473
|
+
books.length == 1
|
|
474
|
+
@actiongroup["SelectAll"].sensitive = books.length < library.length
|
|
475
|
+
@actiongroup["DeselectAll"].sensitive = @actiongroup["SetRating"].sensitive =
|
|
476
|
+
!books.empty?
|
|
477
|
+
|
|
478
|
+
can_delete_or_move =
|
|
477
479
|
if library.is_a?(SmartLibrary)
|
|
478
|
-
|
|
479
|
-
|
|
480
|
+
false
|
|
481
|
+
else
|
|
482
|
+
!books.empty?
|
|
480
483
|
end
|
|
484
|
+
@actiongroup["Delete"].sensitive = @actiongroup["Move"].sensitive =
|
|
485
|
+
can_delete_or_move
|
|
486
|
+
end
|
|
481
487
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
no_urls = false unless has_no_url
|
|
496
|
-
end
|
|
497
|
-
@actiongroup["OnlineInformation"].sensitive = false if no_urls
|
|
488
|
+
# Sensitize providers URL
|
|
489
|
+
def sensitize_provider_actions(books)
|
|
490
|
+
return if books.length != 1
|
|
491
|
+
|
|
492
|
+
b = books.first
|
|
493
|
+
# FIXME: Clean up endless negation in this logic
|
|
494
|
+
no_urls = true
|
|
495
|
+
BookProviders.list.each do |provider|
|
|
496
|
+
has_no_url = true
|
|
497
|
+
begin
|
|
498
|
+
has_no_url = b.isbn.nil? || b.isbn.strip.empty? || provider.url(b).nil?
|
|
499
|
+
rescue StandardError => ex
|
|
500
|
+
log.warn { "Error determining URL from #{provider.name}; #{ex.message}" }
|
|
498
501
|
end
|
|
502
|
+
@actiongroup[provider.action_name].sensitive = !has_no_url
|
|
503
|
+
no_urls = false unless has_no_url
|
|
499
504
|
end
|
|
500
|
-
@
|
|
505
|
+
@actiongroup["OnlineInformation"].sensitive = false if no_urls
|
|
501
506
|
end
|
|
502
507
|
|
|
503
508
|
def on_switch_page(_notebook, _page, page_num)
|
|
@@ -624,7 +629,7 @@ module Alexandria
|
|
|
624
629
|
if response_type == Gtk::ResponseType::OK
|
|
625
630
|
# progress indicator...
|
|
626
631
|
@progressbar.fraction = 0
|
|
627
|
-
|
|
632
|
+
show_progress_bar
|
|
628
633
|
|
|
629
634
|
total_book_count = @libraries.ruined_books.size
|
|
630
635
|
fraction_per_book = 1.0 / total_book_count
|
|
@@ -634,38 +639,8 @@ module Alexandria
|
|
|
634
639
|
GLib::Idle.add do
|
|
635
640
|
ruined_book = @libraries.ruined_books.pop
|
|
636
641
|
if ruined_book
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
book_rslt = Alexandria::BookProviders.isbn_search(isbn.to_s)
|
|
640
|
-
book = book_rslt[0]
|
|
641
|
-
cover_uri = book_rslt[1]
|
|
642
|
-
|
|
643
|
-
# TODO: if the book was saved okay, make sure the old
|
|
644
|
-
# empty yaml file doesn't stick around esp if doing
|
|
645
|
-
# isbn-10 --> isbn-13 conversion...
|
|
646
|
-
if isbn.size == 10
|
|
647
|
-
filename = library.yaml(isbn)
|
|
648
|
-
log.debug { "removing old file #{filename}" }
|
|
649
|
-
begin
|
|
650
|
-
File.delete(filename)
|
|
651
|
-
rescue StandardError => ex
|
|
652
|
-
log.error { "Could not delete empty file #{filename}" }
|
|
653
|
-
end
|
|
654
|
-
end
|
|
655
|
-
|
|
656
|
-
log.debug do
|
|
657
|
-
"Trying to add #{book.title}, #{cover_uri} " \
|
|
658
|
-
"in library ''#{library.name}'"
|
|
659
|
-
end
|
|
660
|
-
library.save_cover(book, cover_uri) unless cover_uri.nil?
|
|
661
|
-
library << book
|
|
662
|
-
library.save(book)
|
|
663
|
-
set_status_label(format(_("Added '%s' to library '%s'"),
|
|
664
|
-
book.title, library.name))
|
|
665
|
-
rescue StandardError => ex
|
|
666
|
-
log.error { "Couldn't add book #{isbn}: #{ex}" }
|
|
667
|
-
log.error { ex.backtrace.join("\n") }
|
|
668
|
-
end
|
|
642
|
+
_book, isbn, library = ruined_book
|
|
643
|
+
repair_ruined_book(isbn, library)
|
|
669
644
|
|
|
670
645
|
prog_percentage += fraction_per_book
|
|
671
646
|
@progressbar.fraction = prog_percentage
|
|
@@ -680,8 +655,7 @@ module Alexandria
|
|
|
680
655
|
# @listview.columns_autosize
|
|
681
656
|
|
|
682
657
|
@progressbar.fraction = 1
|
|
683
|
-
|
|
684
|
-
@appbar.children.first.visible = false
|
|
658
|
+
hide_progress_bar
|
|
685
659
|
## Refresh the status bar.
|
|
686
660
|
set_status_label("")
|
|
687
661
|
# on_books_selection_changed
|
|
@@ -692,6 +666,75 @@ module Alexandria
|
|
|
692
666
|
end
|
|
693
667
|
end
|
|
694
668
|
|
|
669
|
+
def repair_ruined_book(isbn, library)
|
|
670
|
+
book_rslt = Alexandria::BookProviders.isbn_search(isbn.to_s)
|
|
671
|
+
book = book_rslt[0]
|
|
672
|
+
cover_uri = book_rslt[1]
|
|
673
|
+
|
|
674
|
+
# TODO: if the book was saved okay, make sure the old
|
|
675
|
+
# empty yaml file doesn't stick around esp if doing
|
|
676
|
+
# isbn-10 --> isbn-13 conversion...
|
|
677
|
+
if isbn.size == 10
|
|
678
|
+
filename = library.yaml(isbn)
|
|
679
|
+
log.debug { "removing old file #{filename}" }
|
|
680
|
+
begin
|
|
681
|
+
File.delete(filename)
|
|
682
|
+
rescue StandardError => ex
|
|
683
|
+
log.error { "Could not delete empty file #{filename}" }
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
log.debug do
|
|
688
|
+
"Trying to add #{book.title}, #{cover_uri} " \
|
|
689
|
+
"in library ''#{library.name}'"
|
|
690
|
+
end
|
|
691
|
+
library.save_cover(book, cover_uri) unless cover_uri.nil?
|
|
692
|
+
library << book
|
|
693
|
+
library.save(book)
|
|
694
|
+
set_status_label(format(_("Added '%s' to library '%s'"),
|
|
695
|
+
book.title, library.name))
|
|
696
|
+
rescue StandardError => ex
|
|
697
|
+
log.error { "Couldn't add book #{isbn}: #{ex}" }
|
|
698
|
+
log.error { ex.backtrace.join("\n") }
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
def progress_bar
|
|
702
|
+
@progress_bar ||= @appbar.children.first
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
def show_progress_bar
|
|
706
|
+
progress_bar.visible = true
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
def hide_progress_bar
|
|
710
|
+
progress_bar.visible = false
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
def start_progress_bar_pulsing(dialog)
|
|
714
|
+
GLib::Idle.add do
|
|
715
|
+
show_progress_bar
|
|
716
|
+
@progress_pulsing = GLib::Timeout.add(100) do
|
|
717
|
+
if dialog.destroyed?
|
|
718
|
+
@progress_pulsing = nil
|
|
719
|
+
hide_progress_bar
|
|
720
|
+
false
|
|
721
|
+
else
|
|
722
|
+
progress_bar.pulse
|
|
723
|
+
true
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
false
|
|
727
|
+
end
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
def stop_progress_bar_pulsing
|
|
731
|
+
GLib::Idle.add do
|
|
732
|
+
hide_progress_bar
|
|
733
|
+
GLib::Source.remove(@progress_pulsing) if @progress_pulsing
|
|
734
|
+
false
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
695
738
|
def cache_scaled_icon(icon, width, height)
|
|
696
739
|
log.debug { "cache_scaled_icon #{icon}, #{width}, #{height}" }
|
|
697
740
|
@cache ||= {}
|
|
@@ -703,11 +746,9 @@ module Alexandria
|
|
|
703
746
|
REDUCE_TITLE_REGEX = Regexp.new("^(.{#{ICON_TITLE_MAXLEN}}).*$")
|
|
704
747
|
|
|
705
748
|
def fill_iter_with_book(iter, book)
|
|
706
|
-
log.debug { "fill iter #{iter} with book #{book}" }
|
|
707
749
|
iter[Columns::IDENT] = book.ident.to_s
|
|
708
750
|
iter[Columns::TITLE] = book.title
|
|
709
|
-
|
|
710
|
-
iter[Columns::TITLE_REDUCED] = title
|
|
751
|
+
iter[Columns::TITLE_REDUCED] = reduced_title(book.title)
|
|
711
752
|
iter[Columns::AUTHORS] = book.authors.join(", ")
|
|
712
753
|
iter[Columns::ISBN] = book.isbn.to_s
|
|
713
754
|
iter[Columns::PUBLISHER] = book.publisher
|
|
@@ -715,30 +756,37 @@ module Alexandria
|
|
|
715
756
|
iter[Columns::EDITION] = book.edition
|
|
716
757
|
iter[Columns::NOTES] = (book.notes || "")
|
|
717
758
|
iter[Columns::LOANED_TO] = (book.loaned_to || "")
|
|
718
|
-
|
|
759
|
+
|
|
760
|
+
rating = book.rating || Book::DEFAULT_RATING
|
|
719
761
|
# ascending order is the default
|
|
720
762
|
iter[Columns::RATING] = Book::MAX_RATING_STARS - rating
|
|
763
|
+
|
|
721
764
|
iter[Columns::OWN] = book.own?
|
|
722
765
|
iter[Columns::REDD] = book.redd?
|
|
723
766
|
iter[Columns::WANT] = book.want?
|
|
724
|
-
iter[Columns::TAGS] =
|
|
725
|
-
book.tags.join(",")
|
|
726
|
-
else
|
|
727
|
-
""
|
|
728
|
-
end
|
|
767
|
+
iter[Columns::TAGS] = book.tags&.join(",") || ""
|
|
729
768
|
|
|
730
769
|
icon = Icons.cover(selected_library, book)
|
|
731
|
-
|
|
732
|
-
iter[Columns::
|
|
770
|
+
iter[Columns::COVER_LIST] = cover_list_icon(icon)
|
|
771
|
+
iter[Columns::COVER_ICON] = cover_icon(icon, rating)
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
def reduced_title(title)
|
|
775
|
+
title.sub(REDUCE_TITLE_REGEX, '\1...')
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
def cover_list_icon(icon)
|
|
779
|
+
cache_scaled_icon(icon, 20, 25)
|
|
780
|
+
end
|
|
733
781
|
|
|
782
|
+
def cover_icon(icon, rating)
|
|
734
783
|
if icon.height > ICON_HEIGHT
|
|
735
784
|
new_width = icon.width / (icon.height / ICON_HEIGHT.to_f)
|
|
736
785
|
new_height = [ICON_HEIGHT, icon.height].min
|
|
737
786
|
icon = cache_scaled_icon(icon, new_width, new_height)
|
|
738
787
|
end
|
|
739
788
|
icon = Icons.tag_icon(icon, Icons::FAVORITE_TAG) if rating == Book::MAX_RATING_STARS
|
|
740
|
-
|
|
741
|
-
log.debug { "Full iter: " + (0..15).map { |num| iter[num].inspect }.join(", ") }
|
|
789
|
+
icon
|
|
742
790
|
end
|
|
743
791
|
|
|
744
792
|
def append_book(book, _tail = nil)
|
|
@@ -807,8 +855,10 @@ module Alexandria
|
|
|
807
855
|
@iconview.freeze
|
|
808
856
|
@listview.freeze
|
|
809
857
|
@model.clear
|
|
858
|
+
|
|
810
859
|
@progressbar.fraction = 0
|
|
811
|
-
|
|
860
|
+
show_progress_bar
|
|
861
|
+
|
|
812
862
|
set_status_label(_("Loading '%s'...") % library.name)
|
|
813
863
|
total = library.length
|
|
814
864
|
log.debug { "library #{library.name} length #{library.length}" }
|
|
@@ -833,9 +883,10 @@ module Alexandria
|
|
|
833
883
|
@listview.unfreeze # NEW / bdewey
|
|
834
884
|
@filtered_model.refilter
|
|
835
885
|
@listview.columns_autosize
|
|
886
|
+
|
|
836
887
|
@progressbar.fraction = 1
|
|
837
|
-
|
|
838
|
-
|
|
888
|
+
hide_progress_bar
|
|
889
|
+
|
|
839
890
|
# Refresh the status bar.
|
|
840
891
|
on_books_selection_changed
|
|
841
892
|
@library_listview.set_sensitive(true)
|
|
@@ -850,7 +901,7 @@ module Alexandria
|
|
|
850
901
|
log.debug { "selected_library" }
|
|
851
902
|
if (iter = @library_listview.selection.selected)
|
|
852
903
|
target_name = iter[1]
|
|
853
|
-
@libraries.all_libraries.find {
|
|
904
|
+
@libraries.all_libraries.find { _1.name == target_name }
|
|
854
905
|
else
|
|
855
906
|
@libraries.all_libraries.first
|
|
856
907
|
end
|
|
@@ -1033,9 +1084,8 @@ module Alexandria
|
|
|
1033
1084
|
|
|
1034
1085
|
@actiongroup.remove_action(action)
|
|
1035
1086
|
end
|
|
1036
|
-
actions =
|
|
1037
|
-
|
|
1038
|
-
actions << [
|
|
1087
|
+
actions = @libraries.all_regular_libraries.map do |library|
|
|
1088
|
+
[
|
|
1039
1089
|
library.action_name, nil,
|
|
1040
1090
|
_("In '_%s'") % library.name,
|
|
1041
1091
|
nil, nil, proc { move_selected_books_to_library(library) }
|
|
@@ -1192,5 +1242,6 @@ module Alexandria
|
|
|
1192
1242
|
@filtered_model.convert_path_to_child_path(filter_path) if filter_path
|
|
1193
1243
|
end
|
|
1194
1244
|
end
|
|
1245
|
+
# rubocop:enable Metrics/ClassLength
|
|
1195
1246
|
end
|
|
1196
1247
|
end
|
data/lib/alexandria/ui.rb
CHANGED