#!/usr/bin/perl -w ############################################################################### # # # Telbook - A simple telephone book # # Copyright (C) 2003 Torsten Schönfeld # # # # This program 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. # # # # This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # ############################################################################### use strict; use Gtk2 1.02 -init; use Gtk2::Gdk::Keysyms; ############################################################################### package ui; use constant COLUMN_NAME => 0; use constant COLUMN_HOME => 1; use constant COLUMN_MOBILE => 2; sub new { my ($class) = @_; my $self = {}; bless($self, $class); $self -> changed(0); return $self; } sub changed { my ($self, $value) = @_; if (defined($value)) { if ($value == 1 && $self -> { _changed } != 1) { $self -> { main_window } -> set_title($self -> { main_window } -> get_title() . " (*)"); } $self -> { _changed } = $value; } return $self -> { _changed }; } sub create_buttons { my ($self) = @_; my $button_hbox = Gtk2::HBox -> new(0, 0); my $button_open = Gtk2::Button -> new("_Open"); my $button_save = Gtk2::Button -> new("_Save"); my $button_save_as = Gtk2::Button -> new("Save _As"); my $button_quit = Gtk2::Button -> new("_Quit"); $button_open -> signal_connect("clicked", sub { return $self -> file_open(); }); $button_save -> signal_connect("clicked", sub { return $self -> file_save(); }); $button_save_as -> signal_connect("clicked", sub { return $self -> file_save_as(); }); $button_quit -> signal_connect("clicked", sub { return $self -> quit(); }); $button_hbox -> pack_start($button_open, 1, 1, 0); $button_hbox -> pack_start($button_save, 1, 1, 0); $button_hbox -> pack_start($button_save_as, 1, 1, 0); $button_hbox -> pack_start($button_quit, 1, 1, 0); return $button_hbox; } sub create_edit_frame { my ($self) = @_; my $edit_frame = Gtk2::Frame -> new("Edit"); my $edit_vbox = Gtk2::VBox -> new(0, 0); $edit_vbox -> set_border_width(5); my $edit_entry_hbox = Gtk2::HBox -> new(0, 0); my $edit_button_hbox = Gtk2::HBox -> new(0, 0); my $edit_entry_name = Gtk2::Entry -> new(); my $edit_entry_home = Gtk2::Entry -> new(); my $edit_entry_mobile = Gtk2::Entry -> new(); my $button_add = Gtk2::Button -> new("_Add"); my $button_replace = Gtk2::Button -> new("_Replace"); foreach ($edit_entry_name, $edit_entry_home, $edit_entry_mobile) { $_ -> signal_connect(activate => sub { $self -> { main_view } -> get_selection() -> get_selected() ? $button_replace -> clicked() : $button_add -> clicked(); }); } $button_add -> signal_connect(clicked => sub { my ($name, $home, $mobile) = ($edit_entry_name -> get_text(), $edit_entry_home -> get_text(), $edit_entry_mobile -> get_text()); if ($name ne "") { if ($self -> valid($name, $home, $mobile)) { $self -> { main_model } -> set( $self -> { main_model } -> append(), COLUMN_NAME, $name, COLUMN_HOME, $home || "", COLUMN_MOBILE, $mobile || ""); $self -> changed(1); $edit_entry_name -> set_text(""); $edit_entry_home -> set_text(""); $edit_entry_mobile -> set_text(""); $edit_entry_name -> grab_focus(); } else { $self -> error("Tab characters may not be used."); } } else { # Is this message really necessary? $self -> error("The name field may not be empty."); } return 1; }); $button_replace -> signal_connect(clicked => sub { my ($model, $iterator) = $self -> { main_view } -> get_selection() -> get_selected(); if (defined($model) && defined($iterator)) { my ($name, $home, $mobile) = ($edit_entry_name -> get_text(), $edit_entry_home -> get_text(), $edit_entry_mobile -> get_text()); if ($name ne "") { if ($self -> valid($name, $home, $mobile)) { $self -> { main_model } -> set( $iterator, COLUMN_NAME, $name, COLUMN_HOME, $home || "", COLUMN_MOBILE, $mobile || ""); $self -> changed(1); } else { $self -> error("Tab characters may not be used."); } } else { # See above. $self -> error("The name field may not be empty."); } } return 1; }); $edit_entry_hbox -> pack_start($edit_entry_name, 1, 1, 0); $edit_entry_hbox -> pack_start($edit_entry_home, 1, 1, 0); $edit_entry_hbox -> pack_start($edit_entry_mobile, 1, 1, 0); $edit_button_hbox -> pack_start($button_add, 1, 1, 0); $edit_button_hbox -> pack_start($button_replace, 1, 1, 0); $edit_vbox -> pack_start($edit_entry_hbox, 1, 1, 0); $edit_vbox -> pack_start($edit_button_hbox, 1, 1, 0); $edit_frame -> add($edit_vbox); $self -> { edit_entry_name } = $edit_entry_name; $self -> { edit_entry_home } = $edit_entry_home; $self -> { edit_entry_mobile } = $edit_entry_mobile; return $edit_frame; } sub create_filter_frame { my ($self) = @_; my $filter_frame = Gtk2::Frame -> new("Filter"); my $filter_vbox = Gtk2::VBox -> new(0, 0); $filter_vbox -> set_border_width(5); my $filter_entry_hbox = Gtk2::HBox -> new(0, 0); my $filter_button_hbox = Gtk2::HBox -> new(0, 0); my $filter_entry_name = Gtk2::Entry -> new(); my $filter_entry_home = Gtk2::Entry -> new(); my $filter_entry_mobile = Gtk2::Entry -> new(); $filter_entry_name -> signal_connect(changed => sub { return $self -> filter_rows(); }); $filter_entry_home -> signal_connect(changed => sub { return $self -> filter_rows(); }); $filter_entry_mobile -> signal_connect(changed => sub { return $self -> filter_rows(); }); $filter_entry_hbox -> pack_start($filter_entry_name, 1, 1, 0); $filter_entry_hbox -> pack_start($filter_entry_home, 1, 1, 0); $filter_entry_hbox -> pack_start($filter_entry_mobile, 1, 1, 0); $filter_vbox -> pack_start($filter_entry_hbox, 1, 1, 0); $filter_vbox -> pack_start($filter_button_hbox, 1, 1, 0); $filter_frame -> add($filter_vbox); $self -> { filter_entry_name } = $filter_entry_name; $self -> { filter_entry_home } = $filter_entry_home; $self -> { filter_entry_mobile } = $filter_entry_mobile; return $filter_frame; } sub create_main_list_container { my ($self) = @_; my $container = Gtk2::ScrolledWindow -> new(); my $model = Gtk2::ListStore -> new(qw(Glib::String Glib::String Glib::String)); my $view = Gtk2::TreeView -> new($model); foreach ([COLUMN_NAME, "Name", 240], [COLUMN_HOME, "Home", 100], [COLUMN_MOBILE, "Mobile", 100]) { my ($column, $title, $width) = @{$_}; my $cell_renderer = Gtk2::CellRendererText -> new(); my $view_column = Gtk2::TreeViewColumn -> new_with_attributes($title, $cell_renderer, text => $column); $view_column -> set_min_width($width); $view_column -> set_sort_column_id($column); $view -> append_column($view_column); } $model -> set_sort_column_id(COLUMN_NAME, "ascending"); $view -> set_rules_hint(1); $view -> get_selection() -> set_mode("single"); $view -> get_selection() -> signal_connect(changed => sub { my ($selection) = @_; my ($model, $iterator) = $selection -> get_selected(); if (defined($model) && defined($iterator)) { $self -> { edit_entry_name } -> set_text($model -> get($iterator, COLUMN_NAME)); $self -> { edit_entry_home } -> set_text($model -> get($iterator, COLUMN_HOME)); $self -> { edit_entry_mobile } -> set_text($model -> get($iterator, COLUMN_MOBILE)); } else { $self -> { edit_entry_name } -> set_text(""); $self -> { edit_entry_home } -> set_text(""); $self -> { edit_entry_mobile } -> set_text(""); } return 1; }); $view -> signal_connect(key_press_event => sub { my ($view, $event) = @_; if ($event -> keyval == $Gtk2::Gdk::Keysyms{ Delete }) { my $iterator = $view -> get_selection() -> get_selected(); if (defined($iterator)) { $model -> remove($iterator); $self -> changed(1); } return 1; } return 0; }); $container -> add($view); $container -> set_policy("automatic", "automatic"); $self -> { main_model } = $model; $self -> { main_view } = $view; return $container; } sub create_main_window { my ($self) = @_; my $main_window = Gtk2::Window -> new("toplevel"); my $main_vbox = Gtk2::VBox -> new(0, 5); $main_window -> set_title("Telephone Book"); $main_window -> set_default_size(500, 300); $main_window -> set_border_width(5); $main_window -> signal_connect(delete_event => sub { return $self -> quit(); }); $main_vbox -> pack_start($self -> create_edit_frame(), 0, 0, 0); $main_vbox -> pack_start($self -> create_filter_frame(), 0, 0, 0); $main_vbox -> pack_start($self -> create_main_list_container(), 1, 1, 0); $main_vbox -> pack_start($self -> create_buttons(), 0, 0, 0); $main_window -> add($main_vbox); $main_window -> show_all(); $self -> { main_window } = $main_window; return 1; } sub error { my ($self, $label) = @_; my $dialog = Gtk2::MessageDialog -> new($self -> { main_window }, [qw(modal destroy_with_parent)], "error", "close", $label); $dialog -> run(); $dialog -> destroy(); return 0; } sub file { my ($self, $file) = @_; if (defined($file)) { $self -> { _file } = $file; } return $self -> { _file }; } sub initialize { my ($self) = @_; $self -> create_main_window(); return 1; } sub filter_rows { my ($self) = @_; my ($filter_name, $filter_home, $filter_mobile) = ($self -> { filter_entry_name } -> get_text(), $self -> { filter_entry_home } -> get_text(), $self -> { filter_entry_mobile } -> get_text()); foreach (@{$self -> { main_model } -> { _hidden }}) { $self -> { main_model } -> set( $self -> { main_model } -> append(), COLUMN_NAME, $_ -> [0], COLUMN_HOME, $_ -> [1] || "", COLUMN_MOBILE, $_ -> [2] || ""); } $self -> { main_model } -> { _hidden } = []; my @victims = (); if ($filter_name ne "" || $filter_home ne "" || $filter_mobile ne "") { $self -> { main_model } -> foreach(sub { my ($model, $path, $iterator) = @_; my ($value_name, $value_home, $value_mobile) = ($model -> get($iterator, COLUMN_NAME), $model -> get($iterator, COLUMN_HOME), $model -> get($iterator, COLUMN_MOBILE)); unless (index($value_name, $filter_name) > -1 && index($value_home, $filter_home) > -1 && index($value_mobile, $filter_mobile) > -1) { push(@{$self -> { main_model } -> { _hidden }}, [$value_name, $value_home, $value_mobile]); push(@victims, $iterator -> copy()); } return 0; }); foreach (@victims) { $self -> { main_model } -> remove($_); } } return 1; } sub file_dialog { my ($self, $action) = @_; my $file; my $file_selector = Gtk2::FileSelection -> new( ($action eq "save") ? "Select a file for saving" : "Select the file to open"); $file_selector -> signal_connect(delete_event => sub { Gtk2 -> main_quit(); return 1; }); $file_selector -> ok_button -> signal_connect(clicked => sub { $file = $file_selector -> get_filename(); Gtk2 -> main_quit(); return 1; }); $file_selector -> cancel_button -> signal_connect(clicked => sub { Gtk2 -> main_quit(); return 1; }); $file_selector -> set_modal(1); $file_selector -> set_transient_for($self -> { main_window }); $file_selector -> show_all(); Gtk2 -> main(); $file_selector -> destroy(); return $file; } sub file_open { my ($self, $file) = @_; unless (defined($file)) { $file = $self -> file_dialog("open"); } if (defined($file)) { $self -> changed(0); $self -> file($file); $self -> { main_window } -> set_title("Telephone Book - $file"); if (defined($self -> { main_model } -> get_iter_first())) { if ($self -> question("Do you want to clear the list before inserting the new entries?")) { $self -> { main_model } -> clear(); } else { $self -> changed(1); } } open(FILE, $file) or return $self -> error("Error opening $file: $!"); while () { chomp(); my ($name, $home, $mobile) = split(/\t/); if (defined($name) && $name ne "") { $self -> { main_model } -> set( $self -> { main_model } -> append(), COLUMN_NAME, $name, COLUMN_HOME, $home || "", COLUMN_MOBILE, $mobile || ""); } } close(FILE); } return 1; } sub file_save { my ($self, $file) = @_; unless (defined($file)) { $file = $self -> file(); } if (defined($file)) { $self -> { filter_entry_name } -> set_text(""); $self -> { filter_entry_home } -> set_text(""); $self -> { filter_entry_mobile } -> set_text(""); $self -> filter_rows(); open(FILE, ">" . $file) or return $self -> error("Error opening $file: $!"); $self -> { main_model } -> foreach(sub { my ($model, $path, $iterator) = @_; print FILE join("\t", ($model -> get($iterator, COLUMN_NAME), $model -> get($iterator, COLUMN_HOME), $model -> get($iterator, COLUMN_MOBILE))), "\n"; return 0; }); close(FILE); $self -> changed(0); $self -> file($file); $self -> { main_window } -> set_title("Telephone Book - $file"); } else { $self -> error("No file specified."); } return 1; } sub file_save_as { my ($self) = @_; my $file = $self -> file_dialog("save"); $self -> file_save($file) if (defined($file)); return 1; } sub question { my ($self, $label) = @_; my $dialog = Gtk2::MessageDialog -> new($self -> { main_window }, [qw(modal destroy_with_parent)], "question", "yes_no", $label); my $response = $dialog -> run(); $dialog -> destroy(); return $response eq "yes" ? 1 : 0; } sub quit { my ($self) = @_; if ($self -> changed() == 1) { if ($self -> question("Really quit and abandon all changes?")) { Gtk2 -> main_quit(); } } else { Gtk2 -> main_quit(); } return 1; } sub run { my ($self) = @_; Gtk2 -> main(); return 1; } sub valid { my ($self, @values) = @_; foreach (@values) { return 0 if (m/\t/); } return 1; } ############################################################################### package main; my $ui = ui -> new(); $ui -> initialize(); if (-f $ENV{ HOME } . "/.telbook") { $ui -> file_open($ENV{ HOME } . "/.telbook"); } $ui -> run();