Logo Search packages:      
Sourcecode: efax-gtk version File versions

addressbook.cpp

/* Copyright (C) 2001 2002 Chris Vine

This program is distributed under the General Public Licence, version 2.
For particulars of this and relevant disclaimers see the file
COPYING distributed with the source files.

*/


#include <string>
#include <fstream>
#include <cstdlib>

#include <gtkmm/main.h>
#include <gdkmm/pixbuf.h>
#include <gtkmm/image.h>
#include <gdk/gdkkeysyms.h> // the key codes are here
#include <gtkmm/stock.h>
#include <gtkmm/enums.h>
#include <glibmm/convert.h>

#include "addressbook.h"
#include "dialogs.h"
#include "addressbook_icons.h"

#ifdef ENABLE_NLS
#include <libintl.h>
#endif

#define ADDRESS_FILE ".efax-gtk_addressbook"
#define ADDRESS_DIVIDER 3

int AddressBook::is_address_list = 0;

AddressBook::AddressBook(const int size, Gtk::Window& window):
                             Gtk::Window(Gtk::WINDOW_TOPLEVEL), standard_size(size),
                       parent(window), in_exec_loop(false),
                       table(2, 1, false), list_table(5, 2, false),
                       ok_button(Gtk::Stock::OK), cancel_button(Gtk::Stock::CANCEL),
                       button_box(Gtk::BUTTONBOX_END, standard_size/2) {

  // notify the existence of this object in case I later decide to use this dialog as modeless
  // by omitting the set_modal() call below
  is_address_list++;

  address_list_scroll_window.set_policy(Gtk::POLICY_ALWAYS, Gtk::POLICY_ALWAYS);
  address_list_scroll_window.add(tree_view);
  address_list_scroll_window.set_shadow_type(Gtk::SHADOW_IN);

  // create the tree model:
  list_store_r = Gtk::ListStore::create(model_columns);
  // connect it to the tree view
  tree_view.set_model(list_store_r);
  // add the columns of the tree model to tree view
  tree_view.append_column(gettext("Name"), model_columns.name);
  tree_view.append_column(gettext("Number"), model_columns.number);
  // single line selection
  tree_view.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
  // make drag and drop work
  tree_view.set_reorderable(true);
  // populate the address list
  read_list();

  list_store_r->signal_row_deleted().connect(sigc::mem_fun(*this, &AddressBook::drag_n_drop_slot), true);

  // bring up the icon size registered in MainWindow::MainWindow()
  Gtk::IconSize efax_gtk_button_size = Gtk::IconSize::from_name("EFAX_GTK_BUTTON_SIZE");

  Gtk::Image* image_p;
  image_p = manage(new Gtk::Image(Gdk::Pixbuf::create_from_xpm_data(add_xpm)));
  add_button.add(*image_p);

  image_p = manage(new Gtk::Image(Gtk::Stock::DELETE, efax_gtk_button_size));
  delete_button.add(*image_p);

  image_p = manage(new Gtk::Image(Gtk::Stock::GO_UP, efax_gtk_button_size));
  up_button.add(*image_p);

  image_p = manage(new Gtk::Image(Gtk::Stock::GO_DOWN, efax_gtk_button_size));
  down_button.add(*image_p);

  Gtk::Label* dummy_p = manage(new Gtk::Label);

  list_table.attach(add_button, 0, 1, 0, 1,
                Gtk::SHRINK, Gtk::SHRINK, standard_size/2, 0);
  list_table.attach(delete_button, 0, 1, 1, 2,
                Gtk::SHRINK, Gtk::SHRINK, standard_size/2, 0);
  list_table.attach(up_button, 0, 1, 2, 3,
                Gtk::SHRINK, Gtk::SHRINK, standard_size/2, 0);
  list_table.attach(down_button, 0, 1, 3, 4,
                Gtk::SHRINK, Gtk::SHRINK, standard_size/2, 0);
  list_table.attach(*dummy_p, 0, 1, 4, 5,
                Gtk::SHRINK, Gtk::EXPAND, 0, 0);
  list_table.attach(address_list_scroll_window, 1, 2, 0, 5, Gtk::EXPAND | Gtk::FILL,
         Gtk::EXPAND | Gtk::FILL, 0, 0);

  tooltips.set_tip(add_button, gettext("Add new address"));
  tooltips.set_tip(delete_button, gettext("Delete address"));
  tooltips.set_tip(up_button, gettext("Move address up"));
  tooltips.set_tip(down_button, gettext("Move address down"));

  button_box.add(cancel_button);
  button_box.add(ok_button);

  table.attach(list_table, 0, 1, 0, 1, Gtk::EXPAND | Gtk::FILL,
         Gtk::EXPAND | Gtk::FILL, standard_size/3, standard_size/3);
  table.attach(button_box, 0, 1, 1, 2, Gtk::FILL | Gtk::EXPAND,
       Gtk::SHRINK, standard_size/3, standard_size/3);

  add_button.signal_clicked().connect(sigc::mem_fun(*this, &AddressBook::add_address_prompt));
  delete_button.signal_clicked().connect(sigc::mem_fun(*this, &AddressBook::delete_address_prompt));
  ok_button.signal_clicked().connect(sigc::mem_fun(*this, &AddressBook::ok_slot));
  cancel_button.signal_clicked().connect(sigc::mem_fun(*this, &AddressBook::finish));
  up_button.signal_clicked().connect(sigc::mem_fun(*this, &AddressBook::move_up));
  down_button.signal_clicked().connect(sigc::mem_fun(*this, &AddressBook::move_down));
  
  ok_button.set_flags(Gtk::CAN_DEFAULT);
  cancel_button.set_flags(Gtk::CAN_DEFAULT);
  add_button.unset_flags(Gtk::CAN_FOCUS);
  delete_button.unset_flags(Gtk::CAN_FOCUS);
  up_button.unset_flags(Gtk::CAN_FOCUS);
  down_button.unset_flags(Gtk::CAN_FOCUS);

  table.set_border_width(standard_size/3);

  set_title(gettext("efax-gtk: Address book"));
  add(table);

  set_transient_for(parent);
  set_type_hint(Gdk::WINDOW_TYPE_HINT_DIALOG);
  parent.set_sensitive(false);
  set_modal(true);
  set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
  cancel_button.grab_focus();

  set_icon(prog_config.window_icon_r);

  set_default_size(standard_size * 15, standard_size * 8);

  show_all();
}

