alexandria-book-collection-manager 0.7.5 → 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +9 -0
  3. data/.github/workflows/ruby.yml +72 -0
  4. data/.gitignore +4 -1
  5. data/.rubocop.yml +65 -30
  6. data/.rubocop_todo.yml +49 -165
  7. data/.simplecov +5 -2
  8. data/CHANGELOG.md +64 -0
  9. data/ChangeLog.0 +19 -19
  10. data/INSTALL.md +26 -16
  11. data/README.md +31 -35
  12. data/Rakefile +18 -16
  13. data/alexandria-book-collection-manager.gemspec +35 -29
  14. data/doc/FAQ +2 -2
  15. data/doc/dependency_decisions.yml +22 -3
  16. data/lib/alexandria/about.rb +1 -1
  17. data/lib/alexandria/book_providers/bl_provider.rb +88 -0
  18. data/lib/alexandria/book_providers/douban.rb +2 -2
  19. data/lib/alexandria/book_providers/loc_provider.rb +38 -0
  20. data/lib/alexandria/book_providers/pseudomarc.rb +61 -71
  21. data/lib/alexandria/book_providers/sbn_provider.rb +108 -0
  22. data/lib/alexandria/book_providers/{thalia.rb → thalia_provider.rb} +37 -74
  23. data/lib/alexandria/book_providers/web.rb +2 -2
  24. data/lib/alexandria/book_providers/worldcat.rb +34 -38
  25. data/lib/alexandria/book_providers/z3950_provider.rb +199 -0
  26. data/lib/alexandria/book_providers.rb +48 -65
  27. data/lib/alexandria/default_preferences.rb +2 -1
  28. data/lib/alexandria/execution_queue.rb +13 -12
  29. data/lib/alexandria/export_library.rb +21 -22
  30. data/lib/alexandria/image_fetcher.rb +25 -0
  31. data/lib/alexandria/import_library.rb +46 -70
  32. data/lib/alexandria/import_library_csv.rb +16 -16
  33. data/lib/alexandria/library_sort_order.rb +3 -1
  34. data/lib/alexandria/library_store.rb +19 -20
  35. data/lib/alexandria/logging.rb +5 -9
  36. data/lib/alexandria/models/book.rb +15 -2
  37. data/lib/alexandria/models/library.rb +31 -35
  38. data/lib/alexandria/net.rb +1 -2
  39. data/lib/alexandria/preferences.rb +27 -33
  40. data/lib/alexandria/scanners/cue_cat.rb +6 -6
  41. data/lib/alexandria/scanners/keyboard.rb +1 -1
  42. data/lib/alexandria/scanners.rb +2 -2
  43. data/lib/alexandria/smart_library.rb +22 -26
  44. data/lib/alexandria/ui/about_dialog.rb +1 -1
  45. data/lib/alexandria/ui/acquire_dialog.rb +15 -19
  46. data/lib/alexandria/ui/alert_dialog.rb +36 -19
  47. data/lib/alexandria/ui/bad_isbns_dialog.rb +13 -9
  48. data/lib/alexandria/ui/barcode_animation.rb +6 -6
  49. data/lib/alexandria/ui/book_properties_dialog.rb +2 -3
  50. data/lib/alexandria/ui/book_properties_dialog_base.rb +35 -137
  51. data/lib/alexandria/ui/calendar_popup.rb +58 -0
  52. data/lib/alexandria/ui/callbacks.rb +144 -123
  53. data/lib/alexandria/ui/completion_models.rb +2 -6
  54. data/lib/alexandria/ui/confirm_erase_dialog.rb +1 -1
  55. data/lib/alexandria/ui/conflict_while_copying_dialog.rb +2 -2
  56. data/lib/alexandria/ui/error_dialog.rb +1 -1
  57. data/lib/alexandria/ui/export_dialog.rb +19 -18
  58. data/lib/alexandria/ui/icons.rb +34 -40
  59. data/lib/alexandria/ui/iconview_tooltips.rb +40 -53
  60. data/lib/alexandria/ui/import_dialog.rb +49 -48
  61. data/lib/alexandria/ui/init.rb +14 -12
  62. data/lib/alexandria/ui/keep_bad_isbn_dialog.rb +2 -2
  63. data/lib/alexandria/ui/libraries_combo.rb +10 -9
  64. data/lib/alexandria/ui/listview.rb +6 -7
  65. data/lib/alexandria/ui/main_app.rb +2 -2
  66. data/lib/alexandria/ui/multi_drag_treeview.rb +5 -7
  67. data/lib/alexandria/ui/new_book_dialog.rb +63 -65
  68. data/lib/alexandria/ui/new_book_dialog_manual.rb +1 -1
  69. data/lib/alexandria/ui/new_provider_dialog.rb +12 -11
  70. data/lib/alexandria/ui/new_smart_library_dialog.rb +39 -27
  71. data/lib/alexandria/ui/preferences_dialog.rb +25 -84
  72. data/lib/alexandria/ui/provider_preferences_base_dialog.rb +10 -6
  73. data/lib/alexandria/ui/provider_preferences_dialog.rb +5 -5
  74. data/lib/alexandria/ui/really_delete_dialog.rb +2 -2
  75. data/lib/alexandria/ui/sidepane_manager.rb +38 -38
  76. data/lib/alexandria/ui/skip_entry_dialog.rb +3 -2
  77. data/lib/alexandria/ui/smart_library_properties_dialog.rb +35 -36
  78. data/lib/alexandria/ui/smart_library_properties_dialog_base.rb +61 -244
  79. data/lib/alexandria/ui/smart_library_rule_box.rb +119 -0
  80. data/lib/alexandria/ui/sound.rb +4 -6
  81. data/lib/alexandria/ui/ui_manager.rb +80 -83
  82. data/lib/alexandria/ui.rb +7 -7
  83. data/lib/alexandria/version.rb +2 -2
  84. data/lib/alexandria/web_themes.rb +15 -15
  85. data/lib/alexandria.rb +2 -2
  86. data/po/cs.po +947 -865
  87. data/po/cy.po +913 -864
  88. data/po/de.po +961 -865
  89. data/po/el.po +956 -861
  90. data/po/es.po +952 -857
  91. data/po/fr.po +950 -865
  92. data/po/ga.po +866 -819
  93. data/po/gl.po +946 -861
  94. data/po/it.po +945 -858
  95. data/po/ja.po +921 -836
  96. data/po/mk.po +953 -858
  97. data/po/nb.po +932 -847
  98. data/po/nl.po +955 -849
  99. data/po/pl.po +999 -963
  100. data/po/pt.po +946 -850
  101. data/po/pt_BR.po +944 -859
  102. data/po/ru.po +959 -868
  103. data/po/sk.po +950 -863
  104. data/po/sv.po +944 -859
  105. data/po/uk.po +925 -846
  106. data/po/zh_TW.po +926 -841
  107. data/schemas/alexandria.schemas +1 -1
  108. data/share/alexandria/glade/main_app__builder.glade +6 -21
  109. data/share/gnome/help/alexandria/C/adding-books.xml +3 -4
  110. data/share/gnome/help/alexandria/C/introduction.xml +0 -16
  111. data/share/gnome/help/alexandria/C/searching.xml +1 -4
  112. data/share/gnome/help/alexandria/C/settings.xml +0 -30
  113. data/share/gnome/help/alexandria/C/smart-libraries.xml +2 -2
  114. data/share/gnome/help/alexandria/C/working-with-libraries.xml +1 -1
  115. data/share/gnome/help/alexandria/fr/alexandria.xml +5 -160
  116. data/share/gnome/help/alexandria/ja/adding-books.xml +1 -1
  117. data/share/gnome/help/alexandria/ja/introduction.xml +0 -15
  118. data/share/gnome/help/alexandria/ja/searching.xml +3 -7
  119. data/share/gnome/help/alexandria/ja/settings.xml +0 -27
  120. data/share/gnome/help/alexandria/ja/smart-libraries.xml +1 -1
  121. data/spec/alexandria/book_providers/bl_provider_spec.rb +13 -0
  122. data/spec/alexandria/book_providers/loc_provider_spec.rb +17 -0
  123. data/spec/alexandria/book_providers/sbn_provider_spec.rb +13 -0
  124. data/spec/alexandria/book_providers/thalia_provider_spec.rb +119 -0
  125. data/spec/alexandria/book_providers/world_cat_provider_spec.rb +160 -0
  126. data/spec/alexandria/book_providers_spec.rb +0 -154
  127. data/spec/alexandria/console_spec.rb +0 -5
  128. data/spec/alexandria/export_library_spec.rb +27 -38
  129. data/spec/alexandria/library_spec.rb +76 -46
  130. data/spec/alexandria/preferences_spec.rb +29 -3
  131. data/spec/alexandria/scanners/cue_cat_spec.rb +1 -1
  132. data/spec/alexandria/ui/about_dialog_spec.rb +1 -1
  133. data/spec/alexandria/ui/acquire_dialog_spec.rb +1 -1
  134. data/spec/alexandria/ui/alert_dialog_spec.rb +1 -1
  135. data/spec/alexandria/ui/bad_isbns_dialog_spec.rb +1 -1
  136. data/spec/alexandria/ui/book_properties_dialog_spec.rb +47 -5
  137. data/spec/alexandria/ui/confirm_erase_dialog_spec.rb +1 -1
  138. data/spec/alexandria/ui/conflict_while_copying_dialog_spec.rb +1 -1
  139. data/spec/alexandria/ui/error_dialog_spec.rb +1 -1
  140. data/spec/alexandria/ui/export_dialog_spec.rb +25 -4
  141. data/spec/alexandria/ui/icons_spec.rb +26 -0
  142. data/spec/alexandria/ui/iconview_spec.rb +1 -1
  143. data/spec/alexandria/ui/import_dialog_spec.rb +35 -3
  144. data/spec/alexandria/ui/keep_bad_isbn_dialog_spec.rb +1 -1
  145. data/spec/alexandria/ui/main_app_spec.rb +1 -1
  146. data/spec/alexandria/ui/new_book_dialog_manual_spec.rb +39 -3
  147. data/spec/alexandria/ui/new_provider_dialog_spec.rb +19 -3
  148. data/spec/alexandria/ui/new_smart_library_dialog_spec.rb +28 -3
  149. data/spec/alexandria/ui/preferences_dialog_spec.rb +2 -2
  150. data/spec/alexandria/ui/provider_preferences_dialog_spec.rb +23 -8
  151. data/spec/alexandria/ui/really_delete_dialog_spec.rb +1 -1
  152. data/spec/alexandria/ui/sidepane_manager_spec.rb +2 -2
  153. data/spec/alexandria/ui/skip_entry_dialog_spec.rb +1 -1
  154. data/spec/alexandria/ui/smart_library_properties_dialog_spec.rb +37 -6
  155. data/spec/alexandria/ui/ui_manager_spec.rb +116 -2
  156. data/spec/data/libraries/0.6.2/My Library/9780571147168.yaml +2 -0
  157. data/spec/end_to_end/basic_run_spec.rb +3 -8
  158. data/spec/fixtures/cover.jpg +0 -0
  159. data/spec/spec_helper.rb +47 -3
  160. data/tasks/spec.rake +3 -5
  161. data/util/rake/fileinstall.rb +16 -15
  162. data/util/rake/omfgenerate.rb +1 -1
  163. metadata +141 -52
  164. data/.travis.yml +0 -39
  165. data/lib/alexandria/book_providers/adlibris.rb +0 -196
  166. data/lib/alexandria/book_providers/amazon_aws.rb +0 -252
  167. data/lib/alexandria/book_providers/amazon_ecs_util.rb +0 -388
  168. data/lib/alexandria/book_providers/barnes_and_noble.rb +0 -209
  169. data/lib/alexandria/book_providers/proxis.rb +0 -175
  170. data/lib/alexandria/book_providers/siciliano.rb +0 -257
  171. data/lib/alexandria/book_providers/z3950.rb +0 -415
  172. data/spec/alexandria/ui/ui_utilities_spec.rb +0 -62
  173. data/spec/alexandria/utilities_spec.rb +0 -52
