#!/usr/bin/perl -w

###############################################################################
#                                                                             #
# Telbook - A simple telephone book                                           #
# Copyright (C) 2003 Torsten Schnfeld                                        #
#                                                                             #
# 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 (<FILE>) {
      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();