AddressBook::~AddressBook(void) {
  // notify the destruction of this object
  is_address_list--;
}

Glib::ustring AddressBook::exec(void) {
  in_exec_loop = true;
  Gtk::Main::run();
  return result;
}

void AddressBook::ok_slot(void) {
  result = get_number();
  if (!result.empty()) {
    accepted(result);
    finish();
  }
  else beep();
}

void AddressBook::finish(void) {
  parent.set_sensitive(true);
  hide_all();
  if (in_exec_loop) Gtk::Main::quit();
  // if we have not called exec(), then this dialog is self-owning and it is safe to call `delete this'
  else delete this;
}

bool AddressBook::on_delete_event(GdkEventAny*) {
  finish();
  return true; // returning true prevents destroy sig being emitted
}

Glib::ustring AddressBook::get_number(void) {

  Glib::ustring number;
  Gtk::TreeModel::iterator row_iter = tree_view.get_selection()->get_selected();
  if (row_iter) {
    number = (*row_iter)[model_columns.number];
  }
  return number;
}

void AddressBook::add_address_prompt(void) {

  AddressDialog* dialog_p = new AddressDialog(standard_size, *this);
  dialog_p->accepted.connect(sigc::mem_fun(*this, &AddressBook::add_address));
  // there is no memory leak -- AddressDailog will delete its own memory
  // when it is closed
}

void AddressBook::add_address(const std::vector<Glib::ustring>& address) {

  Gtk::TreeModel::Row row = *(list_store_r->append());
  row[model_columns.name] = address[0];
  row[model_columns.number] = address[1];
  save_list();
}

void AddressBook::delete_address_prompt(void) {

  Gtk::TreeModel::iterator row_iter = tree_view.get_selection()->get_selected();
  if (row_iter) {
    PromptDialog* dialog_p = new PromptDialog(gettext("Delete selected address?"),
                                    gettext("efax-gtk: Delete address"), standard_size, *this);
    dialog_p->accepted.connect(sigc::bind(sigc::mem_fun(*this, &AddressBook::delete_address), row_iter));
    // there is no memory leak -- the memory will be deleted when PromptDialog closes
  }
  else beep();
}