@@ -27,7 +27,7 @@ module Alexandria
27
27
  else
28
28
  message = if books.length == 1
29
29
  format(_("Are you sure you want to delete '%s' " \
30
- "from '%s'?"), books.first.title, library.name)
30
+ "from '%s'?"), books.first.title, library.name)
31
31
  else
32
32
  _("Are you sure you want to delete the " \
33
33
  "selected books from '%s'?") % library.name
@@ -40,7 +40,7 @@ module Alexandria
40
40
  [Gtk::Stock::DELETE, Gtk::ResponseType::OK]],
41
41
  description)
42
42
 
43
- self.default_response = Gtk::ResponseType::CANCEL
43
+ dialog.default_response = Gtk::ResponseType::CANCEL
44
44
  end
45
45
 
46
46
  def ok?
@@ -8,10 +8,11 @@ require "alexandria/ui/error_dialog"
8
8
 
9
9
  module Alexandria
10
10
  module UI
11
- class SidePaneManager
11
+ class SidepaneManager
12
12
  include Logging
13
13
  include GetText
14
14
  attr_accessor :library_listview
15
+
15
16
  def initialize(library_listview, parent)
16
17
  @library_listview = library_listview
17
18
  @parent = parent
@@ -32,12 +33,11 @@ module Alexandria
32
33
  # otherwise returns nil
