alexandria-book-collection-manager 0.7.3 → 0.7.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +9 -0
  3. data/.github/workflows/ruby.yml +77 -0
  4. data/.gitignore +4 -1
  5. data/.rubocop.yml +86 -36
  6. data/.rubocop_todo.yml +58 -161
  7. data/.simplecov +5 -2
  8. data/CHANGELOG.md +56 -2
  9. data/Gemfile +4 -3
  10. data/INSTALL.md +23 -11
  11. data/README.md +52 -41
  12. data/Rakefile +78 -75
  13. data/alexandria-book-collection-manager.gemspec +50 -44
  14. data/bin/alexandria +12 -22
  15. data/doc/FAQ +1 -2
  16. data/doc/dependency_decisions.yml +27 -8
  17. data/lib/alexandria.rb +25 -23
  18. data/lib/alexandria/about.rb +50 -50
  19. data/lib/alexandria/book_providers.rb +86 -91
  20. data/lib/alexandria/book_providers/adlibris.rb +37 -74
  21. data/lib/alexandria/book_providers/amazon_aws.rb +94 -100
  22. data/lib/alexandria/book_providers/amazon_ecs_util.rb +289 -324
  23. data/lib/alexandria/book_providers/barnes_and_noble.rb +42 -42
  24. data/lib/alexandria/book_providers/douban.rb +25 -41
  25. data/lib/alexandria/book_providers/proxis.rb +34 -29
  26. data/lib/alexandria/book_providers/pseudomarc.rb +77 -85
  27. data/lib/alexandria/book_providers/siciliano.rb +60 -64
  28. data/lib/alexandria/book_providers/thalia_provider.rb +161 -0
  29. data/lib/alexandria/book_providers/web.rb +5 -5
  30. data/lib/alexandria/book_providers/worldcat.rb +66 -95
  31. data/lib/alexandria/book_providers/z3950.rb +153 -169
  32. data/lib/alexandria/config.rb +1 -1
  33. data/lib/alexandria/console.rb +3 -3
  34. data/lib/alexandria/default_preferences.rb +37 -0
  35. data/lib/alexandria/execution_queue.rb +13 -12
  36. data/lib/alexandria/export_format.rb +8 -8
  37. data/lib/alexandria/export_library.rb +128 -127
  38. data/lib/alexandria/import_library.rb +102 -126
  39. data/lib/alexandria/import_library_csv.rb +41 -41
  40. data/lib/alexandria/library_collection.rb +6 -5
  41. data/lib/alexandria/library_sort_order.rb +4 -2
  42. data/lib/alexandria/library_store.rb +39 -28
  43. data/lib/alexandria/logging.rb +10 -14
  44. data/lib/alexandria/models/book.rb +5 -4
  45. data/lib/alexandria/models/library.rb +63 -53
  46. data/lib/alexandria/net.rb +5 -6
  47. data/lib/alexandria/preferences.rb +66 -63
  48. data/lib/alexandria/scanners.rb +2 -2
  49. data/lib/alexandria/scanners/{cuecat.rb → cue_cat.rb} +17 -17
  50. data/lib/alexandria/scanners/keyboard.rb +8 -8
  51. data/lib/alexandria/smart_library.rb +110 -112
  52. data/lib/alexandria/ui.rb +15 -15
  53. data/lib/alexandria/ui/{dialogs/about_dialog.rb → about_dialog.rb} +2 -2
  54. data/lib/alexandria/ui/{dialogs/acquire_dialog.rb → acquire_dialog.rb} +108 -109
  55. data/lib/alexandria/ui/alert_dialog.rb +66 -0
  56. data/lib/alexandria/ui/{dialogs/bad_isbns_dialog.rb → bad_isbns_dialog.rb} +13 -9
  57. data/lib/alexandria/ui/{dialogs/barcode_animation.rb → barcode_animation.rb} +16 -15
  58. data/lib/alexandria/ui/{dialogs/book_properties_dialog.rb → book_properties_dialog.rb} +25 -38
  59. data/lib/alexandria/ui/{dialogs/book_properties_dialog_base.rb → book_properties_dialog_base.rb} +64 -157
  60. data/lib/alexandria/ui/builder_base.rb +1 -1
  61. data/lib/alexandria/ui/calendar_popup.rb +58 -0
  62. data/lib/alexandria/ui/callbacks.rb +187 -155
  63. data/lib/alexandria/ui/completion_models.rb +8 -22
  64. data/lib/alexandria/ui/confirm_erase_dialog.rb +33 -0
  65. data/lib/alexandria/ui/conflict_while_copying_dialog.rb +34 -0
  66. data/lib/alexandria/ui/dndable.rb +7 -7
  67. data/lib/alexandria/ui/error_dialog.rb +25 -0
  68. data/lib/alexandria/ui/{dialogs/export_dialog.rb → export_dialog.rb} +37 -58
  69. data/lib/alexandria/ui/icons.rb +38 -43
  70. data/lib/alexandria/ui/iconview.rb +12 -10
  71. data/lib/alexandria/ui/iconview_tooltips.rb +41 -54
  72. data/lib/alexandria/ui/import_dialog.rb +157 -0
  73. data/lib/alexandria/ui/init.rb +30 -41
  74. data/lib/alexandria/ui/{dialogs/keep_bad_isbn_dialog.rb → keep_bad_isbn_dialog.rb} +9 -6
  75. data/lib/alexandria/ui/libraries_combo.rb +15 -14
  76. data/lib/alexandria/ui/listview.rb +69 -67
  77. data/lib/alexandria/ui/main_app.rb +24 -26
  78. data/lib/alexandria/ui/misc_dialogs.rb +10 -0
  79. data/lib/alexandria/ui/multi_drag_treeview.rb +8 -9
  80. data/lib/alexandria/ui/{dialogs/new_book_dialog.rb → new_book_dialog.rb} +113 -114
  81. data/lib/alexandria/ui/{dialogs/new_book_dialog_manual.rb → new_book_dialog_manual.rb} +22 -19
  82. data/lib/alexandria/ui/new_provider_dialog.rb +100 -0
  83. data/lib/alexandria/ui/new_smart_library_dialog.rb +74 -0
  84. data/lib/alexandria/ui/preferences_dialog.rb +313 -0
  85. data/lib/alexandria/ui/provider_preferences_base_dialog.rb +95 -0
  86. data/lib/alexandria/ui/provider_preferences_dialog.rb +35 -0
  87. data/lib/alexandria/ui/{dialogs/misc_dialogs.rb → really_delete_dialog.rb} +7 -28
  88. data/lib/alexandria/ui/{sidepane.rb → sidepane_manager.rb} +53 -48
  89. data/lib/alexandria/ui/skip_entry_dialog.rb +33 -0
  90. data/lib/alexandria/ui/smart_library_properties_dialog.rb +60 -0
  91. data/lib/alexandria/ui/smart_library_properties_dialog_base.rb +242 -0
  92. data/lib/alexandria/ui/smart_library_rule_box.rb +119 -0
  93. data/lib/alexandria/ui/sound.rb +11 -13
  94. data/lib/alexandria/ui/ui_manager.rb +216 -200
  95. data/lib/alexandria/version.rb +4 -19
  96. data/lib/alexandria/web_themes.rb +21 -21
  97. data/po/Makefile +2 -2
  98. data/po/cs.po +992 -875
  99. data/po/cy.po +961 -874
  100. data/po/de.po +990 -865
  101. data/po/el.po +989 -865
  102. data/po/es.po +985 -861
  103. data/po/fr.po +987 -867
  104. data/po/ga.po +908 -820
  105. data/po/gl.po +980 -860
  106. data/po/it.po +986 -864
  107. data/po/ja.po +969 -849
  108. data/po/mk.po +984 -860
  109. data/po/nb.po +979 -859
  110. data/po/nl.po +983 -860
  111. data/po/pl.po +1018 -971
  112. data/po/pt.po +988 -857
  113. data/po/pt_BR.po +983 -863
  114. data/po/ru.po +994 -871
  115. data/po/sk.po +989 -867
  116. data/po/sv.po +976 -856
  117. data/po/uk.po +972 -858
  118. data/po/zh_TW.po +974 -854
  119. data/schemas/alexandria.schemas +24 -2
  120. data/share/alexandria/glade/acquire_dialog__builder.glade +1 -1
  121. data/share/alexandria/glade/book_properties_dialog__builder.glade +1 -1
  122. data/share/alexandria/glade/main_app__builder.glade +6 -21
  123. data/share/alexandria/glade/new_book_dialog__builder.glade +1 -1
  124. data/share/alexandria/glade/preferences_dialog__builder.glade +1 -1
  125. data/share/gnome/help/alexandria/C/introduction.xml +0 -4
  126. data/share/gnome/help/alexandria/C/searching.xml +1 -1
  127. data/share/gnome/help/alexandria/C/smart-libraries.xml +2 -2
  128. data/share/gnome/help/alexandria/C/working-with-libraries.xml +1 -1
  129. data/share/gnome/help/alexandria/fr/alexandria.xml +1 -1
  130. data/share/gnome/help/alexandria/ja/introduction.xml +0 -4
  131. data/share/gnome/help/alexandria/ja/smart-libraries.xml +1 -1
  132. data/spec/alexandria/book_providers/thalia_provider_spec.rb +119 -0
  133. data/spec/alexandria/book_providers/world_cat_provider_spec.rb +160 -0
  134. data/spec/alexandria/book_providers_spec.rb +62 -156
  135. data/spec/alexandria/book_spec.rb +12 -10
  136. data/spec/alexandria/console_spec.rb +6 -11
  137. data/spec/alexandria/export_library_spec.rb +47 -58
  138. data/spec/alexandria/library_spec.rb +121 -109
  139. data/spec/alexandria/library_store_spec.rb +8 -8
  140. data/spec/alexandria/preferences_spec.rb +44 -17
  141. data/spec/alexandria/scanners/cue_cat_spec.rb +52 -0
  142. data/spec/alexandria/smart_library_spec.rb +15 -15
  143. data/spec/alexandria/ui/about_dialog_spec.rb +14 -0
  144. data/spec/alexandria/ui/acquire_dialog_spec.rb +14 -0
  145. data/spec/alexandria/ui/alert_dialog_spec.rb +16 -0
  146. data/spec/alexandria/ui/bad_isbns_dialog_spec.rb +14 -0
  147. data/spec/alexandria/ui/book_properties_dialog_spec.rb +59 -0
  148. data/spec/alexandria/ui/confirm_erase_dialog_spec.rb +14 -0
  149. data/spec/alexandria/ui/conflict_while_copying_dialog_spec.rb +16 -0
  150. data/spec/alexandria/ui/error_dialog_spec.rb +14 -0
  151. data/spec/alexandria/ui/export_dialog_spec.rb +36 -0
  152. data/spec/alexandria/ui/icons_spec.rb +26 -0
  153. data/spec/alexandria/ui/iconview_spec.rb +7 -21
  154. data/spec/alexandria/ui/import_dialog_spec.rb +46 -0
  155. data/spec/alexandria/ui/keep_bad_isbn_dialog_spec.rb +17 -0
  156. data/spec/alexandria/ui/main_app_spec.rb +7 -34
  157. data/spec/alexandria/ui/new_book_dialog_manual_spec.rb +51 -0
  158. data/spec/alexandria/ui/{dialogs/new_book_dialog_spec.rb → new_book_dialog_spec.rb} +4 -4
  159. data/spec/alexandria/ui/new_provider_dialog_spec.rb +30 -0
  160. data/spec/alexandria/ui/new_smart_library_dialog_spec.rb +39 -0
  161. data/spec/alexandria/ui/preferences_dialog_spec.rb +14 -0
  162. data/spec/alexandria/ui/provider_preferences_dialog_spec.rb +34 -0
  163. data/spec/alexandria/ui/really_delete_dialog_spec.rb +16 -0
  164. data/spec/alexandria/ui/sidepane_manager_spec.rb +15 -0
  165. data/spec/alexandria/ui/skip_entry_dialog_spec.rb +14 -0
  166. data/spec/alexandria/ui/smart_library_properties_dialog_spec.rb +49 -0
  167. data/spec/alexandria/ui/sound_spec.rb +2 -2
  168. data/spec/alexandria/ui/ui_manager_spec.rb +44 -20
  169. data/spec/end_to_end/basic_run_spec.rb +21 -38
  170. data/spec/fixtures/cover.jpg +0 -0
  171. data/spec/spec_helper.rb +54 -10
  172. data/tasks/setup.rb +2 -2
  173. data/tasks/spec.rake +11 -11
  174. data/util/rake/fileinstall.rb +38 -35
  175. data/util/rake/gettextgenerate.rb +7 -7
  176. data/util/rake/omfgenerate.rb +7 -7
  177. metadata +158 -45
  178. data/dogtail/basic_run_test.py +0 -9
  179. data/lib/alexandria/book_providers/renaud.rb +0 -155
  180. data/lib/alexandria/book_providers/thalia.rb +0 -198
  181. data/lib/alexandria/ui/dialogs/alert_dialog.rb +0 -63
  182. data/lib/alexandria/ui/dialogs/import_dialog.rb +0 -176
  183. data/lib/alexandria/ui/dialogs/new_smart_library_dialog.rb +0 -62
  184. data/lib/alexandria/ui/dialogs/preferences_dialog.rb +0 -563
  185. data/lib/alexandria/ui/dialogs/smart_library_properties_dialog.rb +0 -61
  186. data/lib/alexandria/ui/dialogs/smart_library_properties_dialog_base.rb +0 -423
  187. data/spec/alexandria/scanners/cuecat_spec.rb +0 -67
  188. data/spec/alexandria/ui/dialogs_spec.rb +0 -162
  189. data/spec/alexandria/ui/sidepane_spec.rb +0 -29
  190. data/spec/alexandria/ui/ui_utilities_spec.rb +0 -62
  191. data/spec/alexandria/utilities_spec.rb +0 -52
  192. data/tasks/dogtail.rake +0 -6
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of Alexandria.
4
+ #
5
+ # See the file README.md for authorship and licensing information.
6
+
7
+ require "alexandria/ui/calendar_popup"
8
+ require "alexandria/ui/smart_library_rule_box"
9
+
10
+ module Alexandria
11
+ module UI
12
+ class SmartLibraryPropertiesDialogBase
13
+ include Logging
14
+ include CalendarPopup
15
+ include GetText
16
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
17
+
18
+ attr_reader :predicate_operator_rule, :dialog
19
+
20
+ def initialize(parent)
21
+ @dialog = Gtk::Dialog.new(title: "",
22
+ parent: parent,
23
+ flags: :modal,
24
+ buttons: [[Gtk::Stock::HELP, :help]])
25
+
26
+ @dialog.window_position = :center
27
+ @dialog.resizable = true
28
+ @dialog.border_width = 4
29
+ @dialog.child.border_width = 12
30
+
31
+ main_box = Gtk::Box.new :vertical
32
+ main_box.border_width = 4
33
+ main_box.spacing = 8
34
+
35
+ @dialog.child << main_box
36
+
37
+ @smart_library_rules = []
38
+
39
+ @rules_header_box = Gtk::Box.new :horizontal
40
+ @rules_header_box.spacing = 2
41
+
42
+ @rules_box = Gtk::Box.new :vertical
43
+ @rules_box.spacing = 8
44
+ @rules_box.border_width = 8
45
+
46
+ scrollview = Gtk::ScrolledWindow.new
47
+ scrollview.hscrollbar_policy = :never
48
+ scrollview.vscrollbar_policy = :automatic
49
+ scrollview.set_size_request(-1, 125)
50
+ scrollview.add_with_viewport(@rules_box)
51
+
52
+ main_box.pack_start(@rules_header_box, expand: false, fill: false)
53
+ main_box << scrollview
54
+ end
55
+
56
+ def handle_date_icon_press(widget, primary, _icon)
57
+ display_calendar_popup(widget) if primary.nick == "primary"
58
+ end
59
+
60
+ def handle_add_rule_clicked
61
+ insert_new_rule
62
+ end
63
+
64
+ def handle_remove_rule_clicked(box_controller)
65
+ remove_rule_box(box_controller.rule_box)
66
+ end
67
+
68
+ # TODO: Move logic to SmartLibraryRuleBox
69
+ def apply_smart_rule_for_rule_box(rule_box, operand, operation)
70
+ idx = @rules_box.children.index(rule_box)
71
+ smart_library_rules[idx] ||= SmartLibrary::Rule.new(operand,
72
+ operation.first,
73
+ nil)
74
+ new_rule = smart_library_rules[idx]
75
+ new_rule.operand = operand
76
+ new_rule.operation = operation.first
77
+ new_rule.value = nil
78
+ end
79
+
80
+ protected
81
+
82
+ attr_reader :smart_library_rules
83
+
84
+ def has_weirdnesses?
85
+ fill_smart_library_rules_values
86
+ smart_library_rules.each do |rule|
87
+ return true if rule.value == ""
88
+ end
89
+ false
90
+ end
91
+
92
+ def user_confirms_possible_weirdnesses_before_saving?
93
+ return true unless has_weirdnesses?
94
+
95
+ dialog = AlertDialog.new(
96
+ @dialog,
97
+ _("Empty or conflictive condition"),
98
+ Gtk::Stock::DIALOG_QUESTION,
99
+ [[Gtk::Stock::CANCEL, Gtk::ResponseType::CANCEL],
100
+ [_("_Save However"), Gtk::ResponseType::YES]],
101
+ _("This smart library contains one or more conditions " \
102
+ "which are empty or conflict with each other. This is " \
103
+ "likely to result in never matching a book. Are you " \
104
+ "sure you want to save this library?"))
105
+ dialog.default_response = Gtk::ResponseType::CANCEL
106
+ dialog.show_all
107
+ confirmed = dialog.run == Gtk::ResponseType::YES
108
+ dialog.destroy
109
+ confirmed
110
+ end
111
+
112
+ def update_rules_header_box(predicate_operator_rule = SmartLibrary::ALL_RULES)
113
+ @rules_header_box.children.each { |x| @rules_header_box.remove(x) }
114
+
115
+ if @rules_box.children.length > 1
116
+ label1 = Gtk::Label.new
117
+ label1.set_alignment(0.0, 0.5)
118
+ label1.text = _("Match")
119
+
120
+ cb = Gtk::ComboBoxText.new
121
+ [_("all"), _("any")].each { |x| cb.append_text(x) }
122
+ cb.signal_connect("changed") do
123
+ @predicate_operator_rule =
124
+ cb.active.zero? ? SmartLibrary::ALL_RULES : SmartLibrary::ANY_RULE
125
+ end
126
+ cb.active =
127
+ predicate_operator_rule == SmartLibrary::ALL_RULES ? 0 : 1
128
+
129
+ label2 = Gtk::Label.new
130
+ label2.set_alignment(0.0, 0.5)
131
+ label2.text = _("of the following rules:")
132
+
133
+ @rules_header_box.pack_start(label1, expand: false, fill: false)
134
+ @rules_header_box.pack_start(cb, expand: false, fill: false)
135
+ @rules_header_box.pack_start(label2, expand: false, fill: false)
136
+ else
137
+ label = Gtk::Label.new
138
+ label.set_alignment(0.0, 0.5)
139
+ label.text = _("Match the following rule:")
140
+ @rules_header_box << label
141
+ @predicate_operator_rule = SmartLibrary::ALL_RULES
142
+ end
143
+
144
+ @rules_header_box.show_all
145
+ end
146
+
147
+ def make_rule_box(rule = nil)
148
+ box_controller = SmartLibraryRuleBox.new self
149
+ rule_box = box_controller.rule_box
150
+ rule_box.show_all
151
+ @rules_box.pack_start(rule_box, expand: false, fill: true)
152
+
153
+ if rule
154
+ operands = SmartLibrary::Rule::Operands::LEFT
155
+ operand_idx = operands.index(rule.operand)
156
+ operations =
157
+ SmartLibrary::Rule.operations_for_operand(rule.operand)
158
+ operation_idx = operations.map(&:first).index(rule.operation)
159
+
160
+ if !operand_idx.nil? && !operation_idx.nil?
161
+ box_controller.left_operand_combo.active = operand_idx
162
+ box_controller.operator_combo.active = operation_idx
163
+ unless rule.value.nil?
164
+ case rule.value
165
+ when String
166
+ box_controller.value_entry.text = rule.value
167
+ when Time
168
+ box_controller.date_entry.text = format_date(rule.value)
169
+ end
170
+ end
171
+ end
172
+ else
173
+ box_controller.left_operand_combo.active = 0
174
+ end
175
+ end
176
+
177
+ def insert_new_rule(rule = nil)
178
+ make_rule_box(rule)
179
+ @rules_box.check_resize # force a layout
180
+ update_rules_header_box
181
+ sensitize_remove_rule_buttons
182
+ end
183
+
184
+ def remove_rule_box(rule_box)
185
+ idx = @rules_box.children.index(rule_box)
186
+ raise if idx.nil?
187
+
188
+ smart_library_rules.delete_at(idx)
189
+ @rules_box.remove(rule_box)
190
+ sensitize_remove_rule_buttons
191
+ update_rules_header_box
192
+ end
193
+
194
+ def sensitize_remove_rule_buttons
195
+ boxes = @rules_box.children
196
+ state = boxes.length > 1
197
+ boxes.each do |box|
198
+ button = box.children[-1]
199
+ button.sensitive = state if button.is_a?(Gtk::Button)
200
+ end
201
+ end
202
+
203
+ def fill_smart_library_rules_values
204
+ @rules_box.children.each_with_index do |box, i|
205
+ entry, date = box.children[2..3]
206
+ value = nil
207
+ if entry.visible?
208
+ value = entry.text.strip
209
+ elsif date.visible?
210
+ begin
211
+ value = parse_date(date.text)
212
+ rescue StandardError => ex
213
+ trace = ex.backtrace.join("\n > ")
214
+ log.warn { "Possibly invalid date entered #{ex.message}" }
215
+ log.warn { "Date widget returned #{date.text} / #{trace}" }
216
+ # user entered some non-date...
217
+ # default to current time, for the moment
218
+ value = Time.now
219
+ end
220
+ end
221
+ smart_library_rules[i].value = value
222
+ end
223
+ end
224
+
225
+ def parse_date(datestring)
226
+ # '%m/%d/%Y' for USA and Canada ; or '%Y-%m-%d' for most of Asia
227
+ # http://en.wikipedia.org/wiki/Calendar_date#Middle_endian_forms.2C_starting_with_the_month
228
+ date_format = "%d/%m/%Y"
229
+ begin
230
+ d = Date.strptime(datestring, date_format)
231
+ Time.gm(d.year, d.month, d.day)
232
+ rescue StandardError
233
+ nil
234
+ end
235
+ end
236
+
237
+ def format_date(datetime)
238
+ datetime.strftime("%d/%m/%Y")
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of Alexandria.
4
+ #
5
+ # See the file README.md for authorship and licensing information.
6
+
7
+ module Alexandria
8
+ module UI
9
+ class SmartLibraryRuleBox
10
+ attr_accessor :rule_box,
11
+ :left_operand_combo, :operator_combo,
12
+ :value_entry, :date_entry, :entry_label,
13
+ :add_button, :remove_button
14
+
15
+ def initialize(parent)
16
+ @parent = parent
17
+
18
+ self.rule_box = Gtk::Box.new :horizontal
19
+ rule_box.spacing = 8
20
+
21
+ self.left_operand_combo = Gtk::ComboBoxText.new
22
+ self.operator_combo = Gtk::ComboBoxText.new
23
+
24
+ self.value_entry = Gtk::Entry.new
25
+
26
+ self.date_entry = Gtk::Entry.new.tap do |entry|
27
+ entry.primary_icon_name = Gtk::Stock::EDIT
28
+
29
+ entry.primary_icon_activatable = true
30
+ entry.signal_connect("icon-press") do |widget, primary, icon|
31
+ @parent.handle_date_icon_press(widget, primary, icon)
32
+ end
33
+ end
34
+
35
+ self.entry_label = Gtk::Label.new("")
36
+
37
+ self.add_button = Gtk::Button.new(label: "").tap do |widget|
38
+ widget.remove(widget.children.first)
39
+ widget << Gtk::Image.new(stock: Gtk::Stock::ADD,
40
+ size: Gtk::IconSize::BUTTON)
41
+
42
+ widget.signal_connect("clicked") { @parent.handle_add_rule_clicked }
43
+ end
44
+
45
+ self.remove_button = Gtk::Button.new(label: "")
46
+ remove_button.remove(remove_button.children.first)
47
+ remove_button << Gtk::Image.new(stock: Gtk::Stock::REMOVE,
48
+ size: Gtk::IconSize::BUTTON)
49
+
50
+ remove_button.signal_connect("clicked") do |_button|
51
+ @parent.handle_remove_rule_clicked(self)
52
+ end
53
+
54
+ operands.each do |operand|
55
+ left_operand_combo.append_text(operand.name)
56
+ end
57
+
58
+ operator_combo.signal_connect("changed") do
59
+ handle_operator_changed
60
+ end
61
+
62
+ left_operand_combo.signal_connect("changed") do
63
+ handle_left_operand_changed
64
+ end
65
+
66
+ rule_box.pack_start(left_operand_combo, expand: false, fill: false)
67
+ rule_box.pack_start(operator_combo, expand: false, fill: false)
68
+ rule_box.pack_start(value_entry)
69
+ rule_box.pack_start(date_entry)
70
+ rule_box.pack_start(entry_label, expand: false, fill: false)
71
+ rule_box.pack_end(remove_button, expand: false, fill: false)
72
+ rule_box.pack_end(add_button, expand: false, fill: false)
73
+
74
+ value_entry.visible = date_entry.visible = entry_label.visible = false
75
+ end
76
+
77
+ def operands
78
+ SmartLibrary::Rule::Operands::LEFT
79
+ end
80
+
81
+ def handle_operator_changed
82
+ operand = operands[left_operand_combo.active]
83
+ operations = SmartLibrary::Rule.operations_for_operand(operand)
84
+ operation = operations[operator_combo.active]
85
+
86
+ value_entry.visible = date_entry.visible = entry_label.visible = false
87
+ right_operand = operation.last
88
+ unless right_operand.nil?
89
+ entry = case right_operand.klass.name
90
+ when "Time"
91
+ date_entry
92
+ else
93
+ value_entry
94
+ end
95
+ entry.visible = true
96
+ unless right_operand.name.nil?
97
+ entry_label.text = right_operand.name
98
+ entry_label.visible = true
99
+ end
100
+ end
101
+
102
+ @parent.apply_smart_rule_for_rule_box(rule_box, operand, operation)
103
+ end
104
+
105
+ def handle_left_operand_changed
106
+ operand = operands[left_operand_combo.active]
107
+ operator_combo.freeze_notify do
108
+ operator_combo.remove_all
109
+ operations = SmartLibrary::Rule.operations_for_operand(operand)
110
+ operations.each do |operation|
111
+ operator = operation.first
112
+ operator_combo.append_text(operator.name)
113
+ end
114
+ operator_combo.active = 0
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -22,7 +22,7 @@
22
22
  # Boston, MA 02110-1301 USA.
