alexandria-book-collection-manager 0.6.9.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +68 -0
- data/.rubocop_todo.yml +200 -0
- data/CHANGELOG.md +23 -0
- data/COPYING +339 -0
- data/ChangeLog.0 +3598 -0
- data/Gemfile +9 -0
- data/INSTALL.rdoc +156 -0
- data/PACKAGING +36 -0
- data/README.md +88 -0
- data/RELEASE_CHECKLIST +18 -0
- data/Rakefile +264 -0
- data/TODO +24 -0
- data/alexandria-book-collection-manager.gemspec +35 -0
- data/alexandria.desktop.in +10 -0
- data/bin/alexandria +82 -0
- data/doc/AUTHORS +60 -0
- data/doc/BUGS +31 -0
- data/doc/FAQ +369 -0
- data/doc/HACKING +19 -0
- data/doc/NEWS +341 -0
- data/doc/alexandria.1 +120 -0
- data/doc/cuecat_support.rdoc +67 -0
- data/lib/alexandria.rb +85 -0
- data/lib/alexandria/about.rb +82 -0
- data/lib/alexandria/book_providers.rb +411 -0
- data/lib/alexandria/book_providers/adlibris.rb +235 -0
- data/lib/alexandria/book_providers/amazon_aws.rb +261 -0
- data/lib/alexandria/book_providers/amazon_ecs_util.rb +405 -0
- data/lib/alexandria/book_providers/barnes_and_noble.rb +229 -0
- data/lib/alexandria/book_providers/bol_it.rb +162 -0
- data/lib/alexandria/book_providers/deastore.rb +277 -0
- data/lib/alexandria/book_providers/douban.rb +135 -0
- data/lib/alexandria/book_providers/ibs_it.rb +149 -0
- data/lib/alexandria/book_providers/mcu.rb +177 -0
- data/lib/alexandria/book_providers/proxis.rb +205 -0
- data/lib/alexandria/book_providers/pseudomarc.rb +185 -0
- data/lib/alexandria/book_providers/renaud.rb +142 -0
- data/lib/alexandria/book_providers/siciliano.rb +271 -0
- data/lib/alexandria/book_providers/thalia.rb +197 -0
- data/lib/alexandria/book_providers/web.rb +59 -0
- data/lib/alexandria/book_providers/webster_it.rb +173 -0
- data/lib/alexandria/book_providers/worldcat.rb +251 -0
- data/lib/alexandria/book_providers/z3950.rb +422 -0
- data/lib/alexandria/config.rb +8 -0
- data/lib/alexandria/console.rb +31 -0
- data/lib/alexandria/execution_queue.rb +96 -0
- data/lib/alexandria/export_library.rb +536 -0
- data/lib/alexandria/import_library.rb +316 -0
- data/lib/alexandria/import_library_csv.rb +270 -0
- data/lib/alexandria/logging.rb +159 -0
- data/lib/alexandria/models/book.rb +72 -0
- data/lib/alexandria/models/library.rb +714 -0
- data/lib/alexandria/net.rb +53 -0
- data/lib/alexandria/preferences.rb +324 -0
- data/lib/alexandria/scanners.rb +42 -0
- data/lib/alexandria/scanners/cuecat.rb +118 -0
- data/lib/alexandria/scanners/keyboard.rb +57 -0
- data/lib/alexandria/smart_library.rb +525 -0
- data/lib/alexandria/ui.rb +53 -0
- data/lib/alexandria/ui/builder_base.rb +43 -0
- data/lib/alexandria/ui/callbacks.rb +389 -0
- data/lib/alexandria/ui/completion_models.rb +228 -0
- data/lib/alexandria/ui/dialogs/about_dialog.rb +59 -0
- data/lib/alexandria/ui/dialogs/acquire_dialog.rb +640 -0
- data/lib/alexandria/ui/dialogs/alert_dialog.rb +68 -0
- data/lib/alexandria/ui/dialogs/bad_isbns_dialog.rb +43 -0
- data/lib/alexandria/ui/dialogs/barcode_animation.rb +159 -0
- data/lib/alexandria/ui/dialogs/book_properties_dialog.rb +210 -0
- data/lib/alexandria/ui/dialogs/book_properties_dialog_base.rb +432 -0
- data/lib/alexandria/ui/dialogs/export_dialog.rb +172 -0
- data/lib/alexandria/ui/dialogs/import_dialog.rb +205 -0
- data/lib/alexandria/ui/dialogs/misc_dialogs.rb +85 -0
- data/lib/alexandria/ui/dialogs/new_book_dialog.rb +639 -0
- data/lib/alexandria/ui/dialogs/new_book_dialog_manual.rb +153 -0
- data/lib/alexandria/ui/dialogs/new_smart_library_dialog.rb +67 -0
- data/lib/alexandria/ui/dialogs/preferences_dialog.rb +587 -0
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog.rb +56 -0
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog_base.rb +432 -0
- data/lib/alexandria/ui/dndable.rb +81 -0
- data/lib/alexandria/ui/gtk_thread_help.rb +88 -0
- data/lib/alexandria/ui/icons.rb +100 -0
- data/lib/alexandria/ui/iconview.rb +91 -0
- data/lib/alexandria/ui/iconview_tooltips.rb +162 -0
- data/lib/alexandria/ui/init.rb +93 -0
- data/lib/alexandria/ui/libraries_combo.rb +75 -0
- data/lib/alexandria/ui/listview.rb +310 -0
- data/lib/alexandria/ui/main_app.rb +67 -0
- data/lib/alexandria/ui/multi_drag_treeview.rb +150 -0
- data/lib/alexandria/ui/sidepane.rb +194 -0
- data/lib/alexandria/ui/sound.rb +107 -0
- data/lib/alexandria/ui/ui_manager.rb +1308 -0
- data/lib/alexandria/undo_manager.rb +78 -0
- data/lib/alexandria/utils.rb +30 -0
- data/lib/alexandria/version.rb +24 -0
- data/lib/alexandria/web_themes.rb +75 -0
- data/misc/sounds/README +15 -0
- data/misc/sounds/bad_scan.csd +62 -0
- data/misc/sounds/good_scan.csd +61 -0
- data/misc/sounds/scanning.csd +46 -0
- data/po/ChangeLog +488 -0
- data/po/Makefile +44 -0
- data/po/README +29 -0
- data/po/commit-po +72 -0
- data/po/cs.po +1437 -0
- data/po/cy.po +1521 -0
- data/po/de.po +1400 -0
- data/po/el.po +1379 -0
- data/po/es.po +1376 -0
- data/po/fr.po +1420 -0
- data/po/ga.po +1359 -0
- data/po/gl.po +1397 -0
- data/po/it.po +1406 -0
- data/po/ja.po +1355 -0
- data/po/mk.po +1373 -0
- data/po/nb.po +1386 -0
- data/po/nl.po +1405 -0
- data/po/pl.po +1373 -0
- data/po/pt.po +1398 -0
- data/po/pt_BR.po +1409 -0
- data/po/ru.po +1372 -0
- data/po/sk.po +1380 -0
- data/po/sv.po +1402 -0
- data/po/uk.po +1423 -0
- data/po/zh_TW.po +1394 -0
- data/schemas/alexandria.schemas +300 -0
- data/share/alexandria/glade/acquire_dialog__builder.glade +201 -0
- data/share/alexandria/glade/book_properties_dialog__builder.glade +910 -0
- data/share/alexandria/glade/main_app__builder.glade +229 -0
- data/share/alexandria/glade/new_book_dialog__builder.glade +379 -0
- data/share/alexandria/glade/preferences_dialog__builder.glade +733 -0
- data/share/alexandria/icons/alexandria.png +0 -0
- data/share/alexandria/icons/alexandria_small.png +0 -0
- data/share/alexandria/icons/book.png +0 -0
- data/share/alexandria/icons/book_icon.png +0 -0
- data/share/alexandria/icons/book_small.png +0 -0
- data/share/alexandria/icons/cuecat.png +0 -0
- data/share/alexandria/icons/cuecat_inactive.png +0 -0
- data/share/alexandria/icons/favorite_tag.png +0 -0
- data/share/alexandria/icons/less.png +0 -0
- data/share/alexandria/icons/library.png +0 -0
- data/share/alexandria/icons/library_small.png +0 -0
- data/share/alexandria/icons/lookup.png +0 -0
- data/share/alexandria/icons/more.png +0 -0
- data/share/alexandria/icons/no_cover.png +0 -0
- data/share/alexandria/icons/smart_library.png +0 -0
- data/share/alexandria/icons/smart_library_small.png +0 -0
- data/share/alexandria/icons/star_set.png +0 -0
- data/share/alexandria/icons/star_unset.png +0 -0
- data/share/alexandria/icons/view_as_icons.png +0 -0
- data/share/alexandria/icons/view_as_list.png +0 -0
- data/share/alexandria/ui/menus.xml +91 -0
- data/share/alexandria/ui/popups.xml +91 -0
- data/share/alexandria/web-themes/clean/clean.css +85 -0
- data/share/alexandria/web-themes/clean/preview.jpg +0 -0
- data/share/alexandria/web-themes/list/list.css +105 -0
- data/share/alexandria/web-themes/list/preview.jpg +0 -0
- data/share/app-icon/16x16/alexandria.png +0 -0
- data/share/app-icon/16x16/alexandria.svg +263 -0
- data/share/app-icon/22x22/alexandria.png +0 -0
- data/share/app-icon/22x22/alexandria.svg +465 -0
- data/share/app-icon/24x24/alexandria.png +0 -0
- data/share/app-icon/32x32/alexandria.png +0 -0
- data/share/app-icon/32x32/alexandria.svg +813 -0
- data/share/app-icon/32x32/alexandria.xpm +241 -0
- data/share/app-icon/48x48/alexandria.png +0 -0
- data/share/app-icon/scalable/alexandria.svg +700 -0
- data/share/gnome/help/alexandria/C/about.xml +44 -0
- data/share/gnome/help/alexandria/C/adding-books.xml +339 -0
- data/share/gnome/help/alexandria/C/alexandria.xml +185 -0
- data/share/gnome/help/alexandria/C/bugs.xml +18 -0
- data/share/gnome/help/alexandria/C/editing-book-properties.xml +124 -0
- data/share/gnome/help/alexandria/C/exporting.xml +81 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_acquire_from_scanner_process.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_add_by_isbn.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_isbn_import.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_manual_details.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_rename_library_after_import.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_search_results.png +0 -0
- data/share/gnome/help/alexandria/C/figures/editing_book_properties_info.png +0 -0
- data/share/gnome/help/alexandria/C/figures/editing_book_properties_loaning.png +0 -0
- data/share/gnome/help/alexandria/C/figures/exporting_information_html.png +0 -0
- data/share/gnome/help/alexandria/C/figures/getting_started_first_launched.png +0 -0
- data/share/gnome/help/alexandria/C/figures/searching_filtering_views_list_view.png +0 -0
- data/share/gnome/help/alexandria/C/figures/searching_filtering_views_list_view_search.png +0 -0
- data/share/gnome/help/alexandria/C/figures/settings_providers_new_z3950.png +0 -0
- data/share/gnome/help/alexandria/C/figures/smart_libraries_new_smart_library.png +0 -0
- data/share/gnome/help/alexandria/C/figures/working_with_libraries_library_pane.png +0 -0
- data/share/gnome/help/alexandria/C/getting-started.xml +154 -0
- data/share/gnome/help/alexandria/C/gnu-fdl-1.2.xml +543 -0
- data/share/gnome/help/alexandria/C/introduction.xml +142 -0
- data/share/gnome/help/alexandria/C/searching.xml +90 -0
- data/share/gnome/help/alexandria/C/settings.xml +140 -0
- data/share/gnome/help/alexandria/C/smart-libraries.xml +160 -0
- data/share/gnome/help/alexandria/C/working-with-libraries.xml +76 -0
- data/share/gnome/help/alexandria/ChangeLog +99 -0
- data/share/gnome/help/alexandria/fr/alexandria.xml +2292 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_button.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_by_isbn_1.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_by_search_1.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_manually.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_z3950.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_close_button.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_edit_info.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_export_web_page.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_importing.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_library_pane.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_list_view.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_list_view_search.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_loaning.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_main_window.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_remove_button.png +0 -0
- data/share/gnome/help/alexandria/ja/about.xml +33 -0
- data/share/gnome/help/alexandria/ja/adding-books.xml +314 -0
- data/share/gnome/help/alexandria/ja/alexandria.xml +172 -0
- data/share/gnome/help/alexandria/ja/bugs.xml +11 -0
- data/share/gnome/help/alexandria/ja/editing-book-properties.xml +100 -0
- data/share/gnome/help/alexandria/ja/exporting.xml +98 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_acquire_from_scanner_process.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_add_by_isbn.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_isbn_import.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_manual_details.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_rename_library_after_import.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_search_results.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/editing_book_properties_info.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/editing_book_properties_loaning.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/exporting_information_html.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/getting_started_first_launched.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/searching_filtering_views_list_view.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/searching_filtering_views_list_view_search.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/settings_providers_new_z3950.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/smart_libraries_new_smart_library.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/working_with_libraries_library_pane.png +0 -0
- data/share/gnome/help/alexandria/ja/getting-started.xml +144 -0
- data/share/gnome/help/alexandria/ja/gnu-fdl-1.2.xml +541 -0
- data/share/gnome/help/alexandria/ja/introduction.xml +134 -0
- data/share/gnome/help/alexandria/ja/searching.xml +104 -0
- data/share/gnome/help/alexandria/ja/settings.xml +129 -0
- data/share/gnome/help/alexandria/ja/smart-libraries.xml +140 -0
- data/share/gnome/help/alexandria/ja/working-with-libraries.xml +88 -0
- data/share/menu/alexandria +7 -0
- data/share/omf/alexandria/alexandria-C.omf.in +27 -0
- data/share/omf/alexandria/alexandria-fr.omf.in +30 -0
- data/share/sounds/alexandria/bad_scan.ogg +0 -0
- data/share/sounds/alexandria/bad_scan.wav +0 -0
- data/share/sounds/alexandria/good_scan.ogg +0 -0
- data/share/sounds/alexandria/good_scan.wav +0 -0
- data/share/sounds/alexandria/scanning.ogg +0 -0
- data/share/sounds/alexandria/scanning.wav +0 -0
- data/spec/alexandria/library_spec.rb +205 -0
- data/spec/alexandria/preferences_spec.rb +22 -0
- data/spec/alexandria/scanners/cuecat_spec.rb +68 -0
- data/spec/alexandria/smart_library_spec.rb +22 -0
- data/spec/alexandria/ui/dialogs_spec.rb +90 -0
- data/spec/alexandria/ui/iconview_spec.rb +27 -0
- data/spec/alexandria/ui/listview_spec.rb +28 -0
- data/spec/alexandria/ui/main_app_spec.rb +48 -0
- data/spec/alexandria/ui/sidepane_spec.rb +27 -0
- data/spec/alexandria/ui/ui_manager_spec.rb +26 -0
- data/spec/alexandria/ui/ui_utilities_spec.rb +60 -0
- data/spec/alexandria/utilities_spec.rb +50 -0
- data/spec/data/libraries/0.6.1-noisbn/My Library/0201398257.yaml +10 -0
- data/spec/data/libraries/0.6.1-noisbn/My Library/1565920007.yaml +10 -0
- data/spec/data/libraries/0.6.1/My Library/0192812173.yaml +9 -0
- data/spec/data/libraries/0.6.1/My Library/0201398257.cover +0 -0
- data/spec/data/libraries/0.6.1/My Library/0201398257.yaml +10 -0
- data/spec/data/libraries/0.6.1/My Library/1565920007.yaml +10 -0
- data/spec/data/libraries/0.6.2/My Library/9780140266146.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780140266146.yaml +16 -0
- data/spec/data/libraries/0.6.2/My Library/9780140278781.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780140278781.yaml +21 -0
- data/spec/data/libraries/0.6.2/My Library/9780571147168.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780571147168.yaml +20 -0
- data/spec/data/libraries/0.6.2/My Library/9780575079038.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780575079038.yaml +20 -0
- data/spec/data/libraries/0.6.2/My Library/9780755322800.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780755322800.yaml +20 -0
- data/spec/spec_helper.rb +46 -0
- data/tasks/rdoc.rake +6 -0
- data/tasks/setup.rb +30 -0
- data/tasks/spec.rake +29 -0
- data/tasks/test.rake +38 -0
- data/test/application_test.rb +39 -0
- data/test/book_test.rb +34 -0
- data/test/data/isbns.txt +3 -0
- data/test/isbn_test.rb +68 -0
- data/test/providers_test.rb +254 -0
- data/test/test_helper.rb +42 -0
- data/util/rake/fileinstall.rb +313 -0
- data/util/rake/gettextgenerate.rb +158 -0
- data/util/rake/omfgenerate.rb +79 -0
- metadata +452 -0
@@ -0,0 +1,1308 @@
|
|
1
|
+
# Copyright (C) 2004-2006 Laurent Sansonetti
|
2
|
+
# Copyright (C) 2008 Joseph Method
|
3
|
+
# Modifications Copyright (C) 2011 Matijs van Zuijlen
|
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.
|
19
|
+
|
20
|
+
module Alexandria
|
21
|
+
module UI
|
22
|
+
MAX_RATING_STARS = 5
|
23
|
+
class UIManager < BuilderBase
|
24
|
+
attr_accessor :main_app, :actiongroup, :appbar, :prefs, :listview, :iconview, :listview_model,
|
25
|
+
:iconview_model, :filtered_model
|
26
|
+
attr_reader :model
|
27
|
+
include Logging
|
28
|
+
include GetText
|
29
|
+
GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
|
30
|
+
|
31
|
+
module Columns
|
32
|
+
COVER_LIST, COVER_ICON, TITLE, TITLE_REDUCED, AUTHORS,
|
33
|
+
ISBN, PUBLISHER, PUBLISH_DATE, EDITION, RATING, IDENT,
|
34
|
+
NOTES, REDD, OWN, WANT, TAGS, LOANED_TO = (0..17).to_a # duplicated from listview.rb
|
35
|
+
end
|
36
|
+
|
37
|
+
# The maximum number of rating stars displayed.
|
38
|
+
MAX_RATING_STARS = 5
|
39
|
+
|
40
|
+
def initialize(parent)
|
41
|
+
super('main_app__builder.glade', widget_names)
|
42
|
+
@parent = parent
|
43
|
+
|
44
|
+
@library_separator_iter = nil
|
45
|
+
@libraries = nil
|
46
|
+
@move_mid = nil
|
47
|
+
@clicking_on_sidepane = true
|
48
|
+
|
49
|
+
get_preferences
|
50
|
+
load_libraries
|
51
|
+
setup_window_icons
|
52
|
+
setup_callbacks
|
53
|
+
create_uimanager
|
54
|
+
add_menus_and_popups_from_xml
|
55
|
+
setup_menus
|
56
|
+
setup_toolbar
|
57
|
+
setup_move_actions
|
58
|
+
setup_active_model
|
59
|
+
setup_dependents
|
60
|
+
setup_accel_group
|
61
|
+
setup_popups
|
62
|
+
setup_window_events
|
63
|
+
setup_dialog_hooks
|
64
|
+
setup_books_iconview_sorting
|
65
|
+
on_books_selection_changed
|
66
|
+
restore_preferences
|
67
|
+
log.debug { "UI Manager initialized: #{@iconview.model.inspect}" }
|
68
|
+
@clicking_on_sidepane = true
|
69
|
+
|
70
|
+
@library_listview.signal_connect('cursor-changed') do
|
71
|
+
@clicking_on_sidepane = true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def show
|
76
|
+
@main_app.show
|
77
|
+
end
|
78
|
+
|
79
|
+
def widget_names
|
80
|
+
[:main_app, :paned, :vbox1, :library_listview,
|
81
|
+
:notebook, :iconview, :listview, :status_label, :appbar,
|
82
|
+
:progressbar]
|
83
|
+
end
|
84
|
+
|
85
|
+
def create_uimanager
|
86
|
+
log.debug { 'Adding actiongroup to uimanager' }
|
87
|
+
@uimanager = Gtk::UIManager.new
|
88
|
+
@uimanager.insert_action_group(@actiongroup, 0)
|
89
|
+
end
|
90
|
+
|
91
|
+
def setup_dependents
|
92
|
+
@listview_model = Gtk::TreeModelSort.new(@filtered_model)
|
93
|
+
@iconview_model = Gtk::TreeModelSort.new(@filtered_model)
|
94
|
+
@listview_manager = ListViewManager.new @listview, self
|
95
|
+
@iconview_manager = IconViewManager.new @iconview, self
|
96
|
+
@sidepane_manager = SidePaneManager.new @library_listview, self
|
97
|
+
@library_listview = @sidepane_manager.library_listview
|
98
|
+
@listview_manager.setup_listview_columns_visibility
|
99
|
+
@listview_manager.setup_listview_columns_width
|
100
|
+
end
|
101
|
+
|
102
|
+
def setup_callbacks
|
103
|
+
require 'alexandria/ui/callbacks'
|
104
|
+
self.class.send(:include, Callbacks)
|
105
|
+
connect_signals
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_preferences
|
109
|
+
@prefs = Preferences.instance
|
110
|
+
end
|
111
|
+
|
112
|
+
def setup_toolbar
|
113
|
+
log.debug { 'setup_toolbar' }
|
114
|
+
setup_book_providers
|
115
|
+
add_main_toolbar_items
|
116
|
+
@toolbar = @uimanager.get_widget('/MainToolbar')
|
117
|
+
@toolbar.show_arrow = true
|
118
|
+
@toolbar.insert(-1, Gtk::SeparatorToolItem.new)
|
119
|
+
setup_toolbar_combobox
|
120
|
+
setup_toolbar_filter_entry
|
121
|
+
@toolbar.insert(-1, Gtk::SeparatorToolItem.new)
|
122
|
+
setup_toolbar_viewas
|
123
|
+
@toolbar.show_all
|
124
|
+
@actiongroup['Undo'].sensitive = @actiongroup['Redo'].sensitive = false
|
125
|
+
UndoManager.instance.add_observer(self)
|
126
|
+
@vbox1.add(@toolbar, position: 1, expand: false, fill: false)
|
127
|
+
end
|
128
|
+
|
129
|
+
def add_main_toolbar_items
|
130
|
+
mid = @uimanager.new_merge_id
|
131
|
+
@uimanager.add_ui(mid, 'ui/', 'MainToolbar', 'MainToolbar',
|
132
|
+
Gtk::UIManager::TOOLBAR, false)
|
133
|
+
@uimanager.add_ui(mid, 'ui/MainToolbar/', 'New', 'New',
|
134
|
+
Gtk::UIManager::TOOLITEM, false)
|
135
|
+
@uimanager.add_ui(mid, 'ui/MainToolbar/', 'AddBook', 'AddBook',
|
136
|
+
Gtk::UIManager::TOOLITEM, false)
|
137
|
+
# @uimanager.add_ui(mid, "ui/MainToolbar/", "sep", "sep",
|
138
|
+
# Gtk::UIManager::SEPARATOR, false)
|
139
|
+
# @uimanager.add_ui(mid, "ui/MainToolbar/", "Refresh", "Refresh",
|
140
|
+
# Gtk::UIManager::TOOLITEM, false)
|
141
|
+
end
|
142
|
+
|
143
|
+
def setup_toolbar_filter_entry
|
144
|
+
@filter_entry = Gtk::Entry.new
|
145
|
+
@filter_entry.signal_connect('changed', &method(:on_toolbar_filter_entry_changed))
|
146
|
+
@toolitem = Gtk::ToolItem.new
|
147
|
+
@toolitem.expand = true
|
148
|
+
@toolitem.border_width = 5
|
149
|
+
@tooltips.set_tip(@filter_entry,
|
150
|
+
_('Type here the search criterion'), nil)
|
151
|
+
@toolitem << @filter_entry
|
152
|
+
@toolbar.insert(-1, @toolitem)
|
153
|
+
end
|
154
|
+
|
155
|
+
def setup_toolbar_combobox
|
156
|
+
@tooltips = Gtk::Tooltips.new
|
157
|
+
|
158
|
+
cb = Gtk::ComboBox.new
|
159
|
+
cb.set_row_separator_func do |_model, iter|
|
160
|
+
# log.debug { "row_separator" }
|
161
|
+
iter[0] == '-'
|
162
|
+
end
|
163
|
+
[_('Match everything'),
|
164
|
+
'-',
|
165
|
+
_('Title contains'),
|
166
|
+
_('Authors contain'),
|
167
|
+
_('ISBN contains'),
|
168
|
+
_('Publisher contains'),
|
169
|
+
_('Notes contain'),
|
170
|
+
_('Tags contain')
|
171
|
+
].each do |item|
|
172
|
+
cb.append_text(item)
|
173
|
+
end
|
174
|
+
cb.active = 0
|
175
|
+
cb.signal_connect('changed', &method(:on_criterion_combobox_changed))
|
176
|
+
|
177
|
+
# Put the combo box in a event box because it is not currently
|
178
|
+
# possible assign a tooltip to a combo box.
|
179
|
+
eb = Gtk::EventBox.new
|
180
|
+
eb << cb
|
181
|
+
@toolitem = Gtk::ToolItem.new
|
182
|
+
@toolitem.border_width = 5
|
183
|
+
@toolitem << eb
|
184
|
+
@toolbar.insert(-1, @toolitem)
|
185
|
+
@tooltips.set_tip(eb, _('Change the search type'), nil)
|
186
|
+
end
|
187
|
+
|
188
|
+
def setup_toolbar_viewas
|
189
|
+
@toolbar_view_as = Gtk::ComboBox.new
|
190
|
+
@toolbar_view_as.append_text(_('View as Icons'))
|
191
|
+
@toolbar_view_as.append_text(_('View as List'))
|
192
|
+
@toolbar_view_as.active = 0
|
193
|
+
@toolbar_view_as_signal_hid = \
|
194
|
+
@toolbar_view_as.signal_connect('changed', &method(:on_toolbar_view_as_changed))
|
195
|
+
|
196
|
+
# Put the combo box in a event box because it is not currently
|
197
|
+
# possible assign a tooltip to a combo box.
|
198
|
+
eb = Gtk::EventBox.new
|
199
|
+
eb << @toolbar_view_as
|
200
|
+
@toolitem = Gtk::ToolItem.new
|
201
|
+
@toolitem.border_width = 5
|
202
|
+
@toolitem << eb
|
203
|
+
@toolbar.insert(-1, @toolitem)
|
204
|
+
@tooltips.set_tip(eb, _('Choose how to show books'), nil)
|
205
|
+
end
|
206
|
+
|
207
|
+
def setup_book_providers
|
208
|
+
log.debug { 'setup_book_providers' }
|
209
|
+
mid = @uimanager.new_merge_id
|
210
|
+
BookProviders.each do |provider|
|
211
|
+
|
212
|
+
name = provider.action_name
|
213
|
+
['ui/MainMenubar/ViewMenu/OnlineInformation/',
|
214
|
+
'ui/BookPopup/OnlineInformation/',
|
215
|
+
'ui/NoBookPopup/OnlineInformation/'].each do |path|
|
216
|
+
log.debug { "Adding #{name} to #{path}" }
|
217
|
+
@uimanager.add_ui(mid, path, name, name,
|
218
|
+
Gtk::UIManager::MENUITEM, false)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def add_menus_and_popups_from_xml
|
224
|
+
log.debug { 'add_menus_and_popups_from_xml' }
|
225
|
+
['menus.xml', 'popups.xml'].each do |ui_file|
|
226
|
+
@uimanager.add_ui(File.join(Alexandria::Config::DATA_DIR,
|
227
|
+
'ui', ui_file))
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def setup_accel_group
|
232
|
+
log.debug { 'setup_accel_group' }
|
233
|
+
@main_app.add_accel_group(@uimanager.accel_group)
|
234
|
+
end
|
235
|
+
|
236
|
+
def setup_menus
|
237
|
+
@menubar = @uimanager.get_widget('/MainMenubar')
|
238
|
+
@vbox1.add(@menubar, position: 0, expand: false, fill: false)
|
239
|
+
end
|
240
|
+
|
241
|
+
def setup_dialog_hooks
|
242
|
+
log.debug { 'setup_dialog_hooks' }
|
243
|
+
Gtk::AboutDialog.set_url_hook do |_about, link|
|
244
|
+
log.debug { 'set_url_hook' }
|
245
|
+
open_web_browser(link)
|
246
|
+
end
|
247
|
+
Gtk::AboutDialog.set_email_hook do |_about, link|
|
248
|
+
log.debug { 'set_email_hook' }
|
249
|
+
open_email_client('mailto:' + link)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def setup_popups
|
254
|
+
log.debug { 'setup_popups' }
|
255
|
+
@library_popup = @uimanager.get_widget('/LibraryPopup')
|
256
|
+
@smart_library_popup = @uimanager.get_widget('/SmartLibraryPopup')
|
257
|
+
@nolibrary_popup = @uimanager.get_widget('/NoLibraryPopup')
|
258
|
+
@book_popup = @uimanager.get_widget('/BookPopup')
|
259
|
+
@nobook_popup = @uimanager.get_widget('/NoBookPopup')
|
260
|
+
end
|
261
|
+
|
262
|
+
def setup_window_events
|
263
|
+
log.debug { 'setup_window_events' }
|
264
|
+
@main_app.signal_connect('window-state-event', &method(:on_window_state_event))
|
265
|
+
@main_app.signal_connect('destroy', &method(:on_window_destroy))
|
266
|
+
end
|
267
|
+
|
268
|
+
def setup_active_model
|
269
|
+
log.debug { 'setting up active model' }
|
270
|
+
# The active model.
|
271
|
+
|
272
|
+
list = [
|
273
|
+
Gdk::Pixbuf, # COVER_LIST
|
274
|
+
Gdk::Pixbuf, # COVER_ICON
|
275
|
+
String, # TITLE
|
276
|
+
String, # TITLE_REDUCED
|
277
|
+
String, # AUTHORS
|
278
|
+
String, # ISBN
|
279
|
+
String, # PUBLISHER
|
280
|
+
String, # PUBLISH_DATE
|
281
|
+
String, # EDITION
|
282
|
+
Integer, # RATING
|
283
|
+
String, # IDENT
|
284
|
+
String, # NOTES
|
285
|
+
TrueClass, # REDD
|
286
|
+
TrueClass, # OWN
|
287
|
+
TrueClass, # WANT
|
288
|
+
String, # TAGS
|
289
|
+
String # LOANED TO
|
290
|
+
]
|
291
|
+
|
292
|
+
@model = Gtk::ListStore.new(*list)
|
293
|
+
|
294
|
+
# Filter books according to the search toolbar widgets.
|
295
|
+
@filtered_model = Gtk::TreeModelFilter.new(@model)
|
296
|
+
@filtered_model.set_visible_func do |_model, iter|
|
297
|
+
# log.debug { "visible_func" }
|
298
|
+
@filter_books_mode ||= 0
|
299
|
+
filter = @filter_entry.text
|
300
|
+
if filter.empty?
|
301
|
+
true
|
302
|
+
else
|
303
|
+
data = case @filter_books_mode
|
304
|
+
when 0 then
|
305
|
+
(iter[Columns::TITLE] or '') +
|
306
|
+
(iter[Columns::AUTHORS] or '') +
|
307
|
+
(iter[Columns::ISBN] or '') +
|
308
|
+
(iter[Columns::PUBLISHER] or '') +
|
309
|
+
(iter[Columns::NOTES] or '') +
|
310
|
+
(iter[Columns::TAGS] or '')
|
311
|
+
when 2 then iter[Columns::TITLE]
|
312
|
+
when 3 then iter[Columns::AUTHORS]
|
313
|
+
when 4 then iter[Columns::ISBN]
|
314
|
+
when 5 then iter[Columns::PUBLISHER]
|
315
|
+
when 6 then iter[Columns::NOTES]
|
316
|
+
when 7 then iter[Columns::TAGS]
|
317
|
+
end
|
318
|
+
!data.nil? and data.downcase.include?(filter.downcase)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Give filter entry the initial keyboard focus.
|
323
|
+
@filter_entry.grab_focus
|
324
|
+
log.debug { 'done setting up active model' }
|
325
|
+
end
|
326
|
+
|
327
|
+
def on_library_button_press_event(widget, event)
|
328
|
+
log.debug { 'library_button_press_event' }
|
329
|
+
|
330
|
+
# right click
|
331
|
+
if event_is_right_click event
|
332
|
+
log.debug { 'library right click!' }
|
333
|
+
library_already_selected = true
|
334
|
+
if (path = widget.get_path_at_pos(event.x, event.y))
|
335
|
+
@clicking_on_sidepane = true
|
336
|
+
obj, path = widget.is_a?(Gtk::TreeView) ? [widget.selection, path.first] : [widget, path]
|
337
|
+
widget.has_focus = true
|
338
|
+
|
339
|
+
unless obj.path_is_selected?(path)
|
340
|
+
|
341
|
+
log.debug { "Select #{path}" }
|
342
|
+
library_already_selected = false
|
343
|
+
widget.unselect_all
|
344
|
+
obj.select_path(path)
|
345
|
+
sensitize_library selected_library
|
346
|
+
|
347
|
+
if widget.is_a?(Gtk::TreeView)
|
348
|
+
Gtk.idle_add do
|
349
|
+
# cur_path, focus_col = widget.cursor
|
350
|
+
|
351
|
+
widget.focus = true
|
352
|
+
|
353
|
+
widget.set_cursor(path, nil, false)
|
354
|
+
widget.grab_focus
|
355
|
+
widget.has_focus = true
|
356
|
+
false
|
357
|
+
end
|
358
|
+
# widget.has_focus = true
|
359
|
+
end
|
360
|
+
|
361
|
+
# library_already_selected = true
|
362
|
+
end
|
363
|
+
else
|
364
|
+
widget.unselect_all
|
365
|
+
end
|
366
|
+
|
367
|
+
menu = determine_library_popup widget, event
|
368
|
+
|
369
|
+
# Fixes part of bug #25021.
|
370
|
+
#
|
371
|
+
# If the library was not selected when it was right-clicked
|
372
|
+
# we should select the library first (we call on_focus
|
373
|
+
# manually, since the above call to obj.select_path(path) doesn't
|
374
|
+
# seem to suffice).
|
375
|
+
#
|
376
|
+
# Then we wait a while and only *then* pop up the menu.
|
377
|
+
|
378
|
+
if library_already_selected
|
379
|
+
sensitize_library selected_library
|
380
|
+
|
381
|
+
Gtk.idle_add do
|
382
|
+
menu.popup(nil, nil, event.button, event.time)
|
383
|
+
# @clicking_on_sidepane = false
|
384
|
+
false
|
385
|
+
end
|
386
|
+
else
|
387
|
+
Gtk.idle_add do
|
388
|
+
menu.popup(nil, nil, event.button, event.time)
|
389
|
+
# @clicking_on_sidepane = false
|
390
|
+
|
391
|
+
false
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
else
|
396
|
+
# not a right click
|
397
|
+
if (path = widget.get_path_at_pos(event.x, event.y))
|
398
|
+
@clicking_on_sidepane = true
|
399
|
+
obj, path = widget.is_a?(Gtk::TreeView) ? [widget.selection, path.first] : [widget, path]
|
400
|
+
obj.select_path(path)
|
401
|
+
sensitize_library selected_library
|
402
|
+
|
403
|
+
end
|
404
|
+
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def determine_library_popup(widget, event)
|
409
|
+
# widget.grab_focus
|
410
|
+
widget.get_path_at_pos(event.x, event.y).nil? ? @nolibrary_popup : selected_library.is_a?(SmartLibrary) ? @smart_library_popup : @library_popup
|
411
|
+
end
|
412
|
+
|
413
|
+
def event_is_right_click(event)
|
414
|
+
event.event_type == Gdk::Event::BUTTON_PRESS and event.button == 3
|
415
|
+
end
|
416
|
+
|
417
|
+
def on_books_button_press_event(widget, event)
|
418
|
+
log.debug { 'books_button_press_event' }
|
419
|
+
if event_is_right_click event
|
420
|
+
widget.grab_focus
|
421
|
+
|
422
|
+
if (path = widget.get_path_at_pos(event.x.to_i, event.y.to_i))
|
423
|
+
obj, path = widget.is_a?(Gtk::TreeView) ? [widget.selection, path.first] : [widget, path]
|
424
|
+
|
425
|
+
unless obj.path_is_selected?(path)
|
426
|
+
log.debug { "Select #{path}" }
|
427
|
+
widget.unselect_all
|
428
|
+
obj.select_path(path)
|
429
|
+
end
|
430
|
+
else
|
431
|
+
widget.unselect_all
|
432
|
+
end
|
433
|
+
|
434
|
+
menu = (selected_books.empty?) ? @nobook_popup : @book_popup
|
435
|
+
menu.popup(nil, nil, event.button, event.time)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
def get_library_selection_text(library)
|
440
|
+
case library.length
|
441
|
+
when 0
|
442
|
+
_("Library '%s' selected") % library.name
|
443
|
+
|
444
|
+
else
|
445
|
+
n_unrated = library.n_unrated
|
446
|
+
if n_unrated == library.length
|
447
|
+
n_("Library '%s' selected, %d unrated book",
|
448
|
+
"Library '%s' selected, %d unrated books",
|
449
|
+
library.length) % [library.name,
|
450
|
+
library.length]
|
451
|
+
elsif n_unrated == 0
|
452
|
+
n_("Library '%s' selected, %d book",
|
453
|
+
"Library '%s' selected, %d books",
|
454
|
+
library.length) % [library.name,
|
455
|
+
library.length]
|
456
|
+
else
|
457
|
+
n_("Library '%s' selected, %d book, " \
|
458
|
+
'%d unrated',
|
459
|
+
"Library '%s' selected, %d books, " \
|
460
|
+
'%d unrated',
|
461
|
+
library.length) % [library.name,
|
462
|
+
library.length,
|
463
|
+
n_unrated]
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def get_appbar_status(library, books)
|
469
|
+
case books.length
|
470
|
+
when 0
|
471
|
+
get_library_selection_text library
|
472
|
+
when 1
|
473
|
+
_("'%s' selected") % books.first.title
|
474
|
+
else
|
475
|
+
n_('%d book selected', '%d books selected',
|
476
|
+
books.length) % books.length
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
def set_status_label(txt)
|
481
|
+
@status_label.text = txt
|
482
|
+
end
|
483
|
+
|
484
|
+
def on_books_selection_changed
|
485
|
+
library = selected_library
|
486
|
+
books = selected_books
|
487
|
+
set_status_label(get_appbar_status(library, books))
|
488
|
+
# selection = @library_listview.selection.selected ? @library_listview.selection.selected.has_focus? : false
|
489
|
+
|
490
|
+
# Focus is the wrong idiom here.
|
491
|
+
unless @clicking_on_sidepane or (@main_app.focus == @library_listview)
|
492
|
+
# unless @main_app.focus == @library_listview
|
493
|
+
|
494
|
+
log.debug { "Currently focused widget: #{@main_app.focus.inspect}" }
|
495
|
+
log.debug { "#{@library_listview} : #{@library_popup} : #{@listview}" }
|
496
|
+
log.debug { "@library_listview: #{@library_listview.has_focus?} or @library_popup:#{@library_popup.has_focus?}" } # or selection: #{selection}"}
|
497
|
+
log.debug { '@library_listview does *NOT* have focus' }
|
498
|
+
log.debug { "Books are empty: #{books.empty?}" }
|
499
|
+
@actiongroup['Properties'].sensitive = \
|
500
|
+
@actiongroup['OnlineInformation'].sensitive = \
|
501
|
+
books.length == 1
|
502
|
+
@actiongroup['SelectAll'].sensitive = \
|
503
|
+
books.length < library.length
|
504
|
+
|
505
|
+
@actiongroup['Delete'].sensitive = \
|
506
|
+
@actiongroup['DeselectAll'].sensitive = \
|
507
|
+
@actiongroup['Move'].sensitive =
|
508
|
+
@actiongroup['SetRating'].sensitive = !books.empty?
|
509
|
+
|
510
|
+
log.debug { "on_books_selection_changed Delete: #{@actiongroup['Delete'].sensitive?}" }
|
511
|
+
|
512
|
+
if library.is_a?(SmartLibrary)
|
513
|
+
@actiongroup['Delete'].sensitive =
|
514
|
+
@actiongroup['Move'].sensitive = false
|
515
|
+
end
|
516
|
+
|
517
|
+
# Sensitize providers URL
|
518
|
+
if books.length == 1
|
519
|
+
b = books.first
|
520
|
+
# FIXME: Clean up endless negation in this logic
|
521
|
+
no_urls = true
|
522
|
+
BookProviders.each do |provider|
|
523
|
+
has_no_url = true
|
524
|
+
begin
|
525
|
+
has_no_url = (b.isbn.nil? or b.isbn.strip.empty? or provider.url(b).nil?)
|
526
|
+
rescue => ex
|
527
|
+
log.warn { "Error determining URL from #{provider.name}; #{ex.message}" }
|
528
|
+
end
|
529
|
+
@actiongroup[provider.action_name].sensitive = !has_no_url
|
530
|
+
no_urls = false unless has_no_url
|
531
|
+
end
|
532
|
+
if no_urls
|
533
|
+
@actiongroup['OnlineInformation'].sensitive = false
|
534
|
+
end
|
535
|
+
end
|
536
|
+
end
|
537
|
+
@clicking_on_sidepane = false
|
538
|
+
end
|
539
|
+
|
540
|
+
def on_switch_page
|
541
|
+
log.debug { 'on_switch_page' }
|
542
|
+
@actiongroup['ArrangeIcons'].sensitive = @notebook.page == 0
|
543
|
+
on_books_selection_changed
|
544
|
+
end
|
545
|
+
|
546
|
+
def on_focus(widget, _event_focus)
|
547
|
+
if @clicking_on_sidepane or widget == @library_listview
|
548
|
+
log.debug { 'on_focus: @library_listview' }
|
549
|
+
Gtk.idle_add do
|
550
|
+
%w(OnlineInformation SelectAll DeselectAll).each do |action|
|
551
|
+
@actiongroup[action].sensitive = false
|
552
|
+
end
|
553
|
+
@actiongroup['Properties'].sensitive = selected_library.is_a?(SmartLibrary)
|
554
|
+
@actiongroup['Delete'].sensitive = determine_delete_option
|
555
|
+
false
|
556
|
+
end
|
557
|
+
else
|
558
|
+
on_books_selection_changed
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
def determine_delete_option
|
563
|
+
sensitive = (@libraries.all_regular_libraries.length > 1 or selected_library.is_a?(SmartLibrary))
|
564
|
+
sensitive
|
565
|
+
end
|
566
|
+
|
567
|
+
def on_close_sidepane
|
568
|
+
log.debug { 'on_close_sidepane' }
|
569
|
+
@actiongroup['Sidepane'].active = false
|
570
|
+
end
|
571
|
+
|
572
|
+
def select_a_book(book)
|
573
|
+
select_this_book = proc do |bk, view|
|
574
|
+
@filtered_model.refilter
|
575
|
+
iter = iter_from_book bk
|
576
|
+
unless iter
|
577
|
+
next
|
578
|
+
end
|
579
|
+
path = iter.path
|
580
|
+
unless view.model
|
581
|
+
next
|
582
|
+
end
|
583
|
+
path = view.model.convert_path_to_child_path(path)
|
584
|
+
path = @filtered_model.convert_path_to_child_path(path)
|
585
|
+
log.debug { "Path for #{bk.ident} is #{path}" }
|
586
|
+
selection = view.respond_to?(:selection) ? @listview.selection : @iconview
|
587
|
+
selection.unselect_all
|
588
|
+
selection.select_path(path)
|
589
|
+
end
|
590
|
+
begin
|
591
|
+
log.debug { 'select_a_book: listview' }
|
592
|
+
select_this_book.call(book, @listview)
|
593
|
+
log.debug { 'select_a_book: listview' }
|
594
|
+
select_this_book.call(book, @iconview)
|
595
|
+
rescue => ex
|
596
|
+
trace = ex.backtrace.join("\n> ")
|
597
|
+
log.warn { "Failed to automatically select book: #{ex.message} #{trace}" }
|
598
|
+
end
|
599
|
+
# TODO: Figure out why this frequently selects the wrong book!
|
600
|
+
end
|
601
|
+
|
602
|
+
def update(*ary)
|
603
|
+
log.debug { "on_update #{ary}" }
|
604
|
+
caller = ary.first
|
605
|
+
if caller.is_a?(UndoManager)
|
606
|
+
@actiongroup['Undo'].sensitive = caller.can_undo?
|
607
|
+
@actiongroup['Redo'].sensitive = caller.can_redo?
|
608
|
+
elsif caller.is_a?(Library)
|
609
|
+
unless caller.updating?
|
610
|
+
Gtk.queue do
|
611
|
+
handle_update_caller_library ary
|
612
|
+
end
|
613
|
+
end
|
614
|
+
else
|
615
|
+
raise 'unrecognized update event'
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
def handle_update_caller_library(ary)
|
620
|
+
library, kind, book = ary
|
621
|
+
if library == selected_library
|
622
|
+
@iconview.freeze # This makes @iconview.model == nil
|
623
|
+
@listview.freeze # NEW
|
624
|
+
case kind
|
625
|
+
when Library::BOOK_ADDED
|
626
|
+
append_book(book)
|
627
|
+
when Library::BOOK_UPDATED
|
628
|
+
iter = iter_from_ident(book.saved_ident)
|
629
|
+
if iter
|
630
|
+
fill_iter_with_book(iter, book)
|
631
|
+
end
|
632
|
+
when Library::BOOK_REMOVED
|
633
|
+
@model.remove(iter_from_book(book))
|
634
|
+
end
|
635
|
+
@iconview.unfreeze
|
636
|
+
@listview.unfreeze # NEW
|
637
|
+
select_a_book(book) if [Library::BOOK_ADDED, Library::BOOK_UPDATED].include? kind
|
638
|
+
elsif selected_library.is_a?(SmartLibrary)
|
639
|
+
refresh_books
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
#######
|
644
|
+
# private
|
645
|
+
#######
|
646
|
+
|
647
|
+
def open_web_browser(url)
|
648
|
+
if url.nil?
|
649
|
+
log.warn('Attempt to open browser with nil url')
|
650
|
+
return
|
651
|
+
end
|
652
|
+
if (cmd = Preferences.instance.www_browser)
|
653
|
+
launch_command = cmd
|
654
|
+
if cmd.downcase.index('%u')
|
655
|
+
launch_command = cmd.gsub(/%U/i, "\"" + url + "\"")
|
656
|
+
else
|
657
|
+
launch_command = cmd + " \"" + url + "\""
|
658
|
+
end
|
659
|
+
Thread.new { system(launch_command) }
|
660
|
+
else
|
661
|
+
ErrorDialog.new(@main_app,
|
662
|
+
_('Unable to launch the web browser'),
|
663
|
+
_('Check out that a web browser is ' \
|
664
|
+
'configured as default (Desktop ' \
|
665
|
+
'Preferences -> Advanced -> Preferred ' \
|
666
|
+
'Applications) and try again.'))
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
def open_email_client(url)
|
671
|
+
if (cmd = Preferences.instance.email_client)
|
672
|
+
launch_command = cmd
|
673
|
+
if cmd.downcase.index('%u')
|
674
|
+
launch_command = cmd.gsub(/%u/i, "\"" + url + "\"")
|
675
|
+
else
|
676
|
+
launch_command = cmd + " \"" + url + "\""
|
677
|
+
end
|
678
|
+
Thread.new { system(launch_command) }
|
679
|
+
else
|
680
|
+
ErrorDialog.new(@main_app,
|
681
|
+
_('Unable to launch the mail reader'),
|
682
|
+
_('Check out that a mail reader is ' \
|
683
|
+
'configured as default (Desktop ' \
|
684
|
+
'Preferences -> Advanced -> Preferred ' \
|
685
|
+
'Applications) and try again.'))
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
def detach_old_libraries
|
690
|
+
log.debug { 'Un-observing old libraries' }
|
691
|
+
@libraries.all_regular_libraries.each do |library|
|
692
|
+
if library.is_a?(Library)
|
693
|
+
library.delete_observer(self)
|
694
|
+
@completion_models.remove_source(library)
|
695
|
+
end
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
def load_libraries
|
700
|
+
log.info { 'Loading Libraries...' }
|
701
|
+
@completion_models = CompletionModels.instance
|
702
|
+
if @libraries
|
703
|
+
detach_old_libraries
|
704
|
+
@libraries.reload
|
705
|
+
else
|
706
|
+
@libraries = Libraries.instance
|
707
|
+
@libraries.reload
|
708
|
+
handle_ruined_books unless @libraries.ruined_books.empty?
|
709
|
+
end
|
710
|
+
@libraries.all_regular_libraries.each do |library|
|
711
|
+
library.add_observer(self)
|
712
|
+
@completion_models.add_source(library)
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
716
|
+
def handle_ruined_books
|
717
|
+
new_message = _("The data files for the following books are malformed or empty. Do you wish to attempt to download new information for them from the online book providers?\n")
|
718
|
+
|
719
|
+
# message = _("These books do not conform to the ISBN-13
|
720
|
+
# standard. We will attempt to replace them from the book
|
721
|
+
# providers. Otherwise, we will turn them into manual
|
722
|
+
# entries.\n" )
|
723
|
+
|
724
|
+
@libraries.ruined_books.each { |bi|
|
725
|
+
new_message += "\n#{bi[1] or bi[1].inspect}"
|
726
|
+
}
|
727
|
+
recovery_dialog = Gtk::MessageDialog.new(@main_app, Gtk::Dialog::MODAL,
|
728
|
+
Gtk::MessageDialog::WARNING,
|
729
|
+
Gtk::MessageDialog::BUTTONS_OK_CANCEL,
|
730
|
+
new_message).show
|
731
|
+
recovery_dialog.signal_connect('response') do |_dialog, response_type|
|
732
|
+
recovery_dialog.destroy
|
733
|
+
if response_type == Gtk::Dialog::RESPONSE_OK
|
734
|
+
# progress indicator...
|
735
|
+
@progressbar.fraction = 0
|
736
|
+
@appbar.children.first.visible = true # show the progress bar
|
737
|
+
|
738
|
+
total_book_count = @libraries.ruined_books.size
|
739
|
+
fraction_per_book = 1.0 / total_book_count
|
740
|
+
prog_percentage = 0
|
741
|
+
|
742
|
+
@libraries.ruined_books.reverse!
|
743
|
+
Gtk.idle_add do
|
744
|
+
ruined_book = @libraries.ruined_books.pop
|
745
|
+
if ruined_book
|
746
|
+
book, isbn, library = ruined_book
|
747
|
+
begin
|
748
|
+
book_rslt = Alexandria::BookProviders.isbn_search(isbn.to_s)
|
749
|
+
book = book_rslt[0]
|
750
|
+
cover_uri = book_rslt[1]
|
751
|
+
|
752
|
+
# TODO if the book was saved okay, make sure the old
|
753
|
+
# empty yaml file doesn't stick around esp if doing
|
754
|
+
# isbn-10 --> isbn-13 conversion...
|
755
|
+
if isbn.size == 10
|
756
|
+
filename = library.yaml(isbn)
|
757
|
+
log.debug { "removing old file #{filename}" }
|
758
|
+
begin
|
759
|
+
File.delete(filename)
|
760
|
+
rescue => ex
|
761
|
+
log.error { "Could not delete empty file #{filename}" }
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
log.debug { "Trying to add #{book.title}, #{cover_uri} in library ''#{library.name}'" }
|
766
|
+
unless cover_uri.nil?
|
767
|
+
library.save_cover(book, cover_uri)
|
768
|
+
end
|
769
|
+
library << book
|
770
|
+
library.save(book)
|
771
|
+
set_status_label(_("Added '%s' to library '%s'") % [book.title, library.name])
|
772
|
+
|
773
|
+
rescue => ex
|
774
|
+
log.error { "Couldn't add book #{isbn}: #{ex}" }
|
775
|
+
log.error { ex.backtrace.join("\n") }
|
776
|
+
end
|
777
|
+
|
778
|
+
prog_percentage += fraction_per_book
|
779
|
+
@progressbar.fraction = prog_percentage
|
780
|
+
|
781
|
+
true
|
782
|
+
else
|
783
|
+
## Totally copied and pasted from refresh_books...
|
784
|
+
## call this the second strike... (CathalMagus)
|
785
|
+
|
786
|
+
# @iconview.unfreeze
|
787
|
+
# @filtered_model.refilter
|
788
|
+
# @listview.columns_autosize
|
789
|
+
|
790
|
+
@progressbar.fraction = 1
|
791
|
+
## Hide the progress bar.
|
792
|
+
@appbar.children.first.visible = false
|
793
|
+
## Refresh the status bar.
|
794
|
+
set_status_label('')
|
795
|
+
# on_books_selection_changed
|
796
|
+
false
|
797
|
+
end
|
798
|
+
end
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
def cache_scaled_icon(icon, width, height)
|
804
|
+
log.debug { "cache_scaled_icon #{icon}, #{width}, #{height}" }
|
805
|
+
@cache ||= {}
|
806
|
+
@cache[[icon, width, height]] ||= icon.scale(width, height)
|
807
|
+
end
|
808
|
+
|
809
|
+
ICON_TITLE_MAXLEN = 20 # characters
|
810
|
+
ICON_HEIGHT = 90 # pixels
|
811
|
+
REDUCE_TITLE_REGEX = Regexp.new("^(.{#{ICON_TITLE_MAXLEN}}).*$")
|
812
|
+
|
813
|
+
def fill_iter_with_book(iter, book)
|
814
|
+
log.debug { "fill iter #{iter} with book #{book}" }
|
815
|
+
iter[Columns::IDENT] = book.ident.to_s
|
816
|
+
iter[Columns::TITLE] = book.title
|
817
|
+
title = book.title.sub(REDUCE_TITLE_REGEX, '\1...')
|
818
|
+
iter[Columns::TITLE_REDUCED] = title
|
819
|
+
iter[Columns::AUTHORS] = book.authors.join(', ')
|
820
|
+
iter[Columns::ISBN] = book.isbn.to_s
|
821
|
+
iter[Columns::PUBLISHER] = book.publisher
|
822
|
+
iter[Columns::PUBLISH_DATE] = book.publishing_year.to_s
|
823
|
+
iter[Columns::EDITION] = book.edition
|
824
|
+
iter[Columns::NOTES] = (book.notes or '')
|
825
|
+
iter[Columns::LOANED_TO] = (book.loaned_to or '')
|
826
|
+
rating = (book.rating or Book::DEFAULT_RATING)
|
827
|
+
iter[Columns::RATING] = MAX_RATING_STARS - rating # ascending order is the default
|
828
|
+
iter[Columns::OWN] = book.own?
|
829
|
+
iter[Columns::REDD] = book.redd?
|
830
|
+
iter[Columns::WANT] = book.want?
|
831
|
+
if book.tags
|
832
|
+
iter[Columns::TAGS] = book.tags.join(',')
|
833
|
+
else
|
834
|
+
iter[Columns::TAGS] = ''
|
835
|
+
end
|
836
|
+
|
837
|
+
icon = Icons.cover(selected_library, book)
|
838
|
+
log.debug { "Setting icon #{icon} for book #{book.title}" }
|
839
|
+
iter[Columns::COVER_LIST] = cache_scaled_icon(icon, 20, 25)
|
840
|
+
|
841
|
+
if icon.height > ICON_HEIGHT
|
842
|
+
new_width = icon.width / (icon.height / ICON_HEIGHT.to_f)
|
843
|
+
new_height = [ICON_HEIGHT, icon.height].min
|
844
|
+
icon = cache_scaled_icon(icon, new_width, new_height)
|
845
|
+
end
|
846
|
+
if rating == MAX_RATING_STARS
|
847
|
+
icon = icon.tag(Icons::FAVORITE_TAG)
|
848
|
+
end
|
849
|
+
iter[Columns::COVER_ICON] = icon
|
850
|
+
log.debug { 'Full iter: ' + (0..15).map { |num| iter[num].inspect }.join(', ') }
|
851
|
+
end
|
852
|
+
|
853
|
+
def append_book(book, _tail = nil)
|
854
|
+
log.debug { @model.inspect }
|
855
|
+
iter = @model.append
|
856
|
+
log.debug { "iter == #{iter}" }
|
857
|
+
if iter
|
858
|
+
fill_iter_with_book(iter, book)
|
859
|
+
else
|
860
|
+
log.debug { '@model.append' }
|
861
|
+
iter = @model.append
|
862
|
+
fill_iter_with_book(iter, book)
|
863
|
+
log.debug { "no iter for book #{book}" }
|
864
|
+
end
|
865
|
+
library = selected_library
|
866
|
+
if library.deleted_books.include?(book)
|
867
|
+
log.debug { "Stop! Don't delete this book! We re-added it!" }
|
868
|
+
library.undelete(book)
|
869
|
+
UndoManager.instance.push { undoable_delete(library, [book]) }
|
870
|
+
end
|
871
|
+
iter
|
872
|
+
end
|
873
|
+
|
874
|
+
def append_library(library, autoselect = false)
|
875
|
+
log.debug { "append_library #{library.name}" }
|
876
|
+
model = @library_listview.model
|
877
|
+
is_smart = library.is_a?(SmartLibrary)
|
878
|
+
if is_smart
|
879
|
+
if @library_separator_iter.nil?
|
880
|
+
@library_separator_iter = append_library_separator
|
881
|
+
end
|
882
|
+
iter = model.append
|
883
|
+
else
|
884
|
+
iter = if @library_separator_iter.nil?
|
885
|
+
model.append
|
886
|
+
else
|
887
|
+
model.insert_before(@library_separator_iter)
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
iter[0] = is_smart ? Icons::SMART_LIBRARY_SMALL : Icons::LIBRARY_SMALL
|
892
|
+
iter[1] = library.name
|
893
|
+
iter[2] = true # editable?
|
894
|
+
iter[3] = false # separator?
|
895
|
+
if autoselect
|
896
|
+
@library_listview.set_cursor(iter.path,
|
897
|
+
@library_listview.get_column(0),
|
898
|
+
true)
|
899
|
+
@actiongroup['Sidepane'].active = true
|
900
|
+
end
|
901
|
+
iter
|
902
|
+
end
|
903
|
+
|
904
|
+
def append_library_separator
|
905
|
+
log.debug { 'append_library_separator' }
|
906
|
+
iter = @library_listview.model.append
|
907
|
+
iter[0] = nil
|
908
|
+
iter[1] = nil
|
909
|
+
iter[2] = false # editable?
|
910
|
+
iter[3] = true # separator?
|
911
|
+
iter
|
912
|
+
end
|
913
|
+
|
914
|
+
def refresh_books
|
915
|
+
log.debug { 'refresh_books' }
|
916
|
+
@library_listview.set_sensitive(false)
|
917
|
+
library = selected_library
|
918
|
+
@model.clear
|
919
|
+
@iconview.freeze
|
920
|
+
@listview.freeze # NEW / bdewey
|
921
|
+
@progressbar.fraction = 0
|
922
|
+
@appbar.children.first.visible = true # show the progress bar
|
923
|
+
set_status_label(_("Loading '%s'...") % library.name)
|
924
|
+
total = library.length
|
925
|
+
log.debug { "library #{library.name} length #{library.length}" }
|
926
|
+
n = 0
|
927
|
+
|
928
|
+
Gtk.idle_add do
|
929
|
+
|
930
|
+
block_return = true
|
931
|
+
book = library[n]
|
932
|
+
if book
|
933
|
+
Gtk.queue do
|
934
|
+
begin
|
935
|
+
append_book(book)
|
936
|
+
rescue => ex
|
937
|
+
trace = ex.backtrace.join("\n > ")
|
938
|
+
log.error { "append_books failed #{ex.message} #{trace}" }
|
939
|
+
end
|
940
|
+
# convert to percents
|
941
|
+
coeff = total / 100.0
|
942
|
+
percent = n / coeff
|
943
|
+
fraction = percent / 100
|
944
|
+
log.debug { "#index #{n} percent #{percent} fraction #{fraction}" }
|
945
|
+
@progressbar.fraction = fraction
|
946
|
+
n += 1
|
947
|
+
end
|
948
|
+
else
|
949
|
+
Gtk.queue do
|
950
|
+
@iconview.unfreeze
|
951
|
+
@listview.unfreeze # NEW / bdewey
|
952
|
+
@filtered_model.refilter
|
953
|
+
@listview.columns_autosize
|
954
|
+
@progressbar.fraction = 1
|
955
|
+
# Hide the progress bar.
|
956
|
+
@appbar.children.first.visible = false
|
957
|
+
# Refresh the status bar.
|
958
|
+
on_books_selection_changed
|
959
|
+
@library_listview.set_sensitive(true)
|
960
|
+
block_return = false
|
961
|
+
end
|
962
|
+
end
|
963
|
+
|
964
|
+
block_return
|
965
|
+
end
|
966
|
+
end
|
967
|
+
|
968
|
+
def selected_library
|
969
|
+
log.debug { 'selected_library' }
|
970
|
+
if (iter = @library_listview.selection.selected)
|
971
|
+
@libraries.all_libraries.find { |x| x.name == iter[1] }
|
972
|
+
else
|
973
|
+
@libraries.all_libraries.first
|
974
|
+
end
|
975
|
+
end
|
976
|
+
|
977
|
+
def select_library(library)
|
978
|
+
log.debug { "select library #{library}" }
|
979
|
+
iter = @library_listview.model.iter_first
|
980
|
+
ok = true
|
981
|
+
while ok
|
982
|
+
if iter[1] == library.name
|
983
|
+
@library_listview.selection.select_iter(iter)
|
984
|
+
break
|
985
|
+
end
|
986
|
+
ok = iter.next!
|
987
|
+
end
|
988
|
+
end
|
989
|
+
|
990
|
+
def book_from_iter(library, iter)
|
991
|
+
log.debug { "Book from iter: #{library} #{iter}" }
|
992
|
+
library.find { |x| x.ident == iter[Columns::IDENT] }
|
993
|
+
end
|
994
|
+
|
995
|
+
def iter_from_ident(ident)
|
996
|
+
log.debug { "#{ident}" }
|
997
|
+
iter = @model.iter_first
|
998
|
+
ok = true
|
999
|
+
while ok
|
1000
|
+
if iter[Columns::IDENT] == ident
|
1001
|
+
return iter
|
1002
|
+
end
|
1003
|
+
ok = iter.next!
|
1004
|
+
end
|
1005
|
+
nil
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
def iter_from_book(book)
|
1009
|
+
log.debug { "#{book}" }
|
1010
|
+
iter_from_ident(book.ident)
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
def collate_selected_books(page)
|
1014
|
+
a = []
|
1015
|
+
library = selected_library
|
1016
|
+
view = page == 0 ? @iconview : @listview
|
1017
|
+
selection = page == 0 ? @iconview : @listview.selection
|
1018
|
+
|
1019
|
+
selection.selected_each do |_the_view, path|
|
1020
|
+
# don't use the_view which is passed in by this block
|
1021
|
+
# as it doesn't consider the filtering for some reason
|
1022
|
+
# see bug #24568
|
1023
|
+
unless view.model
|
1024
|
+
next
|
1025
|
+
end
|
1026
|
+
path = view.model.convert_path_to_child_path(path)
|
1027
|
+
if path
|
1028
|
+
path = @filtered_model.convert_path_to_child_path(path)
|
1029
|
+
# FIX this sometimes returns a nil path for iconview...
|
1030
|
+
if path
|
1031
|
+
iter = @model.get_iter(path)
|
1032
|
+
if iter
|
1033
|
+
a << book_from_iter(library, iter)
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
# This used to cause a crash when manually adding the first
|
1037
|
+
# book to a Library displayed in an Iconview
|
1038
|
+
# TODO find root cause of this
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
a
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
def selected_books
|
1045
|
+
a = collate_selected_books(@notebook.page)
|
1046
|
+
selected = a.select { |x| !x.nil? }
|
1047
|
+
log.debug { "Selected books = #{selected.inspect}" }
|
1048
|
+
selected
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
def refresh_libraries
|
1052
|
+
log.debug { 'refresh_libraries' }
|
1053
|
+
library = selected_library
|
1054
|
+
|
1055
|
+
# Change the application's title.
|
1056
|
+
@main_app.title = library.name + ' - ' + TITLE
|
1057
|
+
|
1058
|
+
# Disable the selected library in the move libraries actions.
|
1059
|
+
@libraries.all_regular_libraries.each do |i_library|
|
1060
|
+
action = @actiongroup[i_library.action_name]
|
1061
|
+
if action
|
1062
|
+
action.sensitive = i_library != library
|
1063
|
+
end
|
1064
|
+
end
|
1065
|
+
sensitize_library library
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
def sensitize_library(library)
|
1069
|
+
smart = library.is_a?(SmartLibrary)
|
1070
|
+
log.debug { "sensitize_library: smartlibrary = #{smart}" }
|
1071
|
+
Gtk.idle_add do
|
1072
|
+
@actiongroup['AddBook'].sensitive = !smart
|
1073
|
+
@actiongroup['AddBookManual'].sensitive = !smart
|
1074
|
+
@actiongroup['Properties'].sensitive = smart
|
1075
|
+
can_delete = smart || (@libraries.all_regular_libraries.length > 1)
|
1076
|
+
@actiongroup['Delete'].sensitive = can_delete ## true #(@libraries.all_regular_libraries.length > 1)
|
1077
|
+
log.debug { "sensitize_library delete: #{@actiongroup['Delete'].sensitive?}" }
|
1078
|
+
false
|
1079
|
+
end
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
def get_view_actiongroup
|
1083
|
+
case @prefs.view_as
|
1084
|
+
when 0
|
1085
|
+
@actiongroup['AsIcons']
|
1086
|
+
when 1
|
1087
|
+
@actiongroup['AsList']
|
1088
|
+
end
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
def restore_preferences
|
1092
|
+
log.debug { 'Restoring preferences...' }
|
1093
|
+
if @prefs.maximized
|
1094
|
+
@main_app.maximize
|
1095
|
+
else
|
1096
|
+
@main_app.move(*@prefs.position) unless @prefs.position == [0, 0]
|
1097
|
+
@main_app.resize(*@prefs.size)
|
1098
|
+
@maximized = false
|
1099
|
+
end
|
1100
|
+
@paned.position = @prefs.sidepane_position
|
1101
|
+
@actiongroup['Sidepane'].active = @prefs.sidepane_visible
|
1102
|
+
@actiongroup['Toolbar'].active = @prefs.toolbar_visible
|
1103
|
+
@actiongroup['Statusbar'].active = @prefs.statusbar_visible
|
1104
|
+
@appbar.visible = @prefs.statusbar_visible
|
1105
|
+
action = get_view_actiongroup
|
1106
|
+
action.activate
|
1107
|
+
library = nil
|
1108
|
+
unless @prefs.selected_library.nil?
|
1109
|
+
library = @libraries.all_libraries.find do |x|
|
1110
|
+
x.name == @prefs.selected_library
|
1111
|
+
end
|
1112
|
+
end
|
1113
|
+
select_a_library library
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
def select_a_library(library)
|
1117
|
+
if library
|
1118
|
+
select_library(library)
|
1119
|
+
else
|
1120
|
+
# Select the first item by default.
|
1121
|
+
iter = @library_listview.model.iter_first
|
1122
|
+
@library_listview.selection.select_iter(iter)
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
def save_preferences
|
1127
|
+
log.debug { 'save_preferences' }
|
1128
|
+
@prefs.position = @main_app.position
|
1129
|
+
@prefs.size = @main_app.allocation.to_a[2..3]
|
1130
|
+
@prefs.maximized = @maximized
|
1131
|
+
@prefs.sidepane_position = @paned.position
|
1132
|
+
@prefs.sidepane_visible = @actiongroup['Sidepane'].active?
|
1133
|
+
@prefs.toolbar_visible = @actiongroup['Toolbar'].active?
|
1134
|
+
@prefs.statusbar_visible = @actiongroup['Statusbar'].active?
|
1135
|
+
@prefs.view_as = @notebook.page
|
1136
|
+
@prefs.selected_library = selected_library.name
|
1137
|
+
cols_width = {}
|
1138
|
+
@listview.columns.each do |c|
|
1139
|
+
cols_width[c.title] = [c.widget.size_request.first, c.width].max
|
1140
|
+
end
|
1141
|
+
@prefs.cols_width = '{' + cols_width.to_a.map do |t, v|
|
1142
|
+
'"' + t + '": ' + v.to_s
|
1143
|
+
end.join(', ') + '}'
|
1144
|
+
log.debug { "cols_width: #{@prefs.cols_width} " }
|
1145
|
+
@prefs.save!
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
def undoable_move(source, dest, books)
|
1149
|
+
log.debug { 'undoable_move' }
|
1150
|
+
Library.move(source, dest, *books)
|
1151
|
+
UndoManager.instance.push { undoable_move(dest, source, books) }
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
def move_selected_books_to_library(library)
|
1155
|
+
books = selected_books.select do |book|
|
1156
|
+
!library.include?(book) or
|
1157
|
+
ConflictWhileCopyingDialog.new(@main_app,
|
1158
|
+
library,
|
1159
|
+
book).replace?
|
1160
|
+
end
|
1161
|
+
undoable_move(selected_library, library, books)
|
1162
|
+
end
|
1163
|
+
|
1164
|
+
def setup_move_actions
|
1165
|
+
@actiongroup.actions.each do |action|
|
1166
|
+
next unless /^MoveIn/.match(action.name)
|
1167
|
+
@actiongroup.remove_action(action)
|
1168
|
+
end
|
1169
|
+
actions = []
|
1170
|
+
@libraries.all_regular_libraries.each do |library|
|
1171
|
+
actions << [
|
1172
|
+
library.action_name, nil,
|
1173
|
+
_("In '_%s'") % library.name,
|
1174
|
+
nil, nil, proc { move_selected_books_to_library(library) }
|
1175
|
+
]
|
1176
|
+
end
|
1177
|
+
@actiongroup.add_actions(actions)
|
1178
|
+
@uimanager.remove_ui(@move_mid) if @move_mid
|
1179
|
+
@move_mid = @uimanager.new_merge_id
|
1180
|
+
@libraries.all_regular_libraries.each do |library|
|
1181
|
+
name = library.action_name
|
1182
|
+
['ui/MainMenubar/EditMenu/Move/',
|
1183
|
+
'ui/BookPopup/Move/'].each do |path|
|
1184
|
+
@uimanager.add_ui(@move_mid, path, name, name,
|
1185
|
+
Gtk::UIManager::MENUITEM, false)
|
1186
|
+
end
|
1187
|
+
end
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
def current_view
|
1191
|
+
case @notebook.page
|
1192
|
+
when 0
|
1193
|
+
@iconview
|
1194
|
+
when 1
|
1195
|
+
@listview
|
1196
|
+
end
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
# Gets the sort order of the current library, for use by export
|
1200
|
+
def library_sort_order
|
1201
|
+
# added by Cathal Mc Ginley, 23 Oct 2007
|
1202
|
+
log.debug { "library_sort_order #{@notebook.page}: #{@iconview.model.inspect} #{@listview.model.inspect}" }
|
1203
|
+
sorted_on = current_view.model.sort_column_id
|
1204
|
+
if sorted_on
|
1205
|
+
sort_column = sorted_on[0]
|
1206
|
+
sort_order = sorted_on[1]
|
1207
|
+
|
1208
|
+
column_ids_to_attributes = { 2 => :title,
|
1209
|
+
4 => :authors,
|
1210
|
+
5 => :isbn,
|
1211
|
+
6 => :publisher,
|
1212
|
+
7 => :publishing_year,
|
1213
|
+
8 => :edition, # binding
|
1214
|
+
12 => :redd,
|
1215
|
+
13 => :own,
|
1216
|
+
14 => :want,
|
1217
|
+
9 => :rating }
|
1218
|
+
|
1219
|
+
sort_attribute = column_ids_to_attributes[sort_column]
|
1220
|
+
ascending = (sort_order == Gtk::SORT_ASCENDING)
|
1221
|
+
LibrarySortOrder.new(sort_attribute, ascending)
|
1222
|
+
else
|
1223
|
+
LibrarySortOrder::Unsorted.new
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
def get_previous_selected_library(library)
|
1228
|
+
log.debug { "get_previous_selected_library: #{library}" }
|
1229
|
+
@previous_selected_library = selected_library
|
1230
|
+
if @previous_selected_library != library
|
1231
|
+
select_library(library)
|
1232
|
+
else
|
1233
|
+
@previous_selected_library = nil
|
1234
|
+
end
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
def remove_library_iter
|
1238
|
+
old_iter = @library_listview.selection.selected
|
1239
|
+
# commenting out this code seems to fix #20681
|
1240
|
+
# "crashes when switching to smart library mid-load"
|
1241
|
+
# next_iter = @library_listview.selection.selected
|
1242
|
+
# next_iter.next!
|
1243
|
+
@library_listview.model.remove(old_iter)
|
1244
|
+
# @library_listview.selection.select_iter(next_iter)
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
def undoable_delete(library, books = nil)
|
1248
|
+
# Deleting a library.
|
1249
|
+
if books.nil?
|
1250
|
+
library.delete_observer(self) if library.is_a?(Library)
|
1251
|
+
library.delete
|
1252
|
+
@libraries.remove_library(library)
|
1253
|
+
remove_library_separator
|
1254
|
+
remove_library_iter
|
1255
|
+
get_previous_selected_library library
|
1256
|
+
setup_move_actions
|
1257
|
+
select_library(@previous_selected_library) unless @previous_selected_library.nil?
|
1258
|
+
@previous_selected_library = nil
|
1259
|
+
else
|
1260
|
+
# Deleting books.
|
1261
|
+
books.each { |book| library.delete(book) }
|
1262
|
+
end
|
1263
|
+
UndoManager.instance.push { undoable_undelete(library, books) }
|
1264
|
+
end
|
1265
|
+
|
1266
|
+
def remove_library_separator
|
1267
|
+
if !@library_separator_iter.nil? and @libraries.all_smart_libraries.empty?
|
1268
|
+
@library_listview.model.remove(@library_separator_iter)
|
1269
|
+
@library_separator_iter = nil
|
1270
|
+
end
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
def undoable_undelete(library, books = nil)
|
1274
|
+
# Undeleting a library.
|
1275
|
+
if books.nil?
|
1276
|
+
library.undelete
|
1277
|
+
@libraries.add_library(library)
|
1278
|
+
append_library(library)
|
1279
|
+
setup_move_actions
|
1280
|
+
library.add_observer(self) if library.is_a?(Library)
|
1281
|
+
# Undeleting books.
|
1282
|
+
else
|
1283
|
+
books.each { |book| library.undelete(book) }
|
1284
|
+
end
|
1285
|
+
select_library(library)
|
1286
|
+
UndoManager.instance.push { undoable_delete(library, books) }
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
def setup_window_icons
|
1290
|
+
@main_app.icon = Icons::ALEXANDRIA_SMALL
|
1291
|
+
Gtk::Window.set_default_icon_name('alexandria')
|
1292
|
+
@main_app.icon_name = 'alexandria'
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
ICONS_SORTS = [
|
1296
|
+
Columns::TITLE, Columns::AUTHORS, Columns::ISBN,
|
1297
|
+
Columns::PUBLISHER, Columns::EDITION, Columns::RATING, Columns::REDD, Columns::OWN, Columns::WANT
|
1298
|
+
]
|
1299
|
+
|
1300
|
+
def setup_books_iconview_sorting
|
1301
|
+
sort_order = @prefs.reverse_icons ? Gtk::SORT_DESCENDING : Gtk::SORT_ASCENDING
|
1302
|
+
mode = ICONS_SORTS[@prefs.arrange_icons_mode]
|
1303
|
+
@iconview_model.set_sort_column_id(mode, sort_order)
|
1304
|
+
@filtered_model.refilter # force redraw
|
1305
|
+
end
|
1306
|
+
end
|
1307
|
+
end
|
1308
|
+
end
|