33
34
  def contains_illegal_character(new_text)
34
35
  new_text.unpack("U*") # attempt to unpack as UTF-8 characters
35
- match = %r{(^\.|/)}.match(new_text)
36
36
  # forbid / character (since Library names become dir names)
37
37
  # also no initial . since that hides the Library (hidden file)
38
38
  # forbidding an initial dot also disallows "." and ".."
39
39
  # which are of course pre-existing directories.
40
- match
40
+ %r{(^\.|/)}.match(new_text)
41
41
  rescue StandardError => ex
42
42
  log.warn { "New library name not valid UTF-8: #{ex.message}" }
43
43
  true
@@ -46,42 +46,40 @@ module Alexandria
46
46
 
47
47
  def on_edited_library(cell, path_string, new_text)
48
48
  log.debug { "edited library name #{new_text}" }
49
- ## new_text = new_text.reverse # for testing;
50
- # a great way to generate broken UTF-8
51
- if cell.text != new_text
52
- if (match = contains_illegal_character(new_text))
53
- if match.instance_of? MatchData
54
- chars = match[1].gsub(/&/, "&")
55
- ErrorDialog.new(@main_app, _("Invalid library name '%s'") % new_text,
56
- _("The name provided contains the " \
57
- "disallowed character <b>%s</b> ") % chars).display
58
- else
59
- ErrorDialog.new(@main_app, _("Invalid library name"),
60
- _("The name provided contains " \
61
- "invalid characters.")).display
62
- end
63
-
64
- elsif new_text.strip.empty?
65
- log.debug { "Empty text" }
66
- ErrorDialog.new(@main_app, _("The library name " \
67
- "can not be empty")).display
68
- elsif library_already_exists new_text
69
- log.debug { "Already exists" }
70
- ErrorDialog.new(@main_app,
71
- _("The library can not be renamed"),
72
- _("There is already a library named " \
73
- "'%s'. Please choose a different " \
74
- "name.") % new_text.strip).display
49
+ return if cell.text == new_text
50
+
51
+ if (match = contains_illegal_character(new_text))
52
+ if match.instance_of? MatchData
53
+ chars = match[1].gsub(/&/, "&amp;")
54
+ ErrorDialog.new(@main_app, _("Invalid library name '%s'") % new_text,
55
+ _("The name provided contains the " \
56
+ "disallowed character <b>%s</b>") % chars).display
75
57
  else