23
23
  #++
24
24
 
25
- require 'gst'
25
+ require "gst"
26
26
 
27
27
  module Alexandria
28
28
  module UI
@@ -39,20 +39,20 @@ module Alexandria
39
39
  def play(effect)
40
40
  file = File.join(@sounds_dir, "#{effect}.ogg")
41
41
  if @playing
42
- puts "Already playing #{effect}." if $DEBUG
42
+ log.debug { "Already playing #{effect}." }
43
43
  else
44
- puts "Not playing. Starting #{effect}." if $DEBUG
44
+ log.debug { "Not playing. Starting #{effect}." }
45
45
  @filesrc.location = file
46
46
  start_playback
47
47
  end
48
48
  end
49
49
 
50
50
  def set_up_pipeline
51
- @filesrc = Gst::ElementFactory.make('filesrc')
52
- demuxer = Gst::ElementFactory.make('oggdemux')
53
- decoder = Gst::ElementFactory.make('vorbisdec')
54
- converter = Gst::ElementFactory.make('audioconvert') # #??
55
- audiosink = Gst::ElementFactory.make('autoaudiosink')
51
+ @filesrc = Gst::ElementFactory.make("filesrc")
52
+ demuxer = Gst::ElementFactory.make("oggdemux")
53
+ decoder = Gst::ElementFactory.make("vorbisdec")
54
+ converter = Gst::ElementFactory.make("audioconvert") # #??
55
+ audiosink = Gst::ElementFactory.make("autoaudiosink")
56
56
 
