alexandria-book-collection-manager 0.6.9 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +34 -30
- data/.rubocop_todo.yml +139 -54
- data/CHANGELOG.md +10 -0
- data/Gemfile +1 -0
- data/Rakefile +10 -11
- data/alexandria-book-collection-manager.gemspec +3 -2
- data/bin/alexandria +1 -1
- data/lib/alexandria.rb +3 -6
- data/lib/alexandria/about.rb +9 -9
- data/lib/alexandria/book_providers.rb +12 -12
- data/lib/alexandria/book_providers/adlibris.rb +14 -18
- data/lib/alexandria/book_providers/amazon_aws.rb +17 -31
- data/lib/alexandria/book_providers/amazon_ecs_util.rb +5 -6
- data/lib/alexandria/book_providers/barnes_and_noble.rb +51 -76
- data/lib/alexandria/book_providers/bol_it.rb +12 -12
- data/lib/alexandria/book_providers/deastore.rb +27 -31
- data/lib/alexandria/book_providers/douban.rb +9 -13
- data/lib/alexandria/book_providers/ibs_it.rb +10 -10
- data/lib/alexandria/book_providers/mcu.rb +12 -18
- data/lib/alexandria/book_providers/proxis.rb +14 -22
- data/lib/alexandria/book_providers/pseudomarc.rb +8 -18
- data/lib/alexandria/book_providers/renaud.rb +16 -16
- data/lib/alexandria/book_providers/siciliano.rb +25 -38
- data/lib/alexandria/book_providers/thalia.rb +13 -16
- data/lib/alexandria/book_providers/webster_it.rb +14 -18
- data/lib/alexandria/book_providers/worldcat.rb +21 -25
- data/lib/alexandria/book_providers/z3950.rb +19 -23
- data/lib/alexandria/config.rb +2 -2
- data/lib/alexandria/execution_queue.rb +3 -1
- data/lib/alexandria/export_library.rb +19 -22
- data/lib/alexandria/import_library.rb +14 -18
- data/lib/alexandria/import_library_csv.rb +12 -30
- data/lib/alexandria/models/book.rb +7 -9
- data/lib/alexandria/models/library.rb +44 -44
- data/lib/alexandria/net.rb +1 -1
- data/lib/alexandria/preferences.rb +12 -57
- data/lib/alexandria/scanners.rb +10 -6
- data/lib/alexandria/scanners/cuecat.rb +2 -2
- data/lib/alexandria/smart_library.rb +12 -12
- data/lib/alexandria/ui.rb +5 -2
- data/lib/alexandria/ui/callbacks.rb +106 -65
- data/lib/alexandria/ui/completion_models.rb +55 -51
- data/lib/alexandria/ui/dialogs/about_dialog.rb +1 -1
- data/lib/alexandria/ui/dialogs/acquire_dialog.rb +25 -51
- data/lib/alexandria/ui/dialogs/alert_dialog.rb +13 -11
- data/lib/alexandria/ui/dialogs/bad_isbns_dialog.rb +2 -2
- data/lib/alexandria/ui/dialogs/barcode_animation.rb +39 -23
- data/lib/alexandria/ui/dialogs/book_properties_dialog.rb +16 -21
- data/lib/alexandria/ui/dialogs/book_properties_dialog_base.rb +23 -24
- data/lib/alexandria/ui/dialogs/export_dialog.rb +46 -45
- data/lib/alexandria/ui/dialogs/import_dialog.rb +26 -35
- data/lib/alexandria/ui/dialogs/misc_dialogs.rb +11 -11
- data/lib/alexandria/ui/dialogs/new_book_dialog.rb +47 -59
- data/lib/alexandria/ui/dialogs/new_book_dialog_manual.rb +14 -13
- data/lib/alexandria/ui/dialogs/new_smart_library_dialog.rb +12 -11
- data/lib/alexandria/ui/dialogs/preferences_dialog.rb +37 -43
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog.rb +9 -8
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog_base.rb +47 -53
- data/lib/alexandria/ui/dndable.rb +5 -4
- data/lib/alexandria/ui/icons.rb +19 -19
- data/lib/alexandria/ui/iconview.rb +7 -12
- data/lib/alexandria/ui/iconview_tooltips.rb +22 -109
- data/lib/alexandria/ui/init.rb +7 -15
- data/lib/alexandria/ui/libraries_combo.rb +54 -48
- data/lib/alexandria/ui/listview.rb +30 -85
- data/lib/alexandria/ui/multi_drag_treeview.rb +110 -107
- data/lib/alexandria/ui/sidepane.rb +23 -25
- data/lib/alexandria/ui/sound.rb +18 -27
- data/lib/alexandria/ui/ui_manager.rb +126 -204
- data/lib/alexandria/undo_manager.rb +2 -2
- data/lib/alexandria/version.rb +4 -4
- data/spec/alexandria/book_providers_spec.rb +7 -4
- data/spec/alexandria/library_spec.rb +13 -16
- data/spec/alexandria/scanners/cuecat_spec.rb +1 -2
- data/spec/alexandria/ui/dialogs_spec.rb +5 -1
- data/spec/alexandria/ui/main_app_spec.rb +3 -3
- data/{lib/alexandria/utils.rb → spec/alexandria/ui/sound_spec.rb} +6 -11
- data/spec/alexandria/ui/ui_utilities_spec.rb +3 -3
- data/spec/spec_helper.rb +2 -2
- data/util/rake/fileinstall.rb +17 -33
- data/util/rake/gettextgenerate.rb +2 -4
- data/util/rake/omfgenerate.rb +1 -3
- metadata +23 -11
- data/lib/alexandria/ui/gtk_thread_help.rb +0 -89
@@ -1,6 +1,6 @@
|
|
1
1
|
# Copyright (C) 2008 Joseph Method
|
2
2
|
# Copyright (C) 2008,2009 Cathal Mc Ginley
|
3
|
-
# Copyright (C) 2011,2014
|
3
|
+
# Copyright (C) 2011, 2014-2016 Matijs van Zuijlen
|
4
4
|
#
|
5
5
|
# Alexandria is free software; you can redistribute it and/or
|
6
6
|
# modify it under the terms of the GNU General Public License as
|
@@ -35,7 +35,7 @@ module Alexandria
|
|
35
35
|
x = (@libraries.all_libraries + Library.deleted_libraries).find do |library|
|
36
36
|
library.name == new_text.strip
|
37
37
|
end
|
38
|
-
x
|
38
|
+
x && (x.name != @parent.selected_library.name)
|
39
39
|
end
|
40
40
|
|
41
41
|
# if new_text is invalid utf-8, returns true
|
@@ -97,7 +97,7 @@ module Alexandria
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def setup_sidepane
|
100
|
-
@library_listview.model = Gtk::ListStore.new(
|
100
|
+
@library_listview.model = Gtk::ListStore.new(GdkPixbuf::Pixbuf,
|
101
101
|
String,
|
102
102
|
TrueClass,
|
103
103
|
TrueClass)
|
@@ -113,7 +113,7 @@ module Alexandria
|
|
113
113
|
cell.pixbuf = iter[0]
|
114
114
|
end
|
115
115
|
renderer = Gtk::CellRendererText.new
|
116
|
-
renderer.ellipsize = Pango::ELLIPSIZE_END
|
116
|
+
renderer.ellipsize = Pango::ELLIPSIZE_END
|
117
117
|
column.pack_start(renderer, true)
|
118
118
|
column.set_cell_data_func(renderer) do |_col, cell, _model, iter|
|
119
119
|
# log.debug { "sidepane: editable #{cell}, #{iter} #{iter[1]}: #{iter[2]}" }
|
@@ -124,9 +124,9 @@ module Alexandria
|
|
124
124
|
renderer.signal_connect('edited', &method(:on_edited_library))
|
125
125
|
@library_listview.append_column(column)
|
126
126
|
|
127
|
-
@library_listview.set_row_separator_func do |
|
127
|
+
@library_listview.set_row_separator_func do |model, iter|
|
128
128
|
# log.debug { "library_listview row_separator #{iter}" }
|
129
|
-
iter
|
129
|
+
model.get_value(iter, 3)
|
130
130
|
end
|
131
131
|
|
132
132
|
@library_listview.selection.signal_connect('changed') do
|
@@ -135,9 +135,7 @@ module Alexandria
|
|
135
135
|
@parent.refresh_books
|
136
136
|
end
|
137
137
|
|
138
|
-
@library_listview.enable_model_drag_dest(
|
139
|
-
BOOKS_TARGET_TABLE,
|
140
|
-
Gdk::DragContext::ACTION_MOVE)
|
138
|
+
@library_listview.enable_model_drag_dest(BOOKS_TARGET_TABLE, :move)
|
141
139
|
|
142
140
|
@library_listview.signal_connect('drag-motion') do |_widget, drag_context, x, y, time, _data|
|
143
141
|
log.debug { 'drag-motion' }
|
@@ -151,7 +149,7 @@ module Alexandria
|
|
151
149
|
path = nil
|
152
150
|
else
|
153
151
|
iter = @library_listview.model.get_iter(path)
|
154
|
-
if iter[3]
|
152
|
+
if iter[3] # separator?
|
155
153
|
path = nil
|
156
154
|
else
|
157
155
|
library = @libraries.all_libraries.find do |lib|
|
@@ -162,22 +160,19 @@ module Alexandria
|
|
162
160
|
end
|
163
161
|
end
|
164
162
|
|
165
|
-
@library_listview.set_drag_dest_row(
|
166
|
-
path,
|
167
|
-
Gtk::TreeView::DROP_INTO_OR_AFTER)
|
163
|
+
@library_listview.set_drag_dest_row(path, :into_or_after)
|
168
164
|
|
169
|
-
|
170
|
-
|
171
|
-
|
165
|
+
Gdk.drag_status(drag_context,
|
166
|
+
!path.nil? ? drag_context.suggested_action : 0,
|
167
|
+
time)
|
172
168
|
end
|
173
169
|
|
174
170
|
@library_listview.signal_connect('drag-drop') do |widget, drag_context, _x, _y, time, _data|
|
175
171
|
log.debug { 'drag-drop' }
|
176
172
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
time)
|
173
|
+
widget.drag_get_data(drag_context,
|
174
|
+
drag_context.targets.first,
|
175
|
+
time)
|
181
176
|
true
|
182
177
|
end
|
183
178
|
|
@@ -185,11 +180,13 @@ module Alexandria
|
|
185
180
|
log.debug { 'drag-data-received' }
|
186
181
|
|
187
182
|
success = false
|
188
|
-
|
189
|
-
|
183
|
+
# FIXME: Ruby-GNOME2 should make comparison work without needing to
|
184
|
+
# call #name.
|
185
|
+
if selection_data.data_type.name == Gdk::Selection::TYPE_STRING.name
|
186
|
+
success, path =
|
190
187
|
@library_listview.get_dest_row_at_pos(x, y)
|
191
188
|
|
192
|
-
if
|
189
|
+
if success
|
193
190
|
iter = @library_listview.model.get_iter(path)
|
194
191
|
library = @libraries.all_libraries.find do |lib|
|
195
192
|
lib.name == iter[1]
|
@@ -199,9 +196,10 @@ module Alexandria
|
|
199
196
|
end
|
200
197
|
end
|
201
198
|
begin
|
202
|
-
|
199
|
+
drag_context.finish(success: success, delete: false)
|
203
200
|
rescue => ex
|
204
|
-
log.error { "
|
201
|
+
log.error { "drag_context.finish failed: #{ex}" }
|
202
|
+
raise
|
205
203
|
end
|
206
204
|
end
|
207
205
|
end
|
data/lib/alexandria/ui/sound.rb
CHANGED
@@ -31,14 +31,17 @@ module Alexandria
|
|
31
31
|
@ogg_vorbis_pipeline = Gst::Pipeline.new
|
32
32
|
set_up_pipeline
|
33
33
|
@playing = false
|
34
|
-
|
34
|
+
set_up_bus_watch
|
35
35
|
end
|
36
36
|
|
37
37
|
def play(effect)
|
38
38
|
file = File.join(@sounds_dir, "#{effect}.ogg")
|
39
39
|
unless @playing
|
40
|
+
puts "Not playing. Starting #{effect}." if $DEBUG
|
40
41
|
@filesrc.location = file
|
41
42
|
start_playback
|
43
|
+
else
|
44
|
+
puts "Already playing #{effect}." if $DEBUG
|
42
45
|
end
|
43
46
|
end
|
44
47
|
|
@@ -57,50 +60,38 @@ module Alexandria
|
|
57
60
|
# have multiple src pads (for audio/video muxed streams)
|
58
61
|
|
59
62
|
demuxer.signal_connect('pad-added') do |_parser, ogg_src_pad|
|
60
|
-
vorbis_sink_pad = decoder.
|
63
|
+
vorbis_sink_pad = decoder.sinkpads.first
|
61
64
|
ogg_src_pad.link(vorbis_sink_pad)
|
62
65
|
end
|
63
66
|
|
64
67
|
decoder >> converter >> audiosink
|
65
68
|
end
|
66
69
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
when Gst::Message::ERROR
|
78
|
-
if $DEBUG
|
79
|
-
puts 'ERROR loop.quit'
|
80
|
-
p message.parse
|
81
|
-
end
|
82
|
-
@playing = false
|
83
|
-
@loop.quit
|
70
|
+
def set_up_bus_watch
|
71
|
+
@bus = @ogg_vorbis_pipeline.bus
|
72
|
+
@bus.add_watch do |_bus, message|
|
73
|
+
case message.type
|
74
|
+
when Gst::MessageType::EOS
|
75
|
+
stop_playback
|
76
|
+
when Gst::MessageType::ERROR
|
77
|
+
if $DEBUG
|
78
|
+
puts 'ERROR loop.quit'
|
79
|
+
p message.parse
|
84
80
|
end
|
85
|
-
|
81
|
+
stop_playback
|
86
82
|
end
|
83
|
+
true
|
87
84
|
end
|
88
85
|
end
|
89
86
|
|
90
87
|
def start_playback
|
91
88
|
@playing = true
|
92
89
|
@ogg_vorbis_pipeline.play
|
93
|
-
begin
|
94
|
-
@loop.run
|
95
|
-
rescue Interrupt
|
96
|
-
ensure
|
97
|
-
@ogg_vorbis_pipeline.stop
|
98
|
-
@playing = false
|
99
|
-
end
|
100
90
|
end
|
101
91
|
|
102
92
|
def stop_playback
|
103
93
|
@ogg_vorbis_pipeline.stop
|
94
|
+
@playing = false
|
104
95
|
end
|
105
96
|
end
|
106
97
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Copyright (C) 2004-2006 Laurent Sansonetti
|
2
2
|
# Copyright (C) 2008 Joseph Method
|
3
|
-
# Copyright (C) 2011 Matijs van Zuijlen
|
3
|
+
# Copyright (C) 2011, 2016 Matijs van Zuijlen
|
4
4
|
#
|
5
5
|
# Alexandria is free software; you can redistribute it and/or
|
6
6
|
# modify it under the terms of the GNU General Public License as
|
@@ -38,7 +38,7 @@ module Alexandria
|
|
38
38
|
MAX_RATING_STARS = 5
|
39
39
|
|
40
40
|
def initialize(parent)
|
41
|
-
super('main_app__builder.glade',
|
41
|
+
super('main_app__builder.glade', widget_names)
|
42
42
|
@parent = parent
|
43
43
|
|
44
44
|
@library_separator_iter = nil
|
@@ -60,7 +60,6 @@ module Alexandria
|
|
60
60
|
setup_accel_group
|
61
61
|
setup_popups
|
62
62
|
setup_window_events
|
63
|
-
setup_dialog_hooks
|
64
63
|
setup_books_iconview_sorting
|
65
64
|
on_books_selection_changed
|
66
65
|
restore_preferences
|
@@ -89,8 +88,8 @@ module Alexandria
|
|
89
88
|
end
|
90
89
|
|
91
90
|
def setup_dependents
|
92
|
-
@listview_model = Gtk::TreeModelSort.new(@filtered_model)
|
93
|
-
@iconview_model = Gtk::TreeModelSort.new(@filtered_model)
|
91
|
+
@listview_model = Gtk::TreeModelSort.new(model: @filtered_model)
|
92
|
+
@iconview_model = Gtk::TreeModelSort.new(model: @filtered_model)
|
94
93
|
@listview_manager = ListViewManager.new @listview, self
|
95
94
|
@iconview_manager = IconViewManager.new @iconview, self
|
96
95
|
@sidepane_manager = SidePaneManager.new @library_listview, self
|
@@ -115,29 +114,29 @@ module Alexandria
|
|
115
114
|
add_main_toolbar_items
|
116
115
|
@toolbar = @uimanager.get_widget('/MainToolbar')
|
117
116
|
@toolbar.show_arrow = true
|
118
|
-
@toolbar.insert(
|
117
|
+
@toolbar.insert(Gtk::SeparatorToolItem.new, -1)
|
119
118
|
setup_toolbar_combobox
|
120
119
|
setup_toolbar_filter_entry
|
121
|
-
@toolbar.insert(
|
120
|
+
@toolbar.insert(Gtk::SeparatorToolItem.new, -1)
|
122
121
|
setup_toolbar_viewas
|
123
122
|
@toolbar.show_all
|
124
123
|
@actiongroup['Undo'].sensitive = @actiongroup['Redo'].sensitive = false
|
125
124
|
UndoManager.instance.add_observer(self)
|
126
|
-
@vbox1.add(@toolbar,
|
125
|
+
@vbox1.add(@toolbar, position: 1, expand: false, fill: false)
|
127
126
|
end
|
128
127
|
|
129
128
|
def add_main_toolbar_items
|
130
129
|
mid = @uimanager.new_merge_id
|
131
130
|
@uimanager.add_ui(mid, 'ui/', 'MainToolbar', 'MainToolbar',
|
132
|
-
|
131
|
+
:toolbar, false)
|
133
132
|
@uimanager.add_ui(mid, 'ui/MainToolbar/', 'New', 'New',
|
134
|
-
|
133
|
+
:toolitem, false)
|
135
134
|
@uimanager.add_ui(mid, 'ui/MainToolbar/', 'AddBook', 'AddBook',
|
136
|
-
|
135
|
+
:toolitem, false)
|
137
136
|
# @uimanager.add_ui(mid, "ui/MainToolbar/", "sep", "sep",
|
138
|
-
#
|
137
|
+
# :separator, false)
|
139
138
|
# @uimanager.add_ui(mid, "ui/MainToolbar/", "Refresh", "Refresh",
|
140
|
-
#
|
139
|
+
# :toolitem, false)
|
141
140
|
end
|
142
141
|
|
143
142
|
def setup_toolbar_filter_entry
|
@@ -146,19 +145,16 @@ module Alexandria
|
|
146
145
|
@toolitem = Gtk::ToolItem.new
|
147
146
|
@toolitem.expand = true
|
148
147
|
@toolitem.border_width = 5
|
149
|
-
@
|
150
|
-
_('Type here the search criterion'), nil)
|
148
|
+
@filter_entry.set_tooltip_text _('Type here the search criterion')
|
151
149
|
@toolitem << @filter_entry
|
152
|
-
@toolbar.insert(-1
|
150
|
+
@toolbar.insert(@toolitem, -1)
|
153
151
|
end
|
154
152
|
|
155
153
|
def setup_toolbar_combobox
|
156
|
-
|
157
|
-
|
158
|
-
cb = Gtk::ComboBox.new
|
159
|
-
cb.set_row_separator_func do |_model, iter|
|
154
|
+
cb = Gtk::ComboBoxText.new
|
155
|
+
cb.set_row_separator_func do |model, iter|
|
160
156
|
# log.debug { "row_separator" }
|
161
|
-
iter
|
157
|
+
model.get_value(iter, 0) == '-'
|
162
158
|
end
|
163
159
|
[_('Match everything'),
|
164
160
|
'-',
|
@@ -167,8 +163,7 @@ module Alexandria
|
|
167
163
|
_('ISBN contains'),
|
168
164
|
_('Publisher contains'),
|
169
165
|
_('Notes contain'),
|
170
|
-
_('Tags contain')
|
171
|
-
].each do |item|
|
166
|
+
_('Tags contain')].each do |item|
|
172
167
|
cb.append_text(item)
|
173
168
|
end
|
174
169
|
cb.active = 0
|
@@ -181,12 +176,12 @@ module Alexandria
|
|
181
176
|
@toolitem = Gtk::ToolItem.new
|
182
177
|
@toolitem.border_width = 5
|
183
178
|
@toolitem << eb
|
184
|
-
@toolbar.insert(-1
|
185
|
-
|
179
|
+
@toolbar.insert(@toolitem, -1)
|
180
|
+
eb.set_tooltip_text _('Change the search type')
|
186
181
|
end
|
187
182
|
|
188
183
|
def setup_toolbar_viewas
|
189
|
-
@toolbar_view_as = Gtk::
|
184
|
+
@toolbar_view_as = Gtk::ComboBoxText.new
|
190
185
|
@toolbar_view_as.append_text(_('View as Icons'))
|
191
186
|
@toolbar_view_as.append_text(_('View as List'))
|
192
187
|
@toolbar_view_as.active = 0
|
@@ -200,8 +195,8 @@ module Alexandria
|
|
200
195
|
@toolitem = Gtk::ToolItem.new
|
201
196
|
@toolitem.border_width = 5
|
202
197
|
@toolitem << eb
|
203
|
-
@toolbar.insert(-1
|
204
|
-
|
198
|
+
@toolbar.insert(@toolitem, -1)
|
199
|
+
eb.set_tooltip_text _('Choose how to show books')
|
205
200
|
end
|
206
201
|
|
207
202
|
def setup_book_providers
|
@@ -214,7 +209,7 @@ module Alexandria
|
|
214
209
|
'ui/NoBookPopup/OnlineInformation/'].each do |path|
|
215
210
|
log.debug { "Adding #{name} to #{path}" }
|
216
211
|
@uimanager.add_ui(mid, path, name, name,
|
217
|
-
|
212
|
+
:menuitem, false)
|
218
213
|
end
|
219
214
|
end
|
220
215
|
end
|
@@ -234,19 +229,7 @@ module Alexandria
|
|
234
229
|
|
235
230
|
def setup_menus
|
236
231
|
@menubar = @uimanager.get_widget('/MainMenubar')
|
237
|
-
@vbox1.add(@menubar,
|
238
|
-
end
|
239
|
-
|
240
|
-
def setup_dialog_hooks
|
241
|
-
log.debug { 'setup_dialog_hooks' }
|
242
|
-
Gtk::AboutDialog.set_url_hook do |_about, link|
|
243
|
-
log.debug { 'set_url_hook' }
|
244
|
-
open_web_browser(link)
|
245
|
-
end
|
246
|
-
Gtk::AboutDialog.set_email_hook do |_about, link|
|
247
|
-
log.debug { 'set_email_hook' }
|
248
|
-
open_email_client('mailto:' + link)
|
249
|
-
end
|
232
|
+
@vbox1.add(@menubar, position: 0, expand: false, fill: false)
|
250
233
|
end
|
251
234
|
|
252
235
|
def setup_popups
|
@@ -269,8 +252,8 @@ module Alexandria
|
|
269
252
|
# The active model.
|
270
253
|
|
271
254
|
list = [
|
272
|
-
|
273
|
-
|
255
|
+
GdkPixbuf::Pixbuf, # COVER_LIST
|
256
|
+
GdkPixbuf::Pixbuf, # COVER_ICON
|
274
257
|
String, # TITLE
|
275
258
|
String, # TITLE_REDUCED
|
276
259
|
String, # AUTHORS
|
@@ -301,12 +284,12 @@ module Alexandria
|
|
301
284
|
else
|
302
285
|
data = case @filter_books_mode
|
303
286
|
when 0 then
|
304
|
-
(iter[Columns::TITLE]
|
305
|
-
(iter[Columns::AUTHORS]
|
306
|
-
(iter[Columns::ISBN]
|
307
|
-
(iter[Columns::PUBLISHER]
|
308
|
-
(iter[Columns::NOTES]
|
309
|
-
(iter[Columns::TAGS]
|
287
|
+
(iter[Columns::TITLE] || '') +
|
288
|
+
(iter[Columns::AUTHORS] || '') +
|
289
|
+
(iter[Columns::ISBN] || '') +
|
290
|
+
(iter[Columns::PUBLISHER] || '') +
|
291
|
+
(iter[Columns::NOTES] || '') +
|
292
|
+
(iter[Columns::TAGS] || '')
|
310
293
|
when 2 then iter[Columns::TITLE]
|
311
294
|
when 3 then iter[Columns::AUTHORS]
|
312
295
|
when 4 then iter[Columns::ISBN]
|
@@ -314,7 +297,7 @@ module Alexandria
|
|
314
297
|
when 6 then iter[Columns::NOTES]
|
315
298
|
when 7 then iter[Columns::TAGS]
|
316
299
|
end
|
317
|
-
!data.nil?
|
300
|
+
!data.nil? && data.downcase.include?(filter.downcase)
|
318
301
|
end
|
319
302
|
end
|
320
303
|
|
@@ -344,7 +327,7 @@ module Alexandria
|
|
344
327
|
sensitize_library selected_library
|
345
328
|
|
346
329
|
if widget.is_a?(Gtk::TreeView)
|
347
|
-
|
330
|
+
GLib::Idle.add do
|
348
331
|
# cur_path, focus_col = widget.cursor
|
349
332
|
|
350
333
|
widget.focus = true
|
@@ -377,13 +360,13 @@ module Alexandria
|
|
377
360
|
if library_already_selected
|
378
361
|
sensitize_library selected_library
|
379
362
|
|
380
|
-
|
363
|
+
GLib::Idle.add do
|
381
364
|
menu.popup(nil, nil, event.button, event.time)
|
382
365
|
# @clicking_on_sidepane = false
|
383
366
|
false
|
384
367
|
end
|
385
368
|
else
|
386
|
-
|
369
|
+
GLib::Idle.add do
|
387
370
|
menu.popup(nil, nil, event.button, event.time)
|
388
371
|
# @clicking_on_sidepane = false
|
389
372
|
|
@@ -410,7 +393,7 @@ module Alexandria
|
|
410
393
|
end
|
411
394
|
|
412
395
|
def event_is_right_click(event)
|
413
|
-
event.event_type ==
|
396
|
+
(event.event_type == :button_press) && (event.button == 3)
|
414
397
|
end
|
415
398
|
|
416
399
|
def on_books_button_press_event(widget, event)
|
@@ -430,7 +413,7 @@ module Alexandria
|
|
430
413
|
widget.unselect_all
|
431
414
|
end
|
432
415
|
|
433
|
-
menu =
|
416
|
+
menu = selected_books.empty? ? @nobook_popup : @book_popup
|
434
417
|
menu.popup(nil, nil, event.button, event.time)
|
435
418
|
end
|
436
419
|
end
|
@@ -447,7 +430,7 @@ module Alexandria
|
|
447
430
|
"Library '%s' selected, %d unrated books",
|
448
431
|
library.length) % [library.name,
|
449
432
|
library.length]
|
450
|
-
elsif n_unrated
|
433
|
+
elsif n_unrated.zero?
|
451
434
|
n_("Library '%s' selected, %d book",
|
452
435
|
"Library '%s' selected, %d books",
|
453
436
|
library.length) % [library.name,
|
@@ -487,7 +470,7 @@ module Alexandria
|
|
487
470
|
# selection = @library_listview.selection.selected ? @library_listview.selection.selected.has_focus? : false
|
488
471
|
|
489
472
|
# Focus is the wrong idiom here.
|
490
|
-
unless @clicking_on_sidepane
|
473
|
+
unless @clicking_on_sidepane || (@main_app.focus == @library_listview)
|
491
474
|
# unless @main_app.focus == @library_listview
|
492
475
|
|
493
476
|
log.debug { "Currently focused widget: #{@main_app.focus.inspect}" }
|
@@ -521,7 +504,7 @@ module Alexandria
|
|
521
504
|
BookProviders.each do |provider|
|
522
505
|
has_no_url = true
|
523
506
|
begin
|
524
|
-
has_no_url = (b.isbn.nil?
|
507
|
+
has_no_url = (b.isbn.nil? || b.isbn.strip.empty? || provider.url(b).nil?)
|
525
508
|
rescue => ex
|
526
509
|
log.warn { "Error determining URL from #{provider.name}; #{ex.message}" }
|
527
510
|
end
|
@@ -536,16 +519,16 @@ module Alexandria
|
|
536
519
|
@clicking_on_sidepane = false
|
537
520
|
end
|
538
521
|
|
539
|
-
def on_switch_page
|
522
|
+
def on_switch_page(_notebook, _page, page_num)
|
540
523
|
log.debug { 'on_switch_page' }
|
541
|
-
@actiongroup['ArrangeIcons'].sensitive =
|
524
|
+
@actiongroup['ArrangeIcons'].sensitive = page_num.zero?
|
542
525
|
on_books_selection_changed
|
543
526
|
end
|
544
527
|
|
545
528
|
def on_focus(widget, _event_focus)
|
546
|
-
if @clicking_on_sidepane
|
529
|
+
if @clicking_on_sidepane || (widget == @library_listview)
|
547
530
|
log.debug { 'on_focus: @library_listview' }
|
548
|
-
|
531
|
+
GLib::Idle.add do
|
549
532
|
%w(OnlineInformation SelectAll DeselectAll).each do |action|
|
550
533
|
@actiongroup[action].sensitive = false
|
551
534
|
end
|
@@ -559,7 +542,7 @@ module Alexandria
|
|
559
542
|
end
|
560
543
|
|
561
544
|
def determine_delete_option
|
562
|
-
sensitive = (@libraries.all_regular_libraries.length > 1
|
545
|
+
sensitive = (@libraries.all_regular_libraries.length > 1 || selected_library.is_a?(SmartLibrary))
|
563
546
|
sensitive
|
564
547
|
end
|
565
548
|
|
@@ -572,15 +555,10 @@ module Alexandria
|
|
572
555
|
select_this_book = proc do |bk, view|
|
573
556
|
@filtered_model.refilter
|
574
557
|
iter = iter_from_book bk
|
575
|
-
unless iter
|
576
|
-
next
|
577
|
-
end
|
558
|
+
next unless iter
|
578
559
|
path = iter.path
|
579
|
-
unless view.model
|
580
|
-
|
581
|
-
end
|
582
|
-
path = view.model.convert_path_to_child_path(path)
|
583
|
-
path = @filtered_model.convert_path_to_child_path(path)
|
560
|
+
next unless view.model
|
561
|
+
path = view_path_to_model_path(view, path)
|
584
562
|
log.debug { "Path for #{bk.ident} is #{path}" }
|
585
563
|
selection = view.respond_to?(:selection) ? @listview.selection : @iconview
|
586
564
|
selection.unselect_all
|
@@ -606,9 +584,7 @@ module Alexandria
|
|
606
584
|
@actiongroup['Redo'].sensitive = caller.can_redo?
|
607
585
|
elsif caller.is_a?(Library)
|
608
586
|
unless caller.updating?
|
609
|
-
|
610
|
-
handle_update_caller_library ary
|
611
|
-
end
|
587
|
+
handle_update_caller_library ary
|
612
588
|
end
|
613
589
|
else
|
614
590
|
raise 'unrecognized update event'
|
@@ -625,9 +601,7 @@ module Alexandria
|
|
625
601
|
append_book(book)
|
626
602
|
when Library::BOOK_UPDATED
|
627
603
|
iter = iter_from_ident(book.saved_ident)
|
628
|
-
if iter
|
629
|
-
fill_iter_with_book(iter, book)
|
630
|
-
end
|
604
|
+
fill_iter_with_book(iter, book) if iter
|
631
605
|
when Library::BOOK_REMOVED
|
632
606
|
@model.remove(iter_from_book(book))
|
633
607
|
end
|
@@ -646,41 +620,7 @@ module Alexandria
|
|
646
620
|
log.warn('Attempt to open browser with nil url')
|
647
621
|
return
|
648
622
|
end
|
649
|
-
|
650
|
-
launch_command = cmd
|
651
|
-
if cmd.downcase.index('%u')
|
652
|
-
launch_command = cmd.gsub(/%U/i, "\"" + url + "\"")
|
653
|
-
else
|
654
|
-
launch_command = cmd + " \"" + url + "\""
|
655
|
-
end
|
656
|
-
Thread.new { system(launch_command) }
|
657
|
-
else
|
658
|
-
ErrorDialog.new(@main_app,
|
659
|
-
_('Unable to launch the web browser'),
|
660
|
-
_('Check out that a web browser is ' \
|
661
|
-
'configured as default (Desktop ' \
|
662
|
-
'Preferences -> Advanced -> Preferred ' \
|
663
|
-
'Applications) and try again.'))
|
664
|
-
end
|
665
|
-
end
|
666
|
-
|
667
|
-
def open_email_client(url)
|
668
|
-
if (cmd = Preferences.instance.email_client)
|
669
|
-
launch_command = cmd
|
670
|
-
if cmd.downcase.index('%u')
|
671
|
-
launch_command = cmd.gsub(/%u/i, "\"" + url + "\"")
|
672
|
-
else
|
673
|
-
launch_command = cmd + " \"" + url + "\""
|
674
|
-
end
|
675
|
-
Thread.new { system(launch_command) }
|
676
|
-
else
|
677
|
-
ErrorDialog.new(@main_app,
|
678
|
-
_('Unable to launch the mail reader'),
|
679
|
-
_('Check out that a mail reader is ' \
|
680
|
-
'configured as default (Desktop ' \
|
681
|
-
'Preferences -> Advanced -> Preferred ' \
|
682
|
-
'Applications) and try again.'))
|
683
|
-
end
|
623
|
+
Gtk.show_uri url
|
684
624
|
end
|
685
625
|
|
686
626
|
def detach_old_libraries
|
@@ -719,7 +659,7 @@ module Alexandria
|
|
719
659
|
# entries.\n" )
|
720
660
|
|
721
661
|
@libraries.ruined_books.each { |bi|
|
722
|
-
new_message += "\n#{bi[1]
|
662
|
+
new_message += "\n#{bi[1] || bi[1].inspect}"
|
723
663
|
}
|
724
664
|
recovery_dialog = Gtk::MessageDialog.new(@main_app, Gtk::Dialog::MODAL,
|
725
665
|
Gtk::MessageDialog::WARNING,
|
@@ -727,17 +667,17 @@ module Alexandria
|
|
727
667
|
new_message).show
|
728
668
|
recovery_dialog.signal_connect('response') do |_dialog, response_type|
|
729
669
|
recovery_dialog.destroy
|
730
|
-
if response_type ==
|
670
|
+
if response_type == :ok
|
731
671
|
# progress indicator...
|
732
672
|
@progressbar.fraction = 0
|
733
|
-
@appbar.children.first.visible = true
|
673
|
+
@appbar.children.first.visible = true # show the progress bar
|
734
674
|
|
735
675
|
total_book_count = @libraries.ruined_books.size
|
736
676
|
fraction_per_book = 1.0 / total_book_count
|
737
677
|
prog_percentage = 0
|
738
678
|
|
739
679
|
@libraries.ruined_books.reverse!
|
740
|
-
|
680
|
+
GLib::Idle.add do
|
741
681
|
ruined_book = @libraries.ruined_books.pop
|
742
682
|
if ruined_book
|
743
683
|
book, isbn, library = ruined_book
|
@@ -746,7 +686,7 @@ module Alexandria
|
|
746
686
|
book = book_rslt[0]
|
747
687
|
cover_uri = book_rslt[1]
|
748
688
|
|
749
|
-
# TODO if the book was saved okay, make sure the old
|
689
|
+
# TODO: if the book was saved okay, make sure the old
|
750
690
|
# empty yaml file doesn't stick around esp if doing
|
751
691
|
# isbn-10 --> isbn-13 conversion...
|
752
692
|
if isbn.size == 10
|
@@ -818,18 +758,18 @@ module Alexandria
|
|
818
758
|
iter[Columns::PUBLISHER] = book.publisher
|
819
759
|
iter[Columns::PUBLISH_DATE] = book.publishing_year.to_s
|
820
760
|
iter[Columns::EDITION] = book.edition
|
821
|
-
iter[Columns::NOTES] = (book.notes
|
822
|
-
iter[Columns::LOANED_TO] = (book.loaned_to
|
823
|
-
rating = (book.rating
|
761
|
+
iter[Columns::NOTES] = (book.notes || '')
|
762
|
+
iter[Columns::LOANED_TO] = (book.loaned_to || '')
|
763
|
+
rating = (book.rating || Book::DEFAULT_RATING)
|
824
764
|
iter[Columns::RATING] = MAX_RATING_STARS - rating # ascending order is the default
|
825
765
|
iter[Columns::OWN] = book.own?
|
826
766
|
iter[Columns::REDD] = book.redd?
|
827
767
|
iter[Columns::WANT] = book.want?
|
828
|
-
if book.tags
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
768
|
+
iter[Columns::TAGS] = if book.tags
|
769
|
+
book.tags.join(',')
|
770
|
+
else
|
771
|
+
''
|
772
|
+
end
|
833
773
|
|
834
774
|
icon = Icons.cover(selected_library, book)
|
835
775
|
log.debug { "Setting icon #{icon} for book #{book.title}" }
|
@@ -916,45 +856,38 @@ module Alexandria
|
|
916
856
|
@iconview.freeze
|
917
857
|
@listview.freeze # NEW / bdewey
|
918
858
|
@progressbar.fraction = 0
|
919
|
-
@appbar.children.first.visible = true
|
859
|
+
@appbar.children.first.visible = true # show the progress bar
|
920
860
|
set_status_label(_("Loading '%s'...") % library.name)
|
921
861
|
total = library.length
|
922
862
|
log.debug { "library #{library.name} length #{library.length}" }
|
923
863
|
n = 0
|
924
864
|
|
925
|
-
|
865
|
+
GLib::Idle.add do
|
926
866
|
block_return = true
|
927
867
|
book = library[n]
|
928
868
|
if book
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
log.error { "append_books failed #{ex.message} #{trace}" }
|
935
|
-
end
|
936
|
-
# convert to percents
|
937
|
-
coeff = total / 100.0
|
938
|
-
percent = n / coeff
|
939
|
-
fraction = percent / 100
|
940
|
-
log.debug { "#index #{n} percent #{percent} fraction #{fraction}" }
|
941
|
-
@progressbar.fraction = fraction
|
942
|
-
n += 1
|
869
|
+
begin
|
870
|
+
append_book(book)
|
871
|
+
rescue => ex
|
872
|
+
trace = ex.backtrace.join("\n > ")
|
873
|
+
log.error { "append_books failed #{ex.message} #{trace}" }
|
943
874
|
end
|
875
|
+
fraction = n * 1.0 / total
|
876
|
+
log.debug { "#index #{n} fraction #{fraction}" }
|
877
|
+
@progressbar.fraction = fraction
|
878
|
+
n += 1
|
944
879
|
else
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
block_return = false
|
957
|
-
end
|
880
|
+
@iconview.unfreeze
|
881
|
+
@listview.unfreeze # NEW / bdewey
|
882
|
+
@filtered_model.refilter
|
883
|
+
@listview.columns_autosize
|
884
|
+
@progressbar.fraction = 1
|
885
|
+
# Hide the progress bar.
|
886
|
+
@appbar.children.first.visible = false
|
887
|
+
# Refresh the status bar.
|
888
|
+
on_books_selection_changed
|
889
|
+
@library_listview.set_sensitive(true)
|
890
|
+
block_return = false
|
958
891
|
end
|
959
892
|
|
960
893
|
block_return
|
@@ -989,57 +922,44 @@ module Alexandria
|
|
989
922
|
end
|
990
923
|
|
991
924
|
def iter_from_ident(ident)
|
992
|
-
log.debug {
|
925
|
+
log.debug { ident.to_s }
|
993
926
|
iter = @model.iter_first
|
994
927
|
ok = true
|
995
928
|
while ok
|
996
|
-
if iter[Columns::IDENT] == ident
|
997
|
-
return iter
|
998
|
-
end
|
929
|
+
return iter if iter[Columns::IDENT] == ident
|
999
930
|
ok = iter.next!
|
1000
931
|
end
|
1001
932
|
nil
|
1002
933
|
end
|
1003
934
|
|
1004
935
|
def iter_from_book(book)
|
1005
|
-
log.debug {
|
936
|
+
log.debug { book.to_s }
|
1006
937
|
iter_from_ident(book.ident)
|
1007
938
|
end
|
1008
939
|
|
1009
940
|
def collate_selected_books(page)
|
1010
|
-
|
941
|
+
result = []
|
1011
942
|
library = selected_library
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
# as it doesn't consider the filtering for some reason
|
1018
|
-
# see bug #24568
|
1019
|
-
unless view.model
|
1020
|
-
next
|
943
|
+
|
944
|
+
if page.zero?
|
945
|
+
result = @iconview.selected_items.map do |path|
|
946
|
+
path = view_path_to_model_path(@iconview, path)
|
947
|
+
book_from_iter(library, @model.get_iter(path))
|
1021
948
|
end
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
if iter
|
1029
|
-
a << book_from_iter(library, iter)
|
1030
|
-
end
|
1031
|
-
end
|
1032
|
-
# This used to cause a crash when manually adding the first
|
1033
|
-
# book to a Library displayed in an Iconview
|
1034
|
-
# TODO find root cause of this
|
949
|
+
else
|
950
|
+
selection = @listview.selection
|
951
|
+
rows, _model = selection.selected_rows
|
952
|
+
result = rows.map do |path|
|
953
|
+
path = view_path_to_model_path(@listview, path)
|
954
|
+
book_from_iter(library, @model.get_iter(path))
|
1035
955
|
end
|
1036
956
|
end
|
1037
|
-
|
957
|
+
|
958
|
+
result
|
1038
959
|
end
|
1039
960
|
|
1040
961
|
def selected_books
|
1041
|
-
|
1042
|
-
selected = a.select { |x| !x.nil? }
|
962
|
+
selected = collate_selected_books(@notebook.page).compact
|
1043
963
|
log.debug { "Selected books = #{selected.inspect}" }
|
1044
964
|
selected
|
1045
965
|
end
|
@@ -1054,9 +974,7 @@ module Alexandria
|
|
1054
974
|
# Disable the selected library in the move libraries actions.
|
1055
975
|
@libraries.all_regular_libraries.each do |i_library|
|
1056
976
|
action = @actiongroup[i_library.action_name]
|
1057
|
-
if action
|
1058
|
-
action.sensitive = i_library != library
|
1059
|
-
end
|
977
|
+
action.sensitive = i_library != library if action
|
1060
978
|
end
|
1061
979
|
sensitize_library library
|
1062
980
|
end
|
@@ -1064,7 +982,7 @@ module Alexandria
|
|
1064
982
|
def sensitize_library(library)
|
1065
983
|
smart = library.is_a?(SmartLibrary)
|
1066
984
|
log.debug { "sensitize_library: smartlibrary = #{smart}" }
|
1067
|
-
|
985
|
+
GLib::Idle.add do
|
1068
986
|
@actiongroup['AddBook'].sensitive = !smart
|
1069
987
|
@actiongroup['AddBookManual'].sensitive = !smart
|
1070
988
|
@actiongroup['Properties'].sensitive = smart
|
@@ -1132,7 +1050,7 @@ module Alexandria
|
|
1132
1050
|
@prefs.selected_library = selected_library.name
|
1133
1051
|
cols_width = {}
|
1134
1052
|
@listview.columns.each do |c|
|
1135
|
-
cols_width[c.title] =
|
1053
|
+
cols_width[c.title] = c.width
|
1136
1054
|
end
|
1137
1055
|
@prefs.cols_width = '{' + cols_width.to_a.map do |t, v|
|
1138
1056
|
'"' + t + '": ' + v.to_s
|
@@ -1149,7 +1067,7 @@ module Alexandria
|
|
1149
1067
|
|
1150
1068
|
def move_selected_books_to_library(library)
|
1151
1069
|
books = selected_books.select do |book|
|
1152
|
-
!library.include?(book)
|
1070
|
+
!library.include?(book) ||
|
1153
1071
|
ConflictWhileCopyingDialog.new(@main_app,
|
1154
1072
|
library,
|
1155
1073
|
book).replace?
|
@@ -1159,7 +1077,7 @@ module Alexandria
|
|
1159
1077
|
|
1160
1078
|
def setup_move_actions
|
1161
1079
|
@actiongroup.actions.each do |action|
|
1162
|
-
next unless /^MoveIn
|
1080
|
+
next unless /^MoveIn/ =~ action.name
|
1163
1081
|
@actiongroup.remove_action(action)
|
1164
1082
|
end
|
1165
1083
|
actions = []
|
@@ -1178,7 +1096,7 @@ module Alexandria
|
|
1178
1096
|
['ui/MainMenubar/EditMenu/Move/',
|
1179
1097
|
'ui/BookPopup/Move/'].each do |path|
|
1180
1098
|
@uimanager.add_ui(@move_mid, path, name, name,
|
1181
|
-
|
1099
|
+
:menuitem, false)
|
1182
1100
|
end
|
1183
1101
|
end
|
1184
1102
|
end
|
@@ -1196,11 +1114,8 @@ module Alexandria
|
|
1196
1114
|
def library_sort_order
|
1197
1115
|
# added by Cathal Mc Ginley, 23 Oct 2007
|
1198
1116
|
log.debug { "library_sort_order #{@notebook.page}: #{@iconview.model.inspect} #{@listview.model.inspect}" }
|
1199
|
-
|
1200
|
-
if
|
1201
|
-
sort_column = sorted_on[0]
|
1202
|
-
sort_order = sorted_on[1]
|
1203
|
-
|
1117
|
+
result, sort_column, sort_order = current_view.model.sort_column_id
|
1118
|
+
if result
|
1204
1119
|
column_ids_to_attributes = { 2 => :title,
|
1205
1120
|
4 => :authors,
|
1206
1121
|
5 => :isbn,
|
@@ -1212,8 +1127,8 @@ module Alexandria
|
|
1212
1127
|
14 => :want,
|
1213
1128
|
9 => :rating }
|
1214
1129
|
|
1215
|
-
sort_attribute = column_ids_to_attributes
|
1216
|
-
ascending = (sort_order ==
|
1130
|
+
sort_attribute = column_ids_to_attributes.fetch sort_column
|
1131
|
+
ascending = (sort_order == :ascending)
|
1217
1132
|
LibrarySortOrder.new(sort_attribute, ascending)
|
1218
1133
|
else
|
1219
1134
|
LibrarySortOrder::Unsorted.new
|
@@ -1260,7 +1175,7 @@ module Alexandria
|
|
1260
1175
|
end
|
1261
1176
|
|
1262
1177
|
def remove_library_separator
|
1263
|
-
if !@library_separator_iter.nil?
|
1178
|
+
if !@library_separator_iter.nil? && @libraries.all_smart_libraries.empty?
|
1264
1179
|
@library_listview.model.remove(@library_separator_iter)
|
1265
1180
|
@library_separator_iter = nil
|
1266
1181
|
end
|
@@ -1291,14 +1206,21 @@ module Alexandria
|
|
1291
1206
|
ICONS_SORTS = [
|
1292
1207
|
Columns::TITLE, Columns::AUTHORS, Columns::ISBN,
|
1293
1208
|
Columns::PUBLISHER, Columns::EDITION, Columns::RATING, Columns::REDD, Columns::OWN, Columns::WANT
|
1294
|
-
]
|
1209
|
+
].freeze
|
1295
1210
|
|
1296
1211
|
def setup_books_iconview_sorting
|
1297
|
-
sort_order = @prefs.reverse_icons ?
|
1212
|
+
sort_order = @prefs.reverse_icons ? :descending : :ascending
|
1298
1213
|
mode = ICONS_SORTS[@prefs.arrange_icons_mode]
|
1299
1214
|
@iconview_model.set_sort_column_id(mode, sort_order)
|
1300
1215
|
@filtered_model.refilter # force redraw
|
1301
1216
|
end
|
1217
|
+
|
1218
|
+
private
|
1219
|
+
|
1220
|
+
def view_path_to_model_path(view, path)
|
1221
|
+
path = view.model.convert_path_to_child_path(path)
|
1222
|
+
@filtered_model.convert_path_to_child_path(path) if path
|
1223
|
+
end
|
1302
1224
|
end
|
1303
1225
|
end
|
1304
1226
|
end
|