76
- log.debug { "Attempting to apply #{path_string}, #{new_text}" }
77
- path = Gtk::TreePath.new(path_string)
78
- iter = @library_listview.model.get_iter(path)
79
- library_name = new_text.strip
80
- log.info { "library name is #{library_name}" }
81
- iter[1] = @parent.selected_library.name = library_name
82
- @parent.setup_move_actions
83
- @parent.refresh_libraries
58
+ ErrorDialog.new(@main_app, _("Invalid library name"),
59
+ _("The name provided contains " \
60
+ "invalid characters.")).display
84
61
  end
62
+
63
+ elsif new_text.strip.empty?
64
+ log.debug { "Empty text" }
65
+ ErrorDialog.new(@main_app, _("The library name " \
66
+ "can not be empty")).display
67
+ elsif library_already_exists new_text
68
+ log.debug { "Already exists" }
69
+ ErrorDialog.new(@main_app,
70
+ _("The library can not be renamed"),
71
+ _("There is already a library named " \
72
+ "'%s'. Please choose a different " \
73
+ "name.") % new_text.strip).display
74
+ else
75
+ log.debug { "Attempting to apply #{path_string}, #{new_text}" }
76
+ path = Gtk::TreePath.new(path_string)
77
+ iter = @library_listview.model.get_iter(path)
78
+ library_name = new_text.strip
79
+ log.info { "library name is #{library_name}" }
80
+ iter[1] = @parent.selected_library.name = library_name
81
+ @parent.setup_move_actions
82
+ @parent.refresh_libraries
85
83
  end
86
84
  end
87
85
 
@@ -110,7 +108,9 @@ module Alexandria
110
108
  cell.editable = iter[2]
111
109
  # log.debug { "exit sidepane: editable #{cell}, #{iter}" }
112
110
  end