57
57
  @ogg_vorbis_pipeline.add(@filesrc, demuxer, decoder,
58
58
  converter, audiosink)
@@ -61,7 +61,7 @@ module Alexandria
61
61
  # this next must be a dynamic link, as demuxers potentially
62
62
  # have multiple src pads (for audio/video muxed streams)
63
63
 
64
- demuxer.signal_connect('pad-added') do |_parser, ogg_src_pad|
64
+ demuxer.signal_connect("pad-added") do |_parser, ogg_src_pad|
65
65
  vorbis_sink_pad = decoder.sinkpads.first
66
66
  ogg_src_pad.link(vorbis_sink_pad)
67
67
  end
@@ -76,10 +76,8 @@ module Alexandria
76
76
  when Gst::MessageType::EOS
77
77
  stop_playback
78
78
  when Gst::MessageType::ERROR
79
- if $DEBUG
80
- puts 'ERROR loop.quit'
81
- p message.parse
82
- end
79
+ log.debug { "ERROR loop.quit" }
80
+ log.debug { message.parse.inspect }
83
81
  stop_playback
84
82
  end
85
83
  true
@@ -4,22 +4,24 @@
4
4
  #
5
5
  # See the file README.md for authorship and licensing information.
6
6
 
7
- require 'alexandria/ui/callbacks'
8
- require 'alexandria/ui/columns'
9
- require 'alexandria/library_sort_order'
7
+ require "alexandria/ui/callbacks"
8
+ require "alexandria/ui/columns"
9
+ require "alexandria/ui/conflict_while_copying_dialog"
10
+ require "alexandria/library_sort_order"
10
11
 
