# Copyright (C) 2004-2006 Laurent Sansonetti
# Copyright (C) 2011 Matijs van Zuijlen
#
# Alexandria is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Alexandria is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with Alexandria; see the file COPYING. If not,
# write to the Free Software Foundation, Inc., 51 Franklin Street,
# Fifth Floor, Boston, MA 02110-1301 USA.
require 'alexandria/scanners/cuecat'
require 'alexandria/scanners/keyboard'
class Gtk::Entry
attr_writer :mandatory
def mandatory?
@mandatory
end
end
module Alexandria
module UI
class ProviderPreferencesBaseDialog < Gtk::Dialog
def initialize(*args)
super(*args)
self.has_separator = false
self.resizable = false
vbox.border_width = 12
@controls = []
end
def fill_table(table, provider)
i = table.n_rows
table.resize(table.n_rows + provider.prefs.length,
table.n_columns)
table.border_width = 12
table.row_spacings = 6
table.column_spacings = 12
@controls.clear
provider.prefs.read.each do |variable|
if variable.name == 'piggyback'
next
# ULTRA-HACK!! for bug #13302
# not displaying the visual choice, as its usually unnecessary
# Either way, this is confusing to the user: FIX
# - Cathal Mc Ginley 2008-02-18
end
if variable.name == 'enabled'
# also don't display Enabled/Disabled
next
end
label = Gtk::Label.new('_' + variable.description + ':')
label.use_underline = true
label.xalign = 0
table.attach_defaults(label, 0, 1, i, i + 1)
if variable.possible_values.nil?
entry = Gtk::Entry.new
entry.text = variable.value.to_s
entry.mandatory = variable.mandatory?
else
entry = Gtk::ComboBox.new
variable.possible_values.each do |value|
entry.append_text(value.to_s)
end
index = variable.possible_values.index(variable.value)
entry.active = index
end
label.mnemonic_widget = entry
@controls << [variable, entry]
table.attach_defaults(entry, 1, 2, i, i + 1)
i += 1
end
table
end
def sync_variables
@controls.each do |variable, entry|
variable.new_value = case entry
when Gtk::ComboBox
variable.possible_values[entry.active]
when Gtk::Entry
entry.text
end
end
end
end
class ProviderPreferencesDialog < ProviderPreferencesBaseDialog
include GetText
GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
def initialize(parent, provider)
super(_('Preferences for %s') % provider.fullname,
parent,
Gtk::Dialog::MODAL,
[Gtk::Stock::CLOSE, Gtk::Dialog::RESPONSE_CLOSE])
self.has_separator = false
self.resizable = false
vbox.border_width = 12
table = Gtk::Table.new(0, 0)
fill_table(table, provider)
vbox.pack_start(table)
signal_connect('destroy') { sync_variables }
show_all
run
destroy
end
end
class NewProviderDialog < ProviderPreferencesBaseDialog
include GetText
GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
def initialize(parent)
super(_('New Provider'),
parent,
Gtk::Dialog::MODAL,
[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL])
@add_button = add_button(Gtk::Stock::ADD,
Gtk::Dialog::RESPONSE_ACCEPT)
instances = BookProviders.abstract_classes.map(&:new)
@selected_instance = nil
@table = Gtk::Table.new(2, 2)
vbox.pack_start(@table)
# Name.
label_name = Gtk::Label.new(_('_Name:'))
label_name.use_underline = true
label_name.xalign = 0
@table.attach_defaults(label_name, 0, 1, 0, 1)
entry_name = Gtk::Entry.new
entry_name.mandatory = true
label_name.mnemonic_widget = entry_name
@table.attach_defaults(entry_name, 1, 2, 0, 1)
# Type.
label_type = Gtk::Label.new(_('_Type:'))
label_type.use_underline = true
label_type.xalign = 0
@table.attach_defaults(label_type, 0, 1, 1, 2)
combo_type = Gtk::ComboBox.new
instances.each do |instance|
combo_type.append_text(instance.name)
end
combo_type.signal_connect('changed') do |cb|
@selected_instance = instances[cb.active]
fill_table(@table, @selected_instance)
sensitize
# FIXME this should be re-written once we have multiple
# abstract providers.
end
combo_type.active = 0
label_type.mnemonic_widget = combo_type
@table.attach_defaults(combo_type, 1, 2, 1, 2)
show_all
if run == Gtk::Dialog::RESPONSE_ACCEPT
@selected_instance.reinitialize(entry_name.text)
sync_variables
else
@selected_instance = nil
end
destroy
end
def instance
@selected_instance
end
private
def sensitize
entries = @table.children.select { |x| x.is_a?(Gtk::Entry) }
entries.each do |entry|
entry.signal_connect('changed') do
sensitive = true
entries.each do |entry2|
if entry2.mandatory?
sensitive = !entry2.text.strip.empty?
break unless sensitive
end
end
@add_button.sensitive = sensitive
end
end
@add_button.sensitive = false
end
end
class PreferencesDialog < BuilderBase
include Alexandria::Logging
include GetText
GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
def initialize(parent, &changed_block)
super('preferences_dialog__builder.glade', widget_names)
@preferences_dialog.transient_for = parent
@changed_block = changed_block
@cols = {
@checkbutton_col_authors => 'col_authors_visible',
@checkbutton_col_isbn => 'col_isbn_visible',
@checkbutton_col_publisher => 'col_publisher_visible',
@checkbutton_col_publish_date => 'col_publish_date_visible',
@checkbutton_col_edition => 'col_edition_visible',
@checkbutton_col_redd => 'col_redd_visible',
@checkbutton_col_own => 'col_own_visible',
@checkbutton_col_want => 'col_want_visible',
@checkbutton_col_rating => 'col_rating_visible',
@checkbutton_col_tags => 'col_tags_visible',
@checkbutton_col_loaned_to => 'col_loaned_to_visible'
}
@cols.each_pair do |checkbutton, pref_name|
if checkbutton
checkbutton.active = Preferences.instance.send(pref_name)
else
log.warn {
"no CheckButton for property #{pref_name} " \
'(probably conflicting versions of GUI and lib code)'
}
end
end
model = Gtk::ListStore.new(String, String, TrueClass, Integer)
@treeview_providers.model = model
reload_providers
model.signal_connect_after('row-changed') { update_priority }
renderer = Gtk::CellRendererToggle.new
renderer.activatable = true
renderer.signal_connect('toggled') do |_rndrr, path|
tree_path = Gtk::TreePath.new(path)
@treeview_providers.selection.select_path(tree_path)
prov = selected_provider
if prov
prov.toggle_enabled
adjust_selected_provider(prov)
# reload_providers
end
end
# renderer.active = true
column = Gtk::TreeViewColumn.new('Enabled', renderer)
column.set_cell_data_func(renderer) do |_col, rndr, _mod, iter|
value = iter[2]
rndr.active = value
end
@treeview_providers.append_column(column)
renderer = Gtk::CellRendererText.new
column = Gtk::TreeViewColumn.new('Providers',
renderer)
# :text => 0)
column.set_cell_data_func(renderer) do |_col, rndr, _mod, iter|
rndr.markup = iter[0]
# enabled = iter[2]
# unless enabled
# rndr.foreground = "gray"
# end
# rndr.active = value
end
@treeview_providers.append_column(column)
@treeview_providers.selection.signal_connect('changed') \
{ sensitize_providers }
@button_prov_setup.sensitive = false
@button_prov_up.sensitive = @button_prov_down.sensitive =
BookProviders.length > 1
@buttonbox_prov.set_child_secondary(@button_prov_add, true)
@buttonbox_prov.set_child_secondary(@button_prov_remove, true)
if BookProviders.abstract_classes.empty?
@checkbutton_prov_advanced.sensitive = false
else
view_advanced = Preferences.instance.view_advanced_settings
if view_advanced
@checkbutton_prov_advanced.active = true
end
end
setup_enable_disable_popup
sensitize_providers
setup_barcode_scanner_tab
end
def widget_names
[:button_prov_add, :button_prov_down, :button_prov_remove,
:button_prov_setup, :button_prov_up, :buttonbox_prov,
:checkbutton_col_authors, :checkbutton_col_edition,
:checkbutton_col_isbn, :checkbutton_col_loaned_to,
:checkbutton_col_own, :checkbutton_col_publish_date,
:checkbutton_col_publisher, :checkbutton_col_rating,
:checkbutton_col_redd, :checkbutton_col_tags,
:checkbutton_col_want, :checkbutton_prov_advanced,
:preferences_dialog, :treeview_providers,
:scanner_device_type, :use_scanning_sound, :use_scan_sound]
end
def setup_barcode_scanner_tab
@scanner_device_model = Gtk::ListStore.new(String, String)
chosen_scanner_name = Preferences.instance.barcode_scanner
index = 0
@scanner_device_type.model = @scanner_device_model
renderer = Gtk::CellRendererText.new
@scanner_device_type.pack_start(renderer, true)
@scanner_device_type.add_attribute(renderer, 'text', 0)
Alexandria::Scanners.each_scanner do |scanner|
iter = @scanner_device_model.append
iter[0] = scanner.display_name
iter[1] = scanner.name
if (chosen_scanner_name == scanner.name)
@scanner_device_type.active = index
end
index += 1
end
@use_scanning_sound.active = Preferences.instance.play_scanning_sound
@use_scan_sound.active = Preferences.instance.play_scan_sound
end
def setup_enable_disable_popup
# New Enable/Disable pop-up menu...
@enable_disable_providers_menu = Gtk::Menu.new
@enable_item = Gtk::MenuItem.new(_('Disable Provider'))
@enable_item.signal_connect('activate') {
prov = selected_provider
prov.toggle_enabled
adjust_selected_provider(prov)
}
@enable_disable_providers_menu.append(@enable_item)
@enable_disable_providers_menu.show_all
@treeview_providers.signal_connect('button_press_event') do |widget, event|
if event_is_right_click(event)
if (path = widget.get_path_at_pos(event.x, event.y))
widget.grab_focus
obj, path = widget.selection, path.first
unless obj.path_is_selected?(path)
widget.unselect_all
obj.select_path(path)
end
sel = widget.selection.selected
if sel
already_enabled = sel[2]
message = already_enabled ? _('Disable Provider') : _('Enable Provider')
@enable_item.label = message
Gtk.idle_add do
@enable_disable_providers_menu.popup(nil, nil, event.button, event.time)
false
end
end
else
puts 'not on a path'
end
end
end
# Popup the menu on Shift-F10
@treeview_providers.signal_connect('popup_menu') {
selected_prov = @treeview_providers.selection.selected
puts selected_prov.inspect
if selected_prov
Gtk.idle_add do
already_enabled = selected_prov[2]
message = already_enabled ? _('Disable Provider') : _('Enable Provider')
@enable_item.label = message
@enable_disable_providers_menu.popup(nil, nil, 0, Gdk::Event::CURRENT_TIME)
false
end
else
puts 'no action'
end
}
end
def event_is_right_click(event)
event.event_type == Gdk::Event::BUTTON_PRESS and event.button == 3
end
def prefs_empty(prefs)
prefs.empty? or (prefs.size == 1 and prefs.first.name == 'enabled')
end
def on_provider_setup
provider = selected_provider
unless prefs_empty(provider.prefs)
ProviderPreferencesDialog.new(@preferences_dialog, provider)
end
end
def on_provider_up
iter = @treeview_providers.selection.selected
previous_path = iter.path
previous_path.prev!
model = @treeview_providers.model
model.move_after(model.get_iter(previous_path), iter)
sensitize_providers
update_priority
end
def on_provider_down
iter = @treeview_providers.selection.selected
next_iter = iter.dup
next_iter.next!
@treeview_providers.model.move_after(iter, next_iter)
sensitize_providers
update_priority
end
def on_provider_advanced_toggled(checkbutton)
on = checkbutton.active?
Preferences.instance.view_advanced_settings = on
@button_prov_add.visible = @button_prov_remove.visible = on
end
def on_provider_add
dialog = NewProviderDialog.new(@preferences_dialog)
if dialog.instance
BookProviders.update_priority
reload_providers
end
end
def on_scanner_device_type(_combo)
iter = @scanner_device_type.active_iter
if iter && iter[1]
Preferences.instance.barcode_scanner = iter[1]
end
end
def on_use_scanning_sound(checkbox)
Preferences.instance.play_scanning_sound = checkbox.active?
end
def on_use_scan_sound(checkbox)
Preferences.instance.play_scan_sound = checkbox.active?
end
def on_provider_remove
provider = selected_provider
dialog = AlertDialog.new(@main_app,
_('Are you sure you want to ' \
'permanently delete the provider ' \
"'%s'?") % provider.fullname,
Gtk::Stock::DIALOG_QUESTION,
[[Gtk::Stock::CANCEL,
Gtk::Dialog::RESPONSE_CANCEL],
[Gtk::Stock::DELETE,
Gtk::Dialog::RESPONSE_OK]],
_('If you continue, the provider and ' \
'all of its preferences will be ' \
'permanently deleted.'))
dialog.default_response = Gtk::Dialog::RESPONSE_CANCEL
dialog.show_all
if dialog.run == Gtk::Dialog::RESPONSE_OK
provider.remove
BookProviders.update_priority
reload_providers
end
dialog.destroy
end
def on_column_toggled(checkbutton)
raise if @cols[checkbutton].nil?
Preferences.instance.send("#{@cols[checkbutton]}=",
checkbutton.active?)
@changed_block.call
end
def on_providers_button_press_event(_widget, event)
# double left click
if event.event_type == Gdk::Event::BUTTON2_PRESS and
event.button == 1
on_provider_setup
end
end
def on_close
@preferences_dialog.destroy
Alexandria::Preferences.instance.save!
end
def on_help
Alexandria::UI.display_help(@preferences_dialog,
'alexandria-preferences')
end
private
def reload_providers
model = @treeview_providers.model
model.clear
BookProviders.each_with_index do |x, index|
iter = model.append
if x.enabled
iter[0] = x.fullname
else
iter[0] = "#{x.fullname}"
end
iter[1] = x.name
iter[2] = x.enabled
iter[3] = index
end
end
def selected_provider
iter = @treeview_providers.selection.selected
unless iter.nil?
BookProviders.find { |x| x.name == iter[1] }
end
end
def adjust_selected_provider(prov)
iter = @treeview_providers.selection.selected
if prov.enabled
iter[0] = prov.fullname
else
iter[0] = "#{prov.fullname}"
end
iter[2] = prov.enabled
end
def sensitize_providers
model = @treeview_providers.model
sel_iter = @treeview_providers.selection.selected
if sel_iter.nil?
# No selection, we are probably called by ListStore#clear
@button_prov_up.sensitive = false
@button_prov_down.sensitive = false
@button_prov_setup.sensitive = false
@button_prov_remove.sensitive = false
else
last_iter = model.get_iter((BookProviders.length - 1).to_s)
@button_prov_up.sensitive = sel_iter != model.iter_first
@button_prov_down.sensitive = sel_iter != last_iter
provider = BookProviders.find { |x| x.name == sel_iter[1] }
@button_prov_setup.sensitive = !prefs_empty(provider.prefs)
@button_prov_remove.sensitive = provider.abstract?
end
end
def update_priority
priority = []
@treeview_providers.model.each do |_model, _path, iter|
priority << iter[1]
end
Preferences.instance.providers_priority = priority
BookProviders.update_priority
end
end
end
end