113
- renderer.signal_connect("edited", &method(:on_edited_library))
111
+ renderer.signal_connect("edited") do |cell, path_string, new_text|
112
+ on_edited_library(cell, path_string, new_text)
113
+ end
114
114
  @library_listview.append_column(column)
115
115
 
116
116
  @library_listview.set_row_separator_func do |model, iter|
@@ -10,6 +10,7 @@ module Alexandria
10
10
  module UI
11
11
  class SkipEntryDialog < AlertDialog
12
12
  include GetText
13
+ include Logging
13
14
  GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
14
15
 
15
16
  def initialize(parent, message)
@@ -18,8 +19,8 @@ module Alexandria
18
19
  [[Gtk::Stock::CANCEL, Gtk::ResponseType::CANCEL],
19
20
  [_("_Continue"), Gtk::ResponseType::OK]],
20
21
  message)
21
- puts "Opened SkipEntryDialog #{inspect}" if $DEBUG
22
- self.default_response = Gtk::ResponseType::CANCEL
22
+ log.debug { "Opened SkipEntryDialog #{inspect}" }
23
+ dialog.default_response = Gtk::ResponseType::CANCEL
23
24
  end
24
25
 
25
26
  def continue?
@@ -1,21 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (C) 2004-2006 Laurent Sansonetti
3
+ # This file is part of Alexandria.
4
4
  #
5
- # Alexandria is free software; you can redistribute it and/or
6
- # modify it under the terms of the GNU General Public License as
7
- # published by the Free Software Foundation; either version 2 of the
8
- # License, or (at your option) any later version.
9
- #
10
- # Alexandria is distributed in the hope that it will be useful,
11
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
- # General Public License for more details.
14
- #
15
- # You should have received a copy of the GNU General Public
16
- # License along with Alexandria; see the file COPYING. If not,
17
- # write to the Free Software Foundation, Inc., 51 Franklin Street,
18
- # Fifth Floor, Boston, MA 02110-1301 USA.
5
+ # See the file README.md for authorship and licensing information.
19
6
 
20
7
  module Alexandria
21
8
  module UI
@@ -26,35 +13,47 @@ module Alexandria
26
13
  def initialize(parent, smart_library)
27
14
  super(parent)
28
15
 
29
- add_buttons([Gtk::Stock::CANCEL, :cancel],
30
- [Gtk::Stock::SAVE, :ok])
16
+ @smart_library = smart_library
17
+
18
+ dialog.add_buttons([Gtk::Stock::CANCEL, :cancel],
19
+ [Gtk::Stock::SAVE, :ok])
31
20
 
32
- self.title = _("Properties for '%s'") % smart_library.name
21
+ dialog.title = _("Properties for '%s'") % @smart_library.name
33
22
  # FIXME: Should accept just :cancel
34
- self.default_response = Gtk::ResponseType::CANCEL
23
+ dialog.default_response = Gtk::ResponseType::CANCEL
24
+ @smart_library.rules.each { |x| insert_new_rule(x) }
25
+ update_rules_header_box(@smart_library.predicate_operator_rule)
35
26
  end
36
27
 
37
28
  def acquire
38
- show_all
39
- smart_library.rules.each { |x| insert_new_rule(x) }
40
- update_rules_header_box(smart_library.predicate_operator_rule)
41
-
42
- while (response = run) != Gtk::ResponseType::CANCEL
43
- if response == Gtk::ResponseType::HELP
44
- Alexandria::UI.display_help(self, "edit-smart-library")
45
- elsif response == Gtk::ResponseType::OK
46
- if user_confirms_possible_weirdnesses_before_saving?
47
- smart_library.rules = smart_library_rules
48
- smart_library.predicate_operator_rule =
49
- predicate_operator_rule
50
- smart_library.save
51
- yield(smart_library)
52
- break
53
- end
29
+ dialog.show_all
30
+
31
+ while (response = dialog.run) != Gtk::ResponseType::CANCEL
32
+ case response
33
+ when Gtk::ResponseType::HELP
34
+ handle_help_response
35
+ when Gtk::ResponseType::OK
36
+ break if handle_ok_response
54
37
  end
55
38
  end
56
39
 
57
- destroy
40
+ dialog.destroy
41
+ end
42
+
43
+ private
44
+
45
+ def handle_ok_response
46
+ user_confirms_possible_weirdnesses_before_saving? or return
47
+
48
+ @smart_library.rules = smart_library_rules
49
+ @smart_library.predicate_operator_rule =
50
+ predicate_operator_rule
51
+ @smart_library.save
52
+ true
53
+ end
54
+
55
+ def handle_help_response
56
+ Alexandria::UI.display_help(self, "edit-smart-library")
58
57
  end