11
12
  module Alexandria
12
13
  module UI
13
14
  class UIManager < BuilderBase
14
- attr_accessor :main_app, :actiongroup, :appbar, :prefs, :listview, :iconview, :listview_model,
15
- :iconview_model, :filtered_model
15
+ attr_accessor :main_app, :actiongroup, :appbar, :prefs, :listview, :iconview,
16
+ :listview_model, :iconview_model, :filtered_model
16
17
  attr_reader :model
18
+
17
19
  include Logging
18
20
  include GetText
19
- GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
21
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
20
22
 
21
23
  def initialize(parent)
22
- super('main_app__builder.glade', widget_names)
24
+ super("main_app__builder.glade", widget_names)
23
25
  @parent = parent
24
26
 
25
27
  @library_separator_iter = nil
@@ -47,7 +49,7 @@ module Alexandria
47
49
  log.debug { "UI Manager initialized: #{@iconview.model.inspect}" }
48
50
  @clicking_on_sidepane = true
49
51
 
50
- @library_listview.signal_connect('cursor-changed') do
52
+ @library_listview.signal_connect("cursor-changed") do
51
53
  @clicking_on_sidepane = true
52
54
  end
53
55
  end
@@ -63,17 +65,17 @@ module Alexandria
63
65
  end
64
66
 
65
67
  def create_uimanager
66
- log.debug { 'Adding actiongroup to uimanager' }
68
+ log.debug { "Adding actiongroup to uimanager" }
67
69
  @uimanager = Gtk::UIManager.new
68
70
  @uimanager.insert_action_group(@actiongroup, 0)
69
71
  end
70
72
 
71
73
  def setup_dependents
72
- @listview_model = Gtk::TreeModelSort.new(model: @filtered_model)
73
- @iconview_model = Gtk::TreeModelSort.new(model: @filtered_model)
74
+ @listview_model = Gtk::TreeModelSort.new(@filtered_model)
75
+ @iconview_model = Gtk::TreeModelSort.new(@filtered_model)
74
76
  @listview_manager = ListViewManager.new @listview, self
75
77
  @iconview_manager = IconViewManager.new @iconview, self
76
- @sidepane_manager = SidePaneManager.new @library_listview, self
78
+ @sidepane_manager = SidepaneManager.new @library_listview, self
77
79
  @library_listview = @sidepane_manager.library_listview
78
80
  @listview_manager.setup_listview_columns_visibility
79
81
  @listview_manager.setup_listview_columns_width
@@ -89,10 +91,10 @@ module Alexandria
89
91
  end
90
92
 
91
93
  def setup_toolbar
92
- log.debug { 'setup_toolbar' }
94
+ log.debug { "setup_toolbar" }
93
95
  setup_book_providers
94
96
  add_main_toolbar_items
95
- @toolbar = @uimanager.get_widget('/MainToolbar')
97
+ @toolbar = @uimanager.get_widget("/MainToolbar")
96
98
  @toolbar.show_arrow = true
97
99
  @toolbar.insert(Gtk::SeparatorToolItem.new, -1)
98
100
  setup_toolbar_combobox
@@ -100,18 +102,18 @@ module Alexandria
100
102
  @toolbar.insert(Gtk::SeparatorToolItem.new, -1)
101
103
  setup_toolbar_viewas
102
104
  @toolbar.show_all
103
- @actiongroup['Undo'].sensitive = @actiongroup['Redo'].sensitive = false
105
+ @actiongroup["Undo"].sensitive = @actiongroup["Redo"].sensitive = false
104
106
  UndoManager.instance.add_observer(self)
105
107
  @vbox1.add(@toolbar, position: 1, expand: false, fill: false)
106
108
  end
107
109
 
108
110
  def add_main_toolbar_items
109
111
  mid = @uimanager.new_merge_id