void AddressBook::delete_address(Gtk::TreeModel::iterator row_iter) {

  // delete the address by removing it from the list store
  list_store_r->erase(row_iter);
  save_list();
}

void AddressBook::read_list(void) {

  std::string filename(prog_config.working_dir);
  filename += "/" ADDRESS_FILE;

#ifdef HAVE_IOS_NOCREATE
  std::ifstream filein(filename.c_str(), std::ios::in | std::ios::nocreate);
#else
  // we must have Std C++ so we probably don't need a ios::nocreate
  // flag on a read open to ensure uniqueness
  std::ifstream filein(filename.c_str(), std::ios::in);
#endif

  if (filein) {
    
    list_store_r->clear();

    std::string line;
    while (std::getline(filein, line)) {
      if (!line.empty()) {
      std::string::size_type pos = line.find(ADDRESS_DIVIDER, 0); // pos now is set to end of name value
      // get a list store row to insert the number and  name
      Gtk::TreeModel::Row row = *(list_store_r->append());
      try {
        row[model_columns.name] = Glib::locale_to_utf8(line.substr(0, pos));
      }
      catch (Glib::ConvertError&) {
        write_error("UTF-8 conversion error in AddressBook::read_list()\n");
      }
      pos++; // pos now is set to the beginning of the number value
      try {
        row[model_columns.number] = Glib::locale_to_utf8(line.substr(pos, line.size() - pos));
      }
      catch (Glib::ConvertError&) {
        write_error("UTF-8 conversion error in AddressBook::read_list()\n");
      }
      }
    }
  }
}

void AddressBook::save_list(void) {

  std::string filename(prog_config.working_dir);
  filename += "/" ADDRESS_FILE;
  std::ofstream fileout(filename.c_str(), std::ios::out);

  if (fileout) {
    
    std::string line;
    Gtk::TreeModel::Children children = list_store_r->children();
    Gtk::TreeModel::Children::iterator row_iter;

    for(row_iter = children.begin(); row_iter != children.end(); ++row_iter) {
      // the try()/catch() blocks here are ultra cautious - something must be
      // seriously wrong if model_columns.name and model_columns.number are not
      // in valid UTF-8 format, since they are tested where necessary at input
      try {
      line = Glib::locale_from_utf8((*row_iter)[model_columns.name]);
      line += ADDRESS_DIVIDER;
      line += Glib::locale_from_utf8((*row_iter)[model_columns.number]);
      line += '\n';
      fileout << line;
      }
      catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in AddressBook::save_list()\n");
      }
    }
  }
}

void AddressBook::drag_n_drop_slot(const Gtk::TreeModel::Path&) {
  save_list();
}

void AddressBook::move_up(void) {

  Gtk::TreeModel::iterator selected_row_iter = tree_view.get_selection()->get_selected();
  if (selected_row_iter && selected_row_iter != list_store_r->children().begin()) {

    Gtk::TreeModel::Path tree_path(selected_row_iter);
    tree_path.prev(); // tree_path now refers to the row of the list store
                      // preceding the selected one
    Gtk::TreeModel::iterator prev_row_iter = list_store_r->get_iter(tree_path);

    Glib::ustring selected_name = (*selected_row_iter)[model_columns.name];
    Glib::ustring selected_number = (*selected_row_iter)[model_columns.number];
    Glib::ustring prev_name = (*prev_row_iter)[model_columns.name];
    Glib::ustring prev_number = (*prev_row_iter)[model_columns.number];
      
    (*selected_row_iter)[model_columns.name] = prev_name;
    (*selected_row_iter)[model_columns.number] = prev_number;
    (*prev_row_iter)[model_columns.name] = selected_name;
    (*prev_row_iter)[model_columns.number] = selected_number;
    tree_view.get_selection()->select(prev_row_iter);

    save_list();
  }
  else beep();
}