59
58
  end
60
59
  end
@@ -1,49 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (C) 2004-2006 Laurent Sansonetti
4
- # Copyright (C) 2011, 2016 Matijs van Zuijlen
3
+ # This file is part of Alexandria.
5
4
  #
6
- # Alexandria is free software; you can redistribute it and/or
7
- # modify it under the terms of the GNU General Public License as
8
- # published by the Free Software Foundation; either version 2 of the
9
- # License, or (at your option) any later version.
10
- #
11
- # Alexandria is distributed in the hope that it will be useful,
12
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
- # General Public License for more details.
15
- #
16
- # You should have received a copy of the GNU General Public
17
- # License along with Alexandria; see the file COPYING. If not,
18
- # write to the Free Software Foundation, Inc., 51 Franklin Street,
19
- # Fifth Floor, Boston, MA 02110-1301 USA.
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"
20
9
 
21
10
  module Alexandria
22
11
  module UI
23
- class SmartLibraryPropertiesDialogBase < SimpleDelegator
12
+ class SmartLibraryPropertiesDialogBase
24
13
  include Logging
14
+ include CalendarPopup
25
15
  include GetText
26
16
  GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
27
17
 
28
- attr_reader :predicate_operator_rule
18
+ attr_reader :predicate_operator_rule, :dialog
29
19
 
30
20
  def initialize(parent)
31
21
  @dialog = Gtk::Dialog.new(title: "",
32
22
  parent: parent,
33
23
  flags: :modal,
34
24
  buttons: [[Gtk::Stock::HELP, :help]])
35
- super(@dialog)
36
25
 
37
- self.window_position = :center
38
- self.resizable = true
39
- self.border_width = 4
40
- child.border_width = 12
26
+ @dialog.window_position = :center
27
+ @dialog.resizable = true
28
+ @dialog.border_width = 4
29
+ @dialog.child.border_width = 12
41
30
 
42
31
  main_box = Gtk::Box.new :vertical
43
32
  main_box.border_width = 4
44
33
  main_box.spacing = 8
45
34
 
46
- child << main_box
35
+ @dialog.child << main_box
47
36
 
48
37
  @smart_library_rules = []
49
38
 
@@ -62,16 +51,36 @@ module Alexandria
62
51
 
63
52
  main_box.pack_start(@rules_header_box, expand: false, fill: false)
64
53
  main_box << scrollview
65
- setup_calendar_widgets
66
54
  end
67
55
 
68
- protected
56
+ def handle_date_icon_press(widget, primary, _icon)
57
+ display_calendar_popup(widget) if primary.nick == "primary"
58
+ end
69
59
 
70
- def smart_library_rules
71
- fill_smart_library_rules_values
72
- @smart_library_rules
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)
73
66
  end
74
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
+
75
84
  def has_weirdnesses?
76
85
  fill_smart_library_rules_values
77
86
  smart_library_rules.each do |rule|
@@ -135,140 +144,53 @@ module Alexandria
135
144
  @rules_header_box.show_all
136
145
  end
137
146
 