110
- @uimanager.add_ui(mid, 'ui/', 'MainToolbar', 'MainToolbar',
112
+ @uimanager.add_ui(mid, "ui/", "MainToolbar", "MainToolbar",
111
113
  :toolbar, false)
112
- @uimanager.add_ui(mid, 'ui/MainToolbar/', 'New', 'New',
114
+ @uimanager.add_ui(mid, "ui/MainToolbar/", "New", "New",
113
115
  :toolitem, false)
114
- @uimanager.add_ui(mid, 'ui/MainToolbar/', 'AddBook', 'AddBook',
116
+ @uimanager.add_ui(mid, "ui/MainToolbar/", "AddBook", "AddBook",
115
117
  :toolitem, false)
116
118
  # @uimanager.add_ui(mid, "ui/MainToolbar/", "sep", "sep",
117
119
  # :separator, false)
@@ -121,11 +123,13 @@ module Alexandria
121
123
 
122
124
  def setup_toolbar_filter_entry
123
125
  @filter_entry = Gtk::Entry.new
124
- @filter_entry.signal_connect('changed', &method(:on_toolbar_filter_entry_changed))
126
+ @filter_entry.signal_connect("changed") do |entry|
127
+ on_toolbar_filter_entry_changed(entry)
128
+ end
125
129
  @toolitem = Gtk::ToolItem.new
126
130
  @toolitem.expand = true
127
131
  @toolitem.border_width = 5
128
- @filter_entry.set_tooltip_text _('Type here the search criterion')
132
+ @filter_entry.set_tooltip_text _("Type here the search criterion")
129
133
  @toolitem << @filter_entry
130
134
  @toolbar.insert(@toolitem, -1)
131
135
  end
@@ -134,20 +138,20 @@ module Alexandria
134
138
  cb = Gtk::ComboBoxText.new
135
139
  cb.set_row_separator_func do |model, iter|
136
140
  # TODO: Replace with iter[0] if possible
137
- model.get_value(iter, 0) == '-'
141
+ model.get_value(iter, 0) == "-"
138
142
  end
139
- [_('Match everything'),
140
- '-',
141
- _('Title contains'),
142
- _('Authors contain'),
143
- _('ISBN contains'),
144
- _('Publisher contains'),
145
- _('Notes contain'),
146
- _('Tags contain')].each do |item|
143
+ [_("Match everything"),
144
+ "-",
145
+ _("Title contains"),
146
+ _("Authors contain"),
147
+ _("ISBN contains"),
148
+ _("Publisher contains"),
149
+ _("Notes contain"),
150
+ _("Tags contain")].each do |item|
147
151
  cb.append_text(item)
148
152
  end
149
153
  cb.active = 0
150
- cb.signal_connect('changed', &method(:on_criterion_combobox_changed))
154
+ cb.signal_connect("changed") { |combo| on_criterion_combobox_changed(combo) }
151
155
 
152
156
  # Put the combo box in a event box because it is not currently
153
157
  # possible assign a tooltip to a combo box.
@@ -157,16 +161,17 @@ module Alexandria
157
161
  @toolitem.border_width = 5
158
162
  @toolitem << eb
159
163
  @toolbar.insert(@toolitem, -1)
160
- eb.set_tooltip_text _('Change the search type')
164
+ eb.set_tooltip_text _("Change the search type")
161
165
  end
162
166
 
163
167
  def setup_toolbar_viewas
164
168
  @toolbar_view_as = Gtk::ComboBoxText.new
165
- @toolbar_view_as.append_text(_('View as Icons'))
166
- @toolbar_view_as.append_text(_('View as List'))
169
+ @toolbar_view_as.append_text(_("View as Icons"))
170
+ @toolbar_view_as.append_text(_("View as List"))
167
171
  @toolbar_view_as.active = 0
168
- @toolbar_view_as_signal_hid = \
169
- @toolbar_view_as.signal_connect('changed', &method(:on_toolbar_view_as_changed))
172
+ @toolbar_view_as_signal_hid = @toolbar_view_as.signal_connect("changed") do |combo|
173
+ on_toolbar_view_as_changed(combo)
174
+ end
170
175
 
171
176
  # Put the combo box in a event box because it is not currently
172
177
  # possible assign a tooltip to a combo box.
@@ -176,59 +181,64 @@ module Alexandria
176
181
  @toolitem.border_width = 5
177
182
  @toolitem << eb
178
183
  @toolbar.insert(@toolitem, -1)
179
- eb.set_tooltip_text _('Choose how to show books')
184
+ eb.set_tooltip_text _("Choose how to show books")
180
185
  end
181
186
 
182
187
  def setup_book_providers
183
- log.debug { 'setup_book_providers' }
188
+ log.debug { "setup_book_providers" }
184
189
  mid = @uimanager.new_merge_id
185
- BookProviders.each do |provider|
190
+ ui_paths = ["ui/MainMenubar/ViewMenu/OnlineInformation/",
191
+ "ui/BookPopup/OnlineInformation/",
192
+ "ui/NoBookPopup/OnlineInformation/"]
193
+ BookProviders.list.each do |provider|
186
194
  name = provider.action_name
187
- ['ui/MainMenubar/ViewMenu/OnlineInformation/',
188
- 'ui/BookPopup/OnlineInformation/',
189
- 'ui/NoBookPopup/OnlineInformation/'].each do |path|
190
- log.debug { "Adding #{name} to #{path}" }
191
- @uimanager.add_ui(mid, path, name, name,
192
- :menuitem, false)
193
- end
195
+ ui_paths.each do |path|
196
+ log.debug { "Adding #{name} to #{path}" }
197
+ @uimanager.add_ui(mid, path, name, name,
198
+ :menuitem, false)
199
+ end
194
200
  end
195
201
  end
196
202
 
197
203
  def add_menus_and_popups_from_xml
198
- log.debug { 'add_menus_and_popups_from_xml' }
199
- ['menus.xml', 'popups.xml'].each do |ui_file|
204
+ log.debug { "add_menus_and_popups_from_xml" }
205
+ ["menus.xml", "popups.xml"].each do |ui_file|
200
206
  @uimanager.add_ui(File.join(Alexandria::Config::DATA_DIR,
201
- 'ui', ui_file))
207
+ "ui", ui_file))
202
208
  end
203
209
  end
204
210
 
205
211
  def setup_accel_group
206
- log.debug { 'setup_accel_group' }
212
+ log.debug { "setup_accel_group" }
207
213
  @main_app.add_accel_group(@uimanager.accel_group)
208
214
  end
209
215
 
210
216
  def setup_menus
211
- @menubar = @uimanager.get_widget('/MainMenubar')
217
+ @menubar = @uimanager.get_widget("/MainMenubar")
212
218
  @vbox1.add(@menubar, position: 0, expand: false, fill: false)
213
219
  end
214
220
 
215
221
  def setup_popups
216
- log.debug { 'setup_popups' }
217
- @library_popup = @uimanager.get_widget('/LibraryPopup')
218
- @smart_library_popup = @uimanager.get_widget('/SmartLibraryPopup')
219
- @nolibrary_popup = @uimanager.get_widget('/NoLibraryPopup')
220
- @book_popup = @uimanager.get_widget('/BookPopup')
221
- @nobook_popup = @uimanager.get_widget('/NoBookPopup')
222
+ log.debug { "setup_popups" }
223
+ @library_popup = @uimanager.get_widget("/LibraryPopup")
224
+ @smart_library_popup = @uimanager.get_widget("/SmartLibraryPopup")
225
+ @nolibrary_popup = @uimanager.get_widget("/NoLibraryPopup")
226
+ @book_popup = @uimanager.get_widget("/BookPopup")
227
+ @nobook_popup = @uimanager.get_widget("/NoBookPopup")
222
228
  end
223
229
 
224
230
  def setup_window_events
225
- log.debug { 'setup_window_events' }
226
- @main_app.signal_connect('window-state-event', &method(:on_window_state_event))
227
- @main_app.signal_connect('destroy', &method(:on_window_destroy))
231
+ log.debug { "setup_window_events" }
232
+ @main_app.signal_connect("window-state-event") do |window, event|
233
+ on_window_state_event(window, event)
234
+ end
235
+ @main_app.signal_connect("destroy") do |window|
236
+ on_window_destroy(window)
237
+ end
228
238
  end
229
239
 
230
240
  def setup_active_model
231
- log.debug { 'setting up active model' }
241
+ log.debug { "setting up active model" }
232
242
  # The active model.
233
243
 
234
244
  list = [
@@ -263,13 +273,13 @@ module Alexandria
263
273
  true
264
274
  else
265
275
  data = case @filter_books_mode
266
- when 0 then
267
- (iter[Columns::TITLE] || '') +
268
- (iter[Columns::AUTHORS] || '') +
269
- (iter[Columns::ISBN] || '') +
270
- (iter[Columns::PUBLISHER] || '') +
271
- (iter[Columns::NOTES] || '') +
272
- (iter[Columns::TAGS] || '')
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] || "")
273
283
  when 2 then iter[Columns::TITLE]
274
284
  when 3 then iter[Columns::AUTHORS]
275
285
  when 4 then iter[Columns::ISBN]
@@ -283,19 +293,20 @@ module Alexandria
283
293
 
284
294
  # Give filter entry the initial keyboard focus.
285
295
  @filter_entry.grab_focus
286
- log.debug { 'done setting up active model' }
296
+ log.debug { "done setting up active model" }
287
297
  end
288
298
 
289
299
  def on_library_button_press_event(widget, event)
290
- log.debug { 'library_button_press_event' }
300
+ log.debug { "library_button_press_event" }
291
301
 
292
302
  # right click
293
303
  if event_is_right_click event
294
- log.debug { 'library right click!' }
304
+ log.debug { "library right click!" }
295
305
  library_already_selected = true
296
306
  if (path = widget.get_path_at_pos(event.x, event.y))
297
307
  @clicking_on_sidepane = true
298
- obj, path = widget.is_a?(Gtk::TreeView) ? [widget.selection, path.first] : [widget, path]
308
+ obj, path =
309
+ widget.is_a?(Gtk::TreeView) ? [widget.selection, path.first] : [widget, path]
299
310
  widget.has_focus = true
300
311
 
301
312
  unless obj.path_is_selected?(path)
@@ -339,14 +350,15 @@ module Alexandria
339
350
  sensitize_library selected_library if library_already_selected
340
351
 
341
352
  GLib::Idle.add do
342
- menu.popup(nil, nil, event.button, event.time)
353
+ menu.popup_at_pointer(event)
343
354
  false
344
355
  end
345
356
 
346
357
  # not a right click
347
358
  elsif (path = widget.get_path_at_pos(event.x, event.y))
348
359
  @clicking_on_sidepane = true