void AddressBook::move_down(void) {

  Gtk::TreeModel::iterator selected_iter = tree_view.get_selection()->get_selected();
  if (selected_iter) {
    Gtk::TreeModel::iterator succeding_iter = selected_iter;
    ++succeding_iter;
   
    if (succeding_iter != list_store_r->children().end()) {

      Glib::ustring selected_name = (*selected_iter)[model_columns.name];
      Glib::ustring selected_number = (*selected_iter)[model_columns.number];
      Glib::ustring succeding_name = (*succeding_iter)[model_columns.name];
      Glib::ustring succeding_number = (*succeding_iter)[model_columns.number];
      
      (*selected_iter)[model_columns.name] = succeding_name;
      (*selected_iter)[model_columns.number] = succeding_number;
      (*succeding_iter)[model_columns.name] = selected_name;
      (*succeding_iter)[model_columns.number] = selected_number;
      tree_view.get_selection()->select(succeding_iter);
      save_list();
    }
    else beep();
  }
  else beep();
}

AddressDialog::AddressDialog(const int standard_size, Gtk::Window& window):
                             Gtk::Window(Gtk::WINDOW_TOPLEVEL), in_exec_loop(false),
                       ok_button(Gtk::Stock::OK), cancel_button(Gtk::Stock::CANCEL),
                       button_box(Gtk::BUTTONBOX_END, standard_size/2),
                       name_label(gettext("Name:")), number_label(gettext("Number:")),
                       table(3, 1, false), parent(window) {

  name_label.set_size_request(standard_size * 2, standard_size);
  number_label.set_size_request(standard_size * 2, standard_size);

  name_entry.set_size_request(standard_size * 8, standard_size);
  number_entry.set_size_request(standard_size * 8, standard_size);

  name_box.pack_start(name_label, false, false, standard_size/2);
  name_box.pack_start(name_entry, true, true, 0);

  number_box.pack_start(number_label, false, false, standard_size/2);
  number_box.pack_start(number_entry, true, true, 0);

  button_box.add(cancel_button);
  button_box.add(ok_button);

  table.attach(name_box, 0, 1, 0, 1, Gtk::FILL | Gtk::EXPAND,
       Gtk::FILL | Gtk::EXPAND, standard_size/2, standard_size/4);
  table.attach(number_box, 0, 1, 1, 2, Gtk::FILL | Gtk::EXPAND,
       Gtk::FILL | Gtk::EXPAND, standard_size/2, standard_size/4);
  table.attach(button_box, 0, 1, 2, 3, Gtk::FILL | Gtk::EXPAND,
       Gtk::SHRINK, standard_size/2, standard_size/4);

  ok_button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &AddressDialog::selected), true));
  cancel_button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &AddressDialog::selected), false));

  add(table);
  
  set_title(gettext("efax-gtk: Add address"));
  set_transient_for(parent);
  set_type_hint(Gdk::WINDOW_TYPE_HINT_DIALOG);
  parent.set_sensitive(false);
  set_modal(true);

  set_border_width(standard_size/2);

  name_entry.grab_focus();

  set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
  set_resizable(false);

  set_icon(prog_config.window_icon_r);

  show_all();
}

void AddressDialog::exec(void) {
  in_exec_loop = true;
  Gtk::Main::run();
}

void AddressDialog::selected(bool accept) {
  // check pre-conditions to closing the dialog
  if (accept && (!name_entry.get_text_length() || !number_entry.get_text_length())) beep();
  
  else {   
    parent.set_sensitive(true); // do this before we emit accepted()
    hide_all();
    if (accept) {
      std::vector<Glib::ustring> out_val;
      out_val.push_back(name_entry.get_text());
      out_val.push_back(number_entry.get_text());
      accepted(out_val);
    }
    if (in_exec_loop) Gtk::Main::quit();
    // if we have not called exec(), then this dialog is self-owning and it is safe to call `delete this'
    else delete this;
  }
}

bool AddressDialog::on_delete_event(GdkEventAny*) {
  selected(false);
  return true; // returning true prevents destroy sig being emitted
}

bool AddressDialog::on_key_press_event(GdkEventKey* event_p) {
  if (event_p->keyval == GDK_Escape) selected(false);
  else if (event_p->keyval == GDK_Return && !cancel_button.has_focus()) selected(true);
  else Gtk::Window::on_key_press_event(event_p);
  return true; // processing ends here
}

Generated by  Doxygen 1.6.0   Back to index