138
- def insert_new_rule(rule = nil)
139
- rule_box = Gtk::Box.new :horizontal
140
- rule_box.spacing = 8
141
-
142
- left_operand_combo = Gtk::ComboBoxText.new
143
- operator_combo = Gtk::ComboBoxText.new
144
-
145
- value_entry = Gtk::Entry.new
146
-
147
- date_entry = Gtk::Entry.new
148
- date_entry.primary_icon_name = Gtk::Stock::EDIT
149
- date_entry.primary_icon_activatable = true
150
- date_entry.signal_connect("icon-press") do |entry, primary, _icon|
151
- display_calendar_popup(entry) if primary.nick == "primary"
152
- end
153
-
154
- # Really hide the time part of the date entry, as the constructor
155
- # does not seem to do it...
156
- # ##date_entry.children[2..3].each { |x| date_entry.remove(x) }
157
- # ##date_entry.spacing = 8
158
- entry_label = Gtk::Label.new("")
159
-
160
- add_button = Gtk::Button.new(label: "")
161
- add_button.remove(add_button.children.first)
162
- add_button << Gtk::Image.new(stock: Gtk::Stock::ADD,
163
- size: Gtk::IconSize::BUTTON)
164
-
165
- add_button.signal_connect("clicked") { insert_new_rule }
166
-
167
- remove_button = Gtk::Button.new(label: "")
168
- remove_button.remove(remove_button.children.first)
169
- remove_button << Gtk::Image.new(stock: Gtk::Stock::REMOVE,
170
- size: Gtk::IconSize::BUTTON)
171
-
172
- remove_button.signal_connect("clicked") do |_button|
173
- idx = @rules_box.children.index(rule_box)
174
- raise if idx.nil?
175
-
176
- @smart_library_rules.delete_at(idx)
177
- @rules_box.remove(rule_box)
178
- sensitize_remove_rule_buttons
179
- update_rules_header_box
180
- end
181
-
182
- operands = SmartLibrary::Rule::Operands::LEFT
183
- operands.each do |operand|
184
- left_operand_combo.append_text(operand.name)
185
- end
186
- operator_combo.signal_connect("changed") do
187
- operand = operands[left_operand_combo.active]
188
- operations = SmartLibrary::Rule.operations_for_operand(operand)
189
- operation = operations[operator_combo.active]
190
-
191
- value_entry.visible = date_entry.visible = entry_label.visible = false
192
- right_operand = operation.last
193
- unless right_operand.nil?
194
- entry = case right_operand.klass.name
195
- when "Time"
196
- date_entry
197
- else
198
- value_entry
199
- end
200
- entry.visible = true
201
- unless right_operand.name.nil?
202
- entry_label.text = right_operand.name
203
- entry_label.visible = true
204
- end
205
- end
206
-
207
- idx = @rules_box.children.index(rule_box)
208
- new_rule = @smart_library_rules[idx]
209
- if new_rule.nil?
210
- new_rule = SmartLibrary::Rule.new(operand,
211
- operation.first,
212
- nil)
213
- @smart_library_rules << new_rule
214
- end
215
- new_rule.operand = operand
216
- new_rule.operation = operation.first
217
- new_rule.value = nil
218
- end
219
- left_operand_combo.signal_connect("changed") do
220
- operand = operands[left_operand_combo.active]
221
- operator_combo.freeze_notify do
222
- operator_combo.remove_all
223
- operations = SmartLibrary::Rule.operations_for_operand(operand)
224
- operations.each do |operation|
225
- operator = operation.first
226
- operator_combo.append_text(operator.name)
227
- end
228
- operator_combo.active = 0
229
- end
230
- end
231
-
232
- rule_box.pack_start(left_operand_combo, expand: false, fill: false)
233
- rule_box.pack_start(operator_combo, expand: false, fill: false)
234
- rule_box.pack_start(value_entry)
235
- rule_box.pack_start(date_entry)
236
- rule_box.pack_start(entry_label, expand: false, fill: false)
237
- rule_box.pack_end(remove_button, expand: false, fill: false)
238
- rule_box.pack_end(add_button, expand: false, fill: false)
239
-
147
+ def make_rule_box(rule = nil)
148
+ box_controller = SmartLibraryRuleBox.new self
149
+ rule_box = box_controller.rule_box
240
150
  rule_box.show_all
241
- value_entry.visible = date_entry.visible = entry_label.visible = false
242
-
243
151
  @rules_box.pack_start(rule_box, expand: false, fill: true)
244
152
 
245
153
  if rule
154
+ operands = SmartLibrary::Rule::Operands::LEFT
246
155
  operand_idx = operands.index(rule.operand)
247
156
  operations =
248
157
  SmartLibrary::Rule.operations_for_operand(rule.operand)
249
158
  operation_idx = operations.map(&:first).index(rule.operation)
250
159
 
251
160
  if !operand_idx.nil? && !operation_idx.nil?
252
- left_operand_combo.active = operand_idx
253
- operator_combo.active = operation_idx
161
+ box_controller.left_operand_combo.active = operand_idx
162
+ box_controller.operator_combo.active = operation_idx
254
163
  unless rule.value.nil?
255
164
  case rule.value
256
165
  when String
257
- value_entry.text = rule.value
166
+ box_controller.value_entry.text = rule.value
258
167
  when Time
259
- date_entry.text = format_date(rule.value)
168
+ box_controller.date_entry.text = format_date(rule.value)
260
169
  end
261
170
  end
262
171
  end
263
172
  else
264
- left_operand_combo.active = 0
173
+ box_controller.left_operand_combo.active = 0
265
174
  end
175
+ end
266
176
 
177
+ def insert_new_rule(rule = nil)
178
+ make_rule_box(rule)
267
179
  @rules_box.check_resize # force a layout
268
180
  update_rules_header_box
269
181
  sensitize_remove_rule_buttons
270
182
  end
271
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
+
272
194
  def sensitize_remove_rule_buttons
273
195
  boxes = @rules_box.children
274
196
  state = boxes.length > 1
@@ -296,115 +218,10 @@ module Alexandria
296
218
  value = Time.now
297
219
  end
298
220
  end
299
- @smart_library_rules[i].value = value
300
- end
301
- end
302
-
303
- # COPIED and PASTED from book_properties_dialog_base
304
-
305
- def setup_calendar_widgets
306
- @popup_displayed = false
307
- @calendar_popup = Gtk::Window.new # Gtk::Window::POPUP)
308
- # @calendar_popup.modal = true
309
- @calendar_popup.decorated = false
310
- @calendar_popup.skip_taskbar_hint = true
311
- @calendar_popup.skip_pager_hint = true
312
- @calendar_popup.events = [:focus_change_mask]
313
-
314
- @calendar_popup.set_transient_for(@dialog)
315
- @calendar_popup.set_type_hint(:dialog)
316
- @calendar_popup.name = "calendar-popup"
317
- @calendar_popup.resizable = false
318
- # @calendar_popup.border_width = 4
319
- # @calendar_popup.app_paintable = true
320
-
321
- @calendar_popup.signal_connect("focus-out-event") do |_popup, _event|
322
- hide_calendar_popup
323
- false
324
- end
325
-
326
- @calendar = Gtk::Calendar.new
327
- @calendar_popup.add(@calendar)
328
-
329
- @calendar.signal_connect("day-selected") do
330
- date_arr = @calendar.date
331
- year = date_arr[0]
332
- month = date_arr[1] # + 1 # gtk : months 0-indexed, Time.gm : 1-index
333
- day = date_arr[2]
334
- if @calendar_popup_for_entry
335
- time = Time.gm(year, month, day)
336
- @calendar_popup_for_entry.text = format_date(time)
337
- end
338
- end
339
-
340
- @calendar.signal_connect("day-selected-double-click") do
341
- date_arr = @calendar.date
342
- year = date_arr[0]
343
- month = date_arr[1] # + 1 # gtk : months 0-indexed, Time.gm : 1-index
344
- day = date_arr[2]
345
- if @calendar_popup_for_entry
346
- time = Time.gm(year, month, day)
347
- @calendar_popup_for_entry.text = format_date(time)
348
- end
349
- hide_calendar_popup
350
- end
351
- end
352
-
353
- def hide_calendar_popup
354
- @calendar_popup_for_entry = nil
355
-
356
- @calendar_popup.hide_all
357
- self.modal = true
358
-
359
- GLib::Timeout.add(150) do
360
- # If we set @popup_displayed=false immediately, then a click
361
- # event on the primary icon of the Entry simultaneous with
362
- # the focus-out-event of the Calendar causes the Calendar to
363
- # pop up again milliseconds after being closed.
364
- #
365
- # This is never what the user intends.
366
- #
367
- # So we add a small delay before the primary icon's event
368
- # handler is told to pop up the calendar in response to
369
- # clicks.
370
-
371
- @popup_displayed = false
372
- false
373
- end
374
- end
375
-
376
- def display_calendar_popup(entry)
377
- if @popup_displayed
378
- hide_calendar_popup
379
- else
380
- @calendar_popup_for_entry = entry
381
- unless entry.text.strip.empty?
382
- time = parse_date(entry.text)
383
- unless time.nil?
384
- @calendar.year = time.year
385
- @calendar.month = time.month - 1
386
- @calendar.day = time.day
387
- end
388
- end
389
- self.modal = false
390
- @calendar_popup.move(*get_entry_popup_coords(entry))
391
- @calendar_popup.show_all
392
- @popup_displayed = true
221
+ smart_library_rules[i].value = value
393
222
  end
394
223
  end
395
224
 
396
- def get_entry_popup_coords(entry)
397
- gdk_win = entry.parent_window
398
- x, y = gdk_win.origin
399
- alloc = entry.allocation
400
- x += alloc.x
401
- y += alloc.y
402
- y += alloc.height
403
- # x = [0, x].max
404
- # y = [0, y].max
405
- [x, y]
406
- end
407
-
408
225
  def parse_date(datestring)
409
226
  # '%m/%d/%Y' for USA and Canada ; or '%Y-%m-%d' for most of Asia
410
227
  # http://en.wikipedia.org/wiki/Calendar_date#Middle_endian_forms.2C_starting_with_the_month