349
- obj, path = widget.is_a?(Gtk::TreeView) ? [widget.selection, path.first] : [widget, path]
360
+ obj, path =
361
+ widget.is_a?(Gtk::TreeView) ? [widget.selection, path.first] : [widget, path]
350
362
  obj.select_path(path)
351
363
  sensitize_library selected_library
352
364
  end
@@ -367,25 +379,26 @@ module Alexandria
367
379
  end
368
380
 
369
381
  def on_books_button_press_event(widget, event)
370
- log.debug { 'books_button_press_event' }
371
- if event_is_right_click event
372
- widget.grab_focus
382
+ log.debug { "books_button_press_event" }
383
+ event_is_right_click event or return
373
384
 
374
- if (path = widget.get_path_at_pos(event.x.to_i, event.y.to_i))
375
- obj, path = widget.is_a?(Gtk::TreeView) ? [widget.selection, path.first] : [widget, path]
385
+ widget.grab_focus
376
386
 
377
- unless obj.path_is_selected?(path)
378
- log.debug { "Select #{path}" }
379
- widget.unselect_all
380
- obj.select_path(path)
381
- end
382
- else
387
+ if (path = widget.get_path_at_pos(event.x.to_i, event.y.to_i))
388
+ obj, path =
389
+ widget.is_a?(Gtk::TreeView) ? [widget.selection, path.first] : [widget, path]
390
+
391
+ unless obj.path_is_selected?(path)
392
+ log.debug { "Select #{path}" }
383
393
  widget.unselect_all
394
+ obj.select_path(path)
384
395
  end
385
-
386
- menu = selected_books.empty? ? @nobook_popup : @book_popup
387
- menu.popup(nil, nil, event.button, event.time)
396
+ else
397
+ widget.unselect_all
388
398
  end
399
+
400
+ menu = selected_books.empty? ? @nobook_popup : @book_popup
401
+ menu.popup_at_pointer(event)
389
402
  end
390
403
 
391
404
  def get_library_selection_text(library)
@@ -405,9 +418,9 @@ module Alexandria
405
418
  library.length), library.name, library.length)
406
419
  else
407
420
  format(n_("Library '%s' selected, %d book, " \
408
- '%d unrated',
421
+ "%d unrated",
409
422
  "Library '%s' selected, %d books, " \
410
- '%d unrated',
423
+ "%d unrated",
411
424
  library.length), library.name, library.length, n_unrated)
412
425
  end
413
426
  end
@@ -420,7 +433,7 @@ module Alexandria
420
433
  when 1
421
434
  _("'%s' selected") % books.first.title
422
435
  else
423
- n_('%d book selected', '%d books selected',
436
+ n_("%d book selected", "%d books selected",
424
437
  books.length) % books.length
425
438
  end
426
439
  end
@@ -440,28 +453,30 @@ module Alexandria
440
453
 
441
454
  log.debug { "Currently focused widget: #{@main_app.focus.inspect}" }
442
455
  log.debug { "#{@library_listview} : #{@library_popup} : #{@listview}" }
443
- log.debug {
456
+ log.debug do
444
457
  "@library_listview: #{@library_listview.has_focus?} " \
445
458
  "or @library_popup:#{@library_popup.has_focus?}"
446
- }
447
- log.debug { '@library_listview does *NOT* have focus' }
459
+ end
460
+ log.debug { "@library_listview does *NOT* have focus" }
448
461
  log.debug { "Books are empty: #{books.empty?}" }
449
- @actiongroup['Properties'].sensitive = \
450
- @actiongroup['OnlineInformation'].sensitive = \
462
+ @actiongroup["Properties"].sensitive = \
463
+ @actiongroup["OnlineInformation"].sensitive = \
451
464
  books.length == 1
452
- @actiongroup['SelectAll'].sensitive = \
465
+ @actiongroup["SelectAll"].sensitive = \
453
466
  books.length < library.length
454
467
 
455
- @actiongroup['Delete'].sensitive = \
456
- @actiongroup['DeselectAll'].sensitive = \
457
- @actiongroup['Move'].sensitive =
458
- @actiongroup['SetRating'].sensitive = !books.empty?
468
+ @actiongroup["Delete"].sensitive = \
469
+ @actiongroup["DeselectAll"].sensitive = \
470
+ @actiongroup["Move"].sensitive =
471
+ @actiongroup["SetRating"].sensitive = !books.empty?
459
472
 
460
- log.debug { "on_books_selection_changed Delete: #{@actiongroup['Delete'].sensitive?}" }
473
+ log.debug do
474
+ "on_books_selection_changed Delete: #{@actiongroup['Delete'].sensitive?}"
475
+ end
461
476
 
462
477
  if library.is_a?(SmartLibrary)
463
- @actiongroup['Delete'].sensitive =
464
- @actiongroup['Move'].sensitive = false
478
+ @actiongroup["Delete"].sensitive =
479
+ @actiongroup["Move"].sensitive = false
465
480
  end
466
481
 
467
482
  # Sensitize providers URL
@@ -469,7 +484,7 @@ module Alexandria
469
484
  b = books.first
470
485
  # FIXME: Clean up endless negation in this logic
471
486
  no_urls = true
472
- BookProviders.each do |provider|
487
+ BookProviders.list.each do |provider|
473
488
  has_no_url = true
474
489
  begin
475
490
  has_no_url = (b.isbn.nil? || b.isbn.strip.empty? || provider.url(b).nil?)
@@ -479,27 +494,27 @@ module Alexandria
479
494
  @actiongroup[provider.action_name].sensitive = !has_no_url
480
495
  no_urls = false unless has_no_url
481
496
  end
482
- @actiongroup['OnlineInformation'].sensitive = false if no_urls
497
+ @actiongroup["OnlineInformation"].sensitive = false if no_urls
483
498
  end
484
499
  end
485
500
  @clicking_on_sidepane = false
486
501
  end
487
502
 
488
503
  def on_switch_page(_notebook, _page, page_num)
489
- log.debug { 'on_switch_page' }
490
- @actiongroup['ArrangeIcons'].sensitive = page_num.zero?
504
+ log.debug { "on_switch_page" }
505
+ @actiongroup["ArrangeIcons"].sensitive = page_num.zero?
491
506
  on_books_selection_changed
492
507
  end
493
508
 
494
509
  def on_focus(widget, _event_focus)
495
510
  if @clicking_on_sidepane || (widget == @library_listview)
496
- log.debug { 'on_focus: @library_listview' }
511
+ log.debug { "on_focus: @library_listview" }
497
512
  GLib::Idle.add do
498
513
  %w(OnlineInformation SelectAll DeselectAll).each do |action|
499
514
  @actiongroup[action].sensitive = false
500
515
  end
501
- @actiongroup['Properties'].sensitive = selected_library.is_a?(SmartLibrary)
502
- @actiongroup['Delete'].sensitive = determine_delete_option
516
+ @actiongroup["Properties"].sensitive = selected_library.is_a?(SmartLibrary)
517
+ @actiongroup["Delete"].sensitive = determine_delete_option
503
518
  false
504
519
  end
505
520
  else
@@ -508,52 +523,33 @@ module Alexandria
508
523
  end
509
524
 
510
525
  def determine_delete_option
511
- sensitive = (@libraries.all_regular_libraries.length > 1 || selected_library.is_a?(SmartLibrary))
512
- sensitive
526
+ @libraries.all_regular_libraries.length > 1 || selected_library.is_a?(SmartLibrary)
513
527
  end
514
528
 
515
529
  def on_close_sidepane
516
- log.debug { 'on_close_sidepane' }
517
- @actiongroup['Sidepane'].active = false
530
+ log.debug { "on_close_sidepane" }
531
+ @actiongroup["Sidepane"].active = false
518
532
  end
519
533
 
534
+ # TODO: Figure out why this frequently selects the wrong book!
520
535
  def select_a_book(book)
521
- select_this_book = proc do |bk, view|
522
- @filtered_model.refilter
523
- iter = iter_from_book bk
524
- next unless iter
525
-
526
- path = iter.path
527
- next unless view.model
528
-
529
- path = view_path_to_model_path(view, path)
530
- log.debug { "Path for #{bk.ident} is #{path}" }
531
- selection = view.respond_to?(:selection) ? @listview.selection : @iconview
532
- selection.unselect_all
533
- selection.select_path(path)
534
- end
535
- begin
536
- log.debug { 'select_a_book: listview' }
537
- select_this_book.call(book, @listview)
538
- log.debug { 'select_a_book: listview' }
539
- select_this_book.call(book, @iconview)
540
- rescue StandardError => ex
541
- trace = ex.backtrace.join("\n> ")
542
- log.warn { "Failed to automatically select book: #{ex.message} #{trace}" }
543
- end
544
- # TODO: Figure out why this frequently selects the wrong book!
536
+ log.debug { "select_a_book: listview" }
537
+ select_book_in_view(book, @listview)
538
+ log.debug { "select_a_book: iconview" }
539
+ select_book_in_view(book, @iconview)
545
540
  end
546
541
 
547
542
  def update(*ary)
548
543
  log.debug { "on_update #{ary}" }
549
544
  caller = ary.first
550
- if caller.is_a?(UndoManager)
551
- @actiongroup['Undo'].sensitive = caller.can_undo?
552
- @actiongroup['Redo'].sensitive = caller.can_redo?
553
- elsif caller.is_a?(Library)
545
+ case caller
546
+ when UndoManager
547
+ @actiongroup["Undo"].sensitive = caller.can_undo?
548
+ @actiongroup["Redo"].sensitive = caller.can_redo?
549
+ when Library
554
550
  handle_update_caller_library ary unless caller.updating?
555
551
  else
556
- raise 'unrecognized update event'
552
+ raise _("unrecognized update event")
557
553
  end
558
554
  end
559
555
 
@@ -579,18 +575,16 @@ module Alexandria
579
575
  end
580
576
  end
581
577
 
582
- # private
583
-
584
578
  def open_web_browser(url)
585
579
  if url.nil?
586
- log.warn('Attempt to open browser with nil url')
580
+ log.warn("Attempt to open browser with nil url")
587
581
  return
588
582
  end
589
583
  Gtk.show_uri url
590
584
  end
591
585
 
592
586
  def detach_old_libraries
593
- log.debug { 'Un-observing old libraries' }
587
+ log.debug { "Un-observing old libraries" }
594
588
  @libraries.all_regular_libraries.each do |library|
595
589
  if library.is_a?(Library)
596
590
  library.delete_observer(self)
@@ -600,7 +594,7 @@ module Alexandria
600
594
  end
601
595
 
602
596
  def load_libraries
603
- log.info { 'Loading libraries...' }
597
+ log.info { _("Loading libraries...") }
604
598
  @completion_models = CompletionModels.instance
605
599
  if @libraries
606
600
  detach_old_libraries
@@ -618,17 +612,17 @@ module Alexandria
618
612
 
619
613
  def handle_ruined_books
620
614
  new_message = _(
621
- 'The data files for the following books are malformed or empty. Do you wish to' \
615
+ "The data files for the following books are malformed or empty. Do you wish to" \
622
616
  " attempt to download new information for them from the online book providers?\n")
623
617
 
624
- @libraries.ruined_books.each { |bi|
618
+ @libraries.ruined_books.each do |bi|
625
619
  new_message += "\n#{bi[1] || bi[1].inspect}"
626
- }
620
+ end
627
621
  recovery_dialog = Gtk::MessageDialog.new(@main_app, Gtk::Dialog::MODAL,
628
622
  Gtk::MessageDialog::WARNING,
629
623
  Gtk::MessageDialog::BUTTONS_OK_CANCEL,
630
624
  new_message).show
631
- recovery_dialog.signal_connect('response') do |_dialog, response_type|
625
+ recovery_dialog.signal_connect("response") do |_dialog, response_type|
632
626
  recovery_dialog.destroy
633
627
  if response_type == Gtk::ResponseType::OK
634
628
  # progress indicator...
@@ -662,11 +656,15 @@ module Alexandria
662
656
  end
663
657
  end
664
658
 
665
- log.debug { "Trying to add #{book.title}, #{cover_uri} in library ''#{library.name}'" }
659
+ log.debug do
660
+ "Trying to add #{book.title}, #{cover_uri}" \
661
+ " in library ''#{library.name}'"
662
+ end
666
663
  library.save_cover(book, cover_uri) unless cover_uri.nil?
667
664
  library << book
668
665
  library.save(book)
669
- set_status_label(format(_("Added '%s' to library '%s'"), book.title, library.name))
666
+ set_status_label(format(_("Added '%s' to library '%s'"),
667
+ book.title, library.name))
670
668
  rescue StandardError => ex
671
669
  log.error { "Couldn't add book #{isbn}: #{ex}" }
672
670
  log.error { ex.backtrace.join("\n") }
@@ -688,7 +686,7 @@ module Alexandria
688
686
  ## Hide the progress bar.
689
687
  @appbar.children.first.visible = false
690
688
  ## Refresh the status bar.
691
- set_status_label('')
689
+ set_status_label("")
692
690
  # on_books_selection_changed
693
691
  false
694
692
  end
@@ -713,22 +711,23 @@ module Alexandria
713
711
  iter[Columns::TITLE] = book.title
714
712
  title = book.title.sub(REDUCE_TITLE_REGEX, '\1...')
715
713
  iter[Columns::TITLE_REDUCED] = title
716
- iter[Columns::AUTHORS] = book.authors.join(', ')
714
+ iter[Columns::AUTHORS] = book.authors.join(", ")
717
715
  iter[Columns::ISBN] = book.isbn.to_s
718
716
  iter[Columns::PUBLISHER] = book.publisher
719
717
  iter[Columns::PUBLISH_DATE] = book.publishing_year.to_s
720
718
  iter[Columns::EDITION] = book.edition
721
- iter[Columns::NOTES] = (book.notes || '')
722
- iter[Columns::LOANED_TO] = (book.loaned_to || '')
719
+ iter[Columns::NOTES] = (book.notes || "")
720
+ iter[Columns::LOANED_TO] = (book.loaned_to || "")
723
721
  rating = (book.rating || Book::DEFAULT_RATING)
724
- iter[Columns::RATING] = Book::MAX_RATING_STARS - rating # ascending order is the default
722
+ # ascending order is the default
723
+ iter[Columns::RATING] = Book::MAX_RATING_STARS - rating
725
724
  iter[Columns::OWN] = book.own?
726
725
  iter[Columns::REDD] = book.redd?
727
726
  iter[Columns::WANT] = book.want?
728
727
  iter[Columns::TAGS] = if book.tags
729
- book.tags.join(',')
728
+ book.tags.join(",")
730
729
  else
731
- ''
730
+ ""
732
731
  end
733
732
 
734
733
  icon = Icons.cover(selected_library, book)
@@ -740,9 +739,9 @@ module Alexandria
740
739
  new_height = [ICON_HEIGHT, icon.height].min
741
740
  icon = cache_scaled_icon(icon, new_width, new_height)
742
741
  end
743
- icon = icon.tag(Icons::FAVORITE_TAG) if rating == Book::MAX_RATING_STARS
742
+ icon = Icons.tag_icon(icon, Icons::FAVORITE_TAG) if rating == Book::MAX_RATING_STARS
744
743
  iter[Columns::COVER_ICON] = icon
745
- log.debug { 'Full iter: ' + (0..15).map { |num| iter[num].inspect }.join(', ') }
744
+ log.debug { "Full iter: " + (0..15).map { |num| iter[num].inspect }.join(", ") }
746
745
  end
747
746
 
748
747
  def append_book(book, _tail = nil)
@@ -752,7 +751,7 @@ module Alexandria
752
751
  if iter
753
752
  fill_iter_with_book(iter, book)
754
753
  else
755
- log.debug { '@model.append' }
754
+ log.debug { "@model.append" }
756
755
  iter = @model.append
757
756
  fill_iter_with_book(iter, book)
758
757
  log.debug { "no iter for book #{book}" }
@@ -789,13 +788,13 @@ module Alexandria
789
788
  @library_listview.set_cursor(iter.path,
790
789
  @library_listview.get_column(0),
791
790
  true)
792
- @actiongroup['Sidepane'].active = true
791
+ @actiongroup["Sidepane"].active = true
793
792
  end
794
793
  iter
795
794
  end
796
795
 
797
796
  def append_library_separator
798
- log.debug { 'append_library_separator' }
797
+ log.debug { "append_library_separator" }
799
798
  iter = @library_listview.model.append
800
799
  iter[0] = nil
801
800
  iter[1] = nil
@@ -805,7 +804,7 @@ module Alexandria
805
804
  end
806
805
 
807
806
  def refresh_books
808
- log.debug { 'refresh_books' }
807
+ log.debug { "refresh_books" }
809
808
  @library_listview.set_sensitive(false)
810
809
  library = selected_library
811
810
  @iconview.freeze
@@ -851,7 +850,7 @@ module Alexandria
851
850
  end
852
851
 
853
852
  def selected_library
854
- log.debug { 'selected_library' }
853
+ log.debug { "selected_library" }
855
854
  if (iter = @library_listview.selection.selected)
856
855
  target_name = iter[1]
857
856
  @libraries.all_libraries.find { |it| it.name == target_name }
@@ -881,7 +880,7 @@ module Alexandria
881
880
  def iter_from_ident(ident)
882
881
  log.debug { ident.to_s }
883
882
  iter = @model.iter_first
884
- ok = true
883
+ ok = true if iter
885
884
  while ok
886
885
  return iter if iter[Columns::IDENT] == ident
887
886
 
@@ -923,11 +922,11 @@ module Alexandria
923
922
  end
924
923
 
925
924
  def refresh_libraries
926
- log.debug { 'refresh_libraries' }
925
+ log.debug { "refresh_libraries" }
927
926
  library = selected_library
928
927
 
929
928
  # Change the application's title.
930
- @main_app.title = library.name + ' - ' + TITLE
929
+ @main_app.title = library.name + " - " + TITLE
931
930
 
932
931
  # Disable the selected library in the move libraries actions.
933
932
  @libraries.all_regular_libraries.each do |i_library|
@@ -941,11 +940,11 @@ module Alexandria
941
940
  smart = library.is_a?(SmartLibrary)
942
941
  log.debug { "sensitize_library: smartlibrary = #{smart}" }
943
942
  GLib::Idle.add do
944
- @actiongroup['AddBook'].sensitive = !smart
945
- @actiongroup['AddBookManual'].sensitive = !smart
946
- @actiongroup['Properties'].sensitive = smart
943
+ @actiongroup["AddBook"].sensitive = !smart
944
+ @actiongroup["AddBookManual"].sensitive = !smart
945
+ @actiongroup["Properties"].sensitive = smart
947
946
  can_delete = smart || (@libraries.all_regular_libraries.length > 1)
948
- @actiongroup['Delete'].sensitive = can_delete
947
+ @actiongroup["Delete"].sensitive = can_delete
949
948
  log.debug { "sensitize_library delete: #{@actiongroup['Delete'].sensitive?}" }
950
949
  false
951
950
  end
@@ -954,14 +953,14 @@ module Alexandria
954
953
  def get_view_actiongroup
955
954
  case @prefs.view_as
956
955
  when 0
957
- @actiongroup['AsIcons']
956
+ @actiongroup["AsIcons"]
958
957
  when 1
959
- @actiongroup['AsList']
958
+ @actiongroup["AsList"]
960
959
  end
961
960
  end
962
961
 
963
962
  def restore_preferences
964
- log.debug { 'Restoring preferences...' }
963
+ log.debug { "Restoring preferences" }
965
964
  if @prefs.maximized
966
965
  @main_app.maximize
967
966
  else
@@ -970,9 +969,9 @@ module Alexandria
970
969
  @maximized = false
971
970
  end
972
971
  @paned.position = @prefs.sidepane_position
973
- @actiongroup['Sidepane'].active = @prefs.sidepane_visible
974
- @actiongroup['Toolbar'].active = @prefs.toolbar_visible
975
- @actiongroup['Statusbar'].active = @prefs.statusbar_visible
972
+ @actiongroup["Sidepane"].active = @prefs.sidepane_visible
973
+ @actiongroup["Toolbar"].active = @prefs.toolbar_visible
974
+ @actiongroup["Statusbar"].active = @prefs.statusbar_visible
976
975
  @appbar.visible = @prefs.statusbar_visible
977
976
  action = get_view_actiongroup
978
977
  action.activate
@@ -996,29 +995,29 @@ module Alexandria
996
995
  end
997
996
 
998
997
  def save_preferences
999
- log.debug { 'save_preferences' }
998
+ log.debug { "save_preferences" }
1000
999
  @prefs.position = @main_app.position
1001
1000
  @prefs.size = @main_app.allocation.to_a[2..3]
1002
1001
  @prefs.maximized = @maximized
1003
1002
  @prefs.sidepane_position = @paned.position
1004
- @prefs.sidepane_visible = @actiongroup['Sidepane'].active?
1005
- @prefs.toolbar_visible = @actiongroup['Toolbar'].active?
1006
- @prefs.statusbar_visible = @actiongroup['Statusbar'].active?
1003
+ @prefs.sidepane_visible = @actiongroup["Sidepane"].active?
1004
+ @prefs.toolbar_visible = @actiongroup["Toolbar"].active?
1005
+ @prefs.statusbar_visible = @actiongroup["Statusbar"].active?
1007
1006
  @prefs.view_as = @notebook.page
1008
1007
  @prefs.selected_library = selected_library.name
1009
1008
  cols_width = {}
1010
1009
  @listview.columns.each do |c|
1011
1010
  cols_width[c.title] = c.width
1012
1011
  end
1013
- @prefs.cols_width = '{' + cols_width.to_a.map do |t, v|
1012
+ @prefs.cols_width = "{" + cols_width.to_a.map do |t, v|
1014
1013
  '"' + t + '": ' + v.to_s
1015
- end.join(', ') + '}'
1014
+ end.join(", ") + "}"
1016
1015
  log.debug { "cols_width: #{@prefs.cols_width} " }
1017
1016
  @prefs.save!
1018
1017
  end
1019
1018
 
1020
1019
  def undoable_move(source, dest, books)
1021
- log.debug { 'undoable_move' }
1020
+ log.debug { "undoable_move" }
1022
1021
  Library.move(source, dest, *books)
1023
1022
  UndoManager.instance.push { undoable_move(dest, source, books) }
1024
1023
  end
@@ -1035,7 +1034,7 @@ module Alexandria
1035
1034
 
1036
1035
  def setup_move_actions
1037
1036
  @actiongroup.actions.each do |action|
1038
- next unless /^MoveIn/ =~ action.name
1037
+ next unless action.name.start_with?("MoveIn")
1039
1038
 
1040
1039
  @actiongroup.remove_action(action)
1041
1040
  end
@@ -1050,10 +1049,11 @@ module Alexandria
1050
1049
  @actiongroup.add_actions(actions)
1051
1050
  @uimanager.remove_ui(@move_mid) if @move_mid
1052
1051
  @move_mid = @uimanager.new_merge_id
1052
+ ui_paths = ["ui/MainMenubar/EditMenu/Move/",
1053
+ "ui/BookPopup/Move/"]
1053
1054
  @libraries.all_regular_libraries.each do |library|
1054
1055
  name = library.action_name
1055
- ['ui/MainMenubar/EditMenu/Move/',
1056
- 'ui/BookPopup/Move/'].each do |path|
1056
+ ui_paths.each do |path|
1057
1057
  @uimanager.add_ui(@move_mid, path, name, name,
1058
1058
  :menuitem, false)
1059
1059
  end
@@ -1072,10 +1072,10 @@ module Alexandria
1072
1072
  # Gets the sort order of the current library, for use by export
1073
1073
  def library_sort_order
1074
1074
  # added by Cathal Mc Ginley, 23 Oct 2007
1075
- log.debug {
1075
+ log.debug do
1076
1076
  "library_sort_order #{@notebook.page}: " \
1077
1077
  "#{@iconview.model.inspect} #{@listview.model.inspect}"
1078
- }
1078
+ end
1079
1079
  result, sort_column, sort_order = current_view.model.sort_column_id
1080
1080
  if result
1081
1081
  column_ids_to_attributes = { 2 => :title,
@@ -1137,10 +1137,10 @@ module Alexandria
1137
1137
  end
1138
1138
 
1139
1139
  def remove_library_separator
1140
- if !@library_separator_iter.nil? && @libraries.all_smart_libraries.empty?
1141
- @library_listview.model.remove(@library_separator_iter)
1142
- @library_separator_iter = nil
1143
- end
1140
+ return if @library_separator_iter.nil? || @libraries.all_smart_libraries.any?
1141
+
1142
+ @library_listview.model.remove(@library_separator_iter)
1143
+ @library_separator_iter = nil
1144
1144
  end
1145
1145
 
1146
1146
  def undoable_undelete(library, books = nil)
@@ -1161,13 +1161,14 @@ module Alexandria
1161
1161
 
1162
1162
  def setup_window_icons
1163
1163
  @main_app.icon = Icons::ALEXANDRIA_SMALL
1164
- Gtk::Window.set_default_icon_name('alexandria')
1165
- @main_app.icon_name = 'alexandria'
1164
+ Gtk::Window.set_default_icon_name("alexandria")
1165
+ @main_app.icon_name = "alexandria"
1166
1166
  end
1167
1167
 
1168
1168
  ICONS_SORTS = [
1169
1169
  Columns::TITLE, Columns::AUTHORS, Columns::ISBN,
1170
- Columns::PUBLISHER, Columns::EDITION, Columns::RATING, Columns::REDD, Columns::OWN, Columns::WANT
1170
+ Columns::PUBLISHER, Columns::EDITION, Columns::RATING,
1171
+ Columns::REDD, Columns::OWN, Columns::WANT
1171
1172
  ].freeze
1172
1173
 
1173
1174
  def setup_books_iconview_sorting
@@ -1179,6 +1180,21 @@ module Alexandria
1179
1180
 
1180
1181
  private
1181
1182
 
1183
+ def select_book_in_view(book, view)
1184
+ @filtered_model.refilter
1185
+ iter = iter_from_book book
1186
+ return unless iter
1187
+
1188
+ path = iter.path
1189
+ return unless view.model
1190
+
1191
+ path = view_path_to_model_path(view, path)
1192
+ log.debug { "Path for #{book.ident} is #{path}" }
1193
+ selection = view.respond_to?(:selection) ? view.selection : view
1194
+ selection.unselect_all
1195
+ selection.select_path(path)
1196
+ end
1197
+
1182
1198
  def view_path_to_model_path(view, path)
1183
1199
  path = view.model.convert_path_to_child_path(path)
1184
1200
  @filtered_model.convert_path_to_child_path(path) if path