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

fax_list_manager.cpp

/* Copyright (C) 2001 to 2005 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 <vector>

#include <cstring>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>

#include <gtkmm/treesortable.h>
#include <gdkmm/pixmap.h>
#include <gdkmm/bitmap.h>
#include <gdkmm/colormap.h>

#include "fax_list_manager.h"
#include "fax_list_manager_icons.h"

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

#define PATH_DIVIDER '@'
#define DRAG_KEY_STRING "xXefax-gtkXx"


std::pair<bool, Glib::ustring> FolderNameValidator::validate(const Glib::ustring& folder_name) {

  std::pair<bool, Glib::ustring> return_val;

  if (folder_name.find(PATH_DIVIDER) != Glib::ustring::npos) {
    return_val.first = false;
    return_val.second = gettext("Folder name cannot contain character: ");
    return_val.second += PATH_DIVIDER;
  }    

  else {
    std::pair<std::set<std::string>::iterator, bool> result = folder_set.insert(folder_name);
    // we only want a return value - erase the element if the insert succeeded
    if ((return_val.first = result.second)) folder_set.erase(folder_name);
    else {
      return_val.second = gettext("The following folder already exists: ");
      return_val.second += folder_name;
    }
  }

  return return_val;
}

FaxListManager::FaxListManager(FaxListEnum::Mode mode_):
                                                mode(mode_),
                                                folder_drag_source_enabled(false),
                                                fax_drag_source_enabled(false),
                                                folder_tree_view_column(gettext("Folder")) {

  folder_icon_r = Gdk::Pixbuf::create_from_xpm_data(folder_xpm);
  folder_icon_r = folder_icon_r->scale_simple(14, 14, Gdk::INTERP_BILINEAR);
  trash_icon_r = Gdk::Pixbuf::create_from_xpm_data(trash_xpm);
  trash_icon_r = trash_icon_r->scale_simple(14, 16, Gdk::INTERP_BILINEAR);

  // create the tree model for the folders:
  folder_tree_store_r = Gtk::TreeStore::create(folder_model_columns);
  // connect it to the tree view
  folder_tree_view.set_model(folder_tree_store_r);
  // add first and second column of the tree model to tree view column
  // (the third column of tree model is data not for display)
  folder_tree_view_column.pack_start(folder_model_columns.icon, false);
  folder_tree_view_column.pack_start(folder_model_columns.name);
  // add the folder tree view column to the folder tree view
  folder_tree_view.append_column(folder_tree_view_column);

  // single line selection
  folder_tree_view.get_selection()->set_mode(Gtk::SELECTION_SINGLE);

  // create the tree model for the faxes:
  fax_list_store_r = Gtk::ListStore::create(fax_model_columns);
  // connect it to the tree view
  fax_tree_view.set_model(fax_list_store_r);
  // add columns of the tree model to tree view
  fax_tree_view.append_column(gettext("Fax"), fax_model_columns.name);
  fax_tree_view.append_column(gettext("Description"), fax_model_columns.fax_description);
  // multiple line selection
  fax_tree_view.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);

  // populate the fax list
  populate_fax_list();
  // populate_fax_list() will pre-sort for efficiency reasons but make the fax
  // list sortable here for any changes introduced from drag and drop to be sorted
#if GTKMM_VERSION >= 24
  fax_list_store_r->set_sort_column(fax_model_columns.name, Gtk::SORT_ASCENDING);
#else
  fax_list_store_r->set_sort_column_id(fax_model_columns.name, Gtk::SORT_ASCENDING);
#endif

  // now provide drag and drop from the fax tree view to the folder tree view
  // first load target_list
  target_list.push_back( Gtk::TargetEntry("STRING"));
  target_list.push_back( Gtk::TargetEntry("text/plain"));

  // connect slots to ensure that a drag only starts with a valid row to drag
  folder_tree_view.signal_motion_notify_event().connect_notify(sigc::mem_fun(*this, &FaxListManager::folder_tree_view_motion_notify_slot));
  fax_tree_view.signal_motion_notify_event().connect_notify(sigc::mem_fun(*this, &FaxListManager::fax_tree_view_motion_notify_slot));

  // connect slots to handle the drag source in the fax tree view
  fax_tree_view.signal_drag_data_get().connect(sigc::mem_fun(*this, &FaxListManager::drag_data_get_slot));
  fax_tree_view.signal_drag_begin().connect(sigc::mem_fun(*this, &FaxListManager::fax_tree_view_drag_begin_slot));

  // connect slots to handle the drag source in the folder tree view
  folder_tree_view.signal_drag_data_get().connect(sigc::mem_fun(*this, &FaxListManager::drag_data_get_slot));
  folder_tree_view.signal_drag_begin().connect(sigc::mem_fun(*this, &FaxListManager::folder_tree_view_drag_begin_slot));

  // connect slots to handle the drag destination in the folder tree view
  folder_tree_view.signal_drag_motion().connect(sigc::mem_fun(*this, &FaxListManager::drag_motion_slot));
  folder_tree_view.drag_dest_set(target_list, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_MOVE);
  folder_tree_view.signal_drag_data_received().connect(sigc::mem_fun(*this, &FaxListManager::drag_data_received_slot));

  // handle selection of a row in the tree views
  fax_tree_view.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FaxListManager::fax_selected_slot));
  folder_tree_view.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FaxListManager::folder_selected_slot));
  folder_tree_view.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FaxListManager::display_faxes_slot));
}

void FaxListManager::folder_tree_view_drag_begin_slot(const Glib::RefPtr<Gdk::DragContext>& context) {

  drag_is_fax = false;
  folder_drag_source_row_iter = folder_tree_view.get_selection()->get_selected();

  if (folder_drag_source_row_iter) {

    int x_off, y_off, cell_width, cell_height;
    Gdk::Rectangle area;
    folder_tree_view.get_column(0)->cell_get_size(area, x_off, y_off, cell_width, cell_height);

    if (cell_height < 0) cell_height = 0;

    Glib::RefPtr<Gdk::Pixmap> pixmap_r = folder_tree_view.create_row_drag_icon(Gtk::TreeModel::Path(folder_drag_source_row_iter));
    Glib::RefPtr<Gdk::Colormap> colormap_r = Gtk::Widget::get_default_colormap();
    Glib::RefPtr<Gdk::Bitmap> bitmap_r;

    context->set_icon(colormap_r, pixmap_r, bitmap_r, 5, cell_height/2);
  }
}

void FaxListManager::fax_tree_view_drag_begin_slot(const Glib::RefPtr<Gdk::DragContext>& context) {

  drag_is_fax = true;
  SelectedRowsHandle rows_handle(fax_tree_view);
  rows_handle.get_ref_list(fax_list_store_r, fax_drag_source_row_refs);

  // if we are dragging only one row, provide a row icon, but otherwise
  // use the generic GTK+ drag icon
  if (rows_handle.size() == 1 && fax_drag_source_row_refs.front().is_valid()) {

    int x_off, y_off, cell_width, cell_height;
    Gdk::Rectangle area;
    fax_tree_view.get_column(0)->cell_get_size(area, x_off, y_off, cell_width, cell_height);

    int x_pos, y_pos;
    fax_tree_view.get_pointer(x_pos, y_pos);

    if (cell_height < 0) cell_height = 0;
    if (x_pos < 0) x_pos = 0;

    Glib::RefPtr<Gdk::Pixmap> pixmap_r = fax_tree_view.create_row_drag_icon(rows_handle.front());
    Glib::RefPtr<Gdk::Colormap> colormap_r = Gtk::Widget::get_default_colormap();
    Glib::RefPtr<Gdk::Bitmap> bitmap_r;

    context->set_icon(colormap_r, pixmap_r, bitmap_r, x_pos, cell_height/2);
  }
}

bool FaxListManager::drag_motion_slot(const Glib::RefPtr<Gdk::DragContext>& context,
                             int x, int y, guint time) {

  bool return_val = false;
 
  Gtk::TreeModel::Path path;
  Gtk::TreeViewDropPosition drop_pos;
  folder_tree_view.get_dest_row_at_pos(x, y, path, drop_pos);

  if (path.gobj()) {
    if (drag_is_fax) folder_tree_view.set_drag_dest_row(path, Gtk::TREE_VIEW_DROP_INTO_OR_AFTER);
    else folder_tree_view.set_drag_dest_row(path, drop_pos);
    return_val = true;
  }
  return return_val;
}

void FaxListManager::drag_data_get_slot(const Glib::RefPtr<Gdk::DragContext>& context,
#if GTKMM_VERSION >= 24
                              Gtk::SelectionData& selection_data,
#else
                              GtkSelectionData* selection_data_p,
#endif
                              guint info, guint time) {

  // all we do in this method is to set up an identification string (DRAG_KEY_STRING)
  // so that drags not originating in this program can be ignored.  The data which
  // is dragged is placed in FaxListManager::folder_drag_source_row_iter in method
  // FaxListManager::folder_tree_view_drag_begin_slot() (for a folder drag) and in
  // FaxListManager::fax_drag_source_row_refs in method
  // FaxListManager::fax_tree_view_drag_begin_slot() (for a fax drag), rather than in
  // this method.  We can therefore use this method for drags from both the folder tree
  // view and the fax tree view.  The method FaxListManager::drag_data_received_slot()
  // tests for this identification string when a drag is received by the folder tree
  // view (the fax tree view does not receive drags, it only sources them, whereas the
  // folder tree view both receives and sources drags).

  const std::string message(DRAG_KEY_STRING);

#if GTKMM_VERSION >= 24
  //selection_data.set(selection_data.get_target(), 8, (const guchar*) message.c_str() , message.size());
  selection_data.set(selection_data.get_target(), message);
#else
  gtk_selection_data_set(selection_data_p, selection_data_p->target, 8, (const guchar*) message.c_str(), message.size() + 1);
#endif
}

void FaxListManager::drag_data_received_slot(const Glib::RefPtr<Gdk::DragContext>& context,
                                  int x, int y,
#if GTKMM_VERSION >= 24
                                  const Gtk::SelectionData& selection_data,
#else
                                  GtkSelectionData* selection_data_p,
#endif
                                  guint info, guint time) {

  bool success = false;
  std::string message;
#if GTKMM_VERSION >= 24
  if (selection_data.get_length() >= 0 && selection_data.get_format() == 8) {
    message = selection_data.get_data_as_string();
  }
#else
  if(selection_data_p->length >= 0 && selection_data_p->format == 8) {
    message = (const char*)selection_data_p->data;
  }
#endif

  if (message.find(DRAG_KEY_STRING) != std::string::npos) {
    Gtk::TreeModel::Path path;
    Gtk::TreeViewDropPosition drop_pos;
    folder_tree_view.get_dest_row_at_pos(x, y, path, drop_pos);
    if (path.gobj()) {
      Gtk::TreeIter iter = folder_tree_store_r->get_iter(path);
      if (iter) {
      if (drag_is_fax) move_fax(iter);
      else move_folder(iter, drop_pos);
      success = true;
      }
      else write_error("Invalid iterator in FaxListDialog::drag_data_received_slot()\n");
    }
  }
  context->drag_finish(success, false, time);
}

void FaxListManager::folder_tree_view_motion_notify_slot(GdkEventMotion*) {
  int x, y;
  folder_tree_view.get_pointer(x, y);
  if (x >= 0 && y >= 0){
    Gtk::TreeModel::Path path;
    Gtk::TreeViewDropPosition drop_pos;
    folder_tree_view.get_dest_row_at_pos(x, y, path, drop_pos);

    if (path.gobj() && !folder_drag_source_enabled) {
      folder_tree_view.drag_source_set(target_list, Gdk::MODIFIER_MASK, Gdk::ACTION_MOVE);
      folder_drag_source_enabled = true;
    }
    else if (!path.gobj() && folder_drag_source_enabled) {
      folder_tree_view.drag_source_unset();
      folder_drag_source_enabled = false;
    }
  }
}

void FaxListManager::fax_tree_view_motion_notify_slot(GdkEventMotion*) {
  int x, y;
  fax_tree_view.get_pointer(x, y);
  if (x >= 0 && y >= 0){
    Gtk::TreeModel::Path path;
    Gtk::TreeViewDropPosition drop_pos;
    fax_tree_view.get_dest_row_at_pos(x, y, path, drop_pos);

    if (path.gobj() && !fax_drag_source_enabled) {
      fax_tree_view.drag_source_set(target_list, Gdk::MODIFIER_MASK, Gdk::ACTION_MOVE);
      fax_drag_source_enabled = true;
    }
    else if (!path.gobj() && fax_drag_source_enabled) {
      fax_tree_view.drag_source_unset();
      fax_drag_source_enabled = false;
    }
  }
}

void FaxListManager::populate_fax_list(void) {

  std::string dir(prog_config.working_dir);
  if (mode == FaxListEnum::received) dir += "/faxin";
  else dir += "/faxsent";

  chdir(dir.c_str());

  DIR* dir_p;
  if ((dir_p = opendir(dir.c_str())) == 0) {
    std::string msg("Can't open directory ");
    msg += dir;
    msg += '\n';
    write_error(msg.c_str());
  }
  else {

    struct dirent* direntry;
    struct stat statinfo;
    std::list<std::string> dir_list;

    // first populate dir_list with the directory names in $HOME/faxin
    while ((direntry = readdir(dir_p)) != 0) {
      stat(direntry->d_name, &statinfo);
      if (S_ISDIR(statinfo.st_mode)
        // keep the check for the "oldfax" sub-directory even though it is not used
        // in version 2.2.13 onwards (users may still have it lying around)
        && std::strcmp(direntry->d_name, "oldfax")
        && std::strcmp(direntry->d_name, ".")
        && std::strcmp(direntry->d_name, "..")
        && (mode == FaxListEnum::sent
            || std::strcmp(direntry->d_name, prog_config.receive_dirname))) {
      dir_list.push_back(direntry->d_name);
      }
    }
    while (closedir(dir_p) == -1 && errno == EINTR);

    // now insert the directory names in fax_list, with description (if any)
    // first clear the list store -- we need to clear it here before
    // testing for an empty dir_list, or on deleting the last fax in the list,
    // the clear won't take out the former last entry

    folder_tree_store_r->clear();
    fax_list_store_r->clear();
    folder_to_fax_map.clear();
    fax_to_folder_map.clear();
    folder_name_validator.clear();

    // we always have an 'Inbox' and 'Sent box' row items to which children are attached
    Gtk::TreeModel::Row base_folder_row = *folder_tree_store_r->append();

    std::string base_path;
    base_path = PATH_DIVIDER;
    if (mode == FaxListEnum::received) {
      base_folder_row[folder_model_columns.name] = gettext("Inbox");
      base_path +=  gettext("Inbox");
      folder_name_validator.insert_folder_name(gettext("Inbox"));
    }
    else {
      base_folder_row[folder_model_columns.name] = gettext("Sent box");
      base_path += gettext("Sent box");
      folder_name_validator.insert_folder_name(gettext("Sent box"));
    }
    base_folder_row[folder_model_columns.icon] = folder_icon_r;
    base_folder_row[folder_model_columns.root_only] = true;

    // we also always have a 'Trash' folder
    Gtk::TreeModel::Row trash_folder_row = *folder_tree_store_r->append();
    trash_folder_row[folder_model_columns.name] = gettext("Trash");
    std::string trash_path;
    trash_path = PATH_DIVIDER;
    trash_path += gettext("Trash");
    folder_name_validator.insert_folder_name(gettext("Trash"));
    trash_folder_row[folder_model_columns.icon] = trash_icon_r;
    trash_folder_row[folder_model_columns.root_only] = true;

    FolderToRowMap folder_to_row_map;
    TreeModelRowReference folder_row_ref(folder_tree_store_r, Gtk::TreePath(base_folder_row));
    folder_to_row_map.insert(FolderToRowMap::value_type(base_path, folder_row_ref));
    trash_row_ref = TreeModelRowReference(folder_tree_store_r, Gtk::TreePath(trash_folder_row));
    folder_to_row_map.insert(FolderToRowMap::value_type(trash_path, trash_row_ref));

    // get the paths (folders) stored in PathList
    std::string line;
    std::ifstream file;
    std::string filename(dir);
    filename += "/PathList";

#ifdef HAVE_IOS_NOCREATE
    file.open(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
    file.open(filename.c_str(), std::ios::in);
#endif
    if (file) {
      while (std::getline(file, line)) get_folders(folder_to_row_map, line);
    }

    // populate the tree store, and get the fax descriptions (if any)
    if (!dir_list.empty()) {
      // first sort them
      dir_list.sort();
      
      filename = "";
      line = "";
      file.close();
      file.clear();

      std::list<std::string>::const_iterator iter;

      for (iter = dir_list.begin(); iter!= dir_list.end(); ++iter) {

      // get the fax list Path for each fax
      filename = dir + '/';
      filename += *iter;
      filename += "/Path";

#ifdef HAVE_IOS_NOCREATE
      file.open(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
      file.open(filename.c_str(), std::ios::in);
#endif

      if (file) {
        while (std::getline(file, line) && line.empty());
      }

      if (!Glib::ustring(line).validate()) {   // not valid UTF-8!
        write_error("Invalid UTF-8 string in folder path name in FaxListManager::populate_fax_list()\n");
        line = "";
      }

      // sort the faxes into a multimap keyed by the folder and indicating
      // the faxes belong to that folder, and then enter the faxes into a
      // map keyed by the fax and indicating its folder path (this gives
      // faster look up of the fax to its folder by enabling a binary search
      // in both directions)
      if (line.size() == 1 && line[0] == PATH_DIVIDER) {
        folder_to_fax_map.insert(FolderToFaxMap::value_type(base_path, *iter));
        fax_to_folder_map.insert(FaxToFolderMap::value_type(*iter, base_path));
      }
      // include a call to get_folders() in case something has gone wrong
      // and the fax belongs to a folder not listed in the PathList file
      else if (!line.empty() && get_folders(folder_to_row_map, line)) {
        folder_to_fax_map.insert(FolderToFaxMap::value_type(line, *iter));
        fax_to_folder_map.insert(FaxToFolderMap::value_type(*iter, line));
      }
      else {
        folder_to_fax_map.insert(FolderToFaxMap::value_type(base_path, *iter));
        fax_to_folder_map.insert(FaxToFolderMap::value_type(*iter, base_path));
      }

      filename = "";
      line = "";
      file.close();
      file.clear();
      }
    }
    // write path in case this is the first time the program has started or
    // something else has changed
    write_path();

    // select the main folder
    Glib::ustring base_folder;
    if (mode == FaxListEnum::received) base_folder = gettext("Inbox");
    else base_folder = gettext("Sent box");
    bool found_it = false;
    Gtk::TreeModel::Children children = folder_tree_store_r->children();
    Gtk::TreeModel::iterator row_iter = children.begin();
    while (!found_it && row_iter != children.end()) {
      if ((*row_iter)[folder_model_columns.name] == base_folder) found_it = true;
      else ++row_iter;
    }
    if (found_it) {
      folder_tree_view.get_selection()->select(row_iter);
      display_faxes_slot();
    }
  }
  // reset current directory
  std::string temp(prog_config.working_dir + "/faxin");
  chdir(temp.c_str());
}

bool FaxListManager::get_folders(FolderToRowMap& folder_to_row_map, const std::string& line) {
  bool return_val;
  if (!line.empty())  return_val = true;
  else return_val = false;

  std::vector<std::string> child_rowlist;
  std::string remaining_path(line);

  if (!Glib::ustring(line).validate()) { // not valid UTF-8!
    write_error("Invalid UTF-8 string in folder path name in FaxListManager::get_folders()\n");
    remaining_path = "";
    return_val = false;
  }

  // keep going until we get to the root tree store or to a parent row in existence
  while (!remaining_path.empty() && folder_to_row_map.find(remaining_path) == folder_to_row_map.end()) {
    std::string::size_type pos = remaining_path.rfind(PATH_DIVIDER);
    if (pos == std::string::npos) {  // input error
      remaining_path = "";
      child_rowlist.clear();
      return_val = false;
    }
    else {
      child_rowlist.push_back(remaining_path.substr(pos + 1));
      folder_name_validator.insert_folder_name(remaining_path.substr(pos + 1));
      remaining_path.resize(pos);
    }
  }

  // now create the missing child level(s) stored in child_rowlist
  if (!child_rowlist.empty()) {

    std::vector<std::string>::reverse_iterator r_iter = child_rowlist.rbegin();
    TreeModelRowReference row_ref;
    std::string pathname(remaining_path);
    pathname += PATH_DIVIDER;
    pathname += *r_iter;

    if (remaining_path.empty()) { // we need to make a first level node
      Gtk::TreeModel::Row row = *folder_tree_store_r->append();
      // no need for a Glib conversion function here - we store folder
      // names in Path in UTF-8
      row[folder_model_columns.name] = *r_iter;
      row[folder_model_columns.root_only] = false;
      row[folder_model_columns.icon] = folder_icon_r;
      row_ref = TreeModelRowReference(folder_tree_store_r, Gtk::TreePath(row));
      folder_to_row_map.insert(FolderToRowMap::value_type(pathname, row_ref));
    }
    else {                        // build on the nearest ancestor already created
      row_ref = folder_to_row_map[remaining_path];
      Gtk::TreeModel::Row row = *folder_tree_store_r->append(folder_tree_store_r->get_iter(row_ref.get_path())->children());
      if (row) {
      // no need for a Glib conversion function here - we store folder
      // names in Path in UTF-8
      row[folder_model_columns.name] = *r_iter;
      row[folder_model_columns.root_only] = false;
      row[folder_model_columns.icon] = folder_icon_r;
      row_ref = TreeModelRowReference(folder_tree_store_r, Gtk::TreePath(row));
      folder_to_row_map.insert(FolderToRowMap::value_type(pathname, row_ref));
      }
      else {
      write_error("Mapping error in FaxListManager::get_folders()\n");
      return_val = false;
      child_rowlist.clear();
      r_iter = child_rowlist.rbegin();
      }
    }
    // now deal with any further children remaining to be created
    for (++r_iter; r_iter != child_rowlist.rend(); ++r_iter) {
      Gtk::TreeModel::Row row = *folder_tree_store_r->append(folder_tree_store_r->get_iter(row_ref.get_path())->children());
      if (row) {
      // no need for a Glib conversion function here - we store folder
      // names in Path in UTF-8
      row[folder_model_columns.name] = *r_iter;
      row[folder_model_columns.root_only] = false;
      row[folder_model_columns.icon] = folder_icon_r;
      pathname += PATH_DIVIDER;
      pathname += *r_iter;
      row_ref = TreeModelRowReference(folder_tree_store_r, Gtk::TreePath(row));
      folder_to_row_map.insert(FolderToRowMap::value_type(pathname, row_ref));
      }
      else {
      write_error("Mapping error in FaxListManager::get_folders()\n");
      return_val = false;
      child_rowlist.clear();
      r_iter = child_rowlist.rbegin();
      }
    }
  }
  return return_val;
}

void FaxListManager::move_fax(const Gtk::TreeModel::iterator& dest_folder_row_iter) {
  // check pre-conditions
  if (fax_drag_source_row_refs.empty()) return;

  // the source and destination folders  are the same for all the faxes to be moved
  Gtk::TreeModel::iterator row_iter = fax_list_store_r->get_iter(fax_drag_source_row_refs.front().get_path());
  std::string first_faxname = Glib::ustring((*row_iter)[fax_model_columns.name]);

  std::string source_pathname(fax_to_folder_map[first_faxname]);
  std::string dest_pathname(get_pathname_for_folder(dest_folder_row_iter));

  if (source_pathname == dest_pathname) return;

  RowRefList::iterator refs_iter;
  for (refs_iter = fax_drag_source_row_refs.begin();
       refs_iter != fax_drag_source_row_refs.end(); ++refs_iter) {

    row_iter = fax_list_store_r->get_iter(refs_iter->get_path());
    
    const std::string& faxname = Glib::ustring((*row_iter)[fax_model_columns.name]);

    if (fax_to_folder_map.find(faxname) != fax_to_folder_map.end()) {
      fax_to_folder_map[faxname] = dest_pathname;
    }
    else write_error("Database mapping error in FaxListManager::move_fax()\n");

    std::pair<FolderToFaxMap::iterator,
              FolderToFaxMap::iterator> result(folder_to_fax_map.equal_range(source_pathname));
  
    if (result.first == result.second) {
      write_error("Equal range: database mapping error in FaxListManager::move_fax()\n");
      return;
    }

    FolderToFaxMap::iterator iter = result.first;
    bool found_it = false;

    while (!found_it && iter != result.second) {
      if (iter->second == faxname) {
      found_it = true;
      folder_to_fax_map.erase(iter);
      folder_to_fax_map.insert(FolderToFaxMap::value_type(dest_pathname, faxname));
      fax_list_store_r->erase(row_iter);
      }
      else ++iter;
    }
    if (!found_it) {
      write_error("Fax to move not found: database mapping error in FaxListManager::move_fax()\n");
    }
  }
  fax_drag_source_row_refs.clear();
  write_path();
}

void FaxListManager::move_folder(Gtk::TreeModel::iterator dest_folder_row_iter,
                         Gtk::TreeViewDropPosition drop_pos) {
  // check pre-conditions
  if (!folder_drag_source_row_iter) return;

  bool dragging_trash_folder = false;
  if (Gtk::TreeModel::Path(folder_drag_source_row_iter) == trash_row_ref.get_path()) {
    dragging_trash_folder = true;
  }
  bool is_root_only = (*folder_drag_source_row_iter)[folder_model_columns.root_only];

  const std::string& foldername = Glib::ustring((*folder_drag_source_row_iter)[folder_model_columns.name]);

  std::string source_pathname(get_pathname_for_folder(folder_drag_source_row_iter));
  std::string drop_pathname(get_pathname_for_folder(dest_folder_row_iter));

  if (source_pathname != drop_pathname) {
    // get the new pathname for the folder and put it in new_pathname
    std::string new_pathname(drop_pathname);
    std::string::size_type pos;
    if (drop_pos == Gtk::TREE_VIEW_DROP_BEFORE
      || drop_pos == Gtk::TREE_VIEW_DROP_AFTER) {
      if ((pos = new_pathname.rfind(PATH_DIVIDER)) != std::string::npos) {
      new_pathname.resize(pos);
      }
      else {
      write_error("Folder with no pathname in FaxListManager::move_folder()\n");
      new_pathname = "";
      }
    }
    new_pathname += PATH_DIVIDER;
    new_pathname += foldername;

    if (is_valid_drop_path(source_pathname, new_pathname)
      && (!is_root_only
          || new_pathname.rfind(PATH_DIVIDER) == 0)) {
      Gtk::TreeModel::iterator new_row_iter;
      switch (drop_pos) {
      case Gtk::TREE_VIEW_DROP_BEFORE:
      new_row_iter = folder_tree_store_r->insert(dest_folder_row_iter);
      break;
      case Gtk::TREE_VIEW_DROP_AFTER:
      new_row_iter = folder_tree_store_r->insert(++dest_folder_row_iter);
      break;
      default:
      new_row_iter = folder_tree_store_r->prepend(dest_folder_row_iter->children());
      break;
      }

      (*new_row_iter)[folder_model_columns.name] = Glib::ustring(foldername);
      (*new_row_iter)[folder_model_columns.root_only] = is_root_only;
      // although the Trash folder is root only, it can still be dragged
      // within the root level (as can the Inbox/Sent faxes boxes)
      if (dragging_trash_folder) {
      (*new_row_iter)[folder_model_columns.icon] = trash_icon_r;
      trash_row_ref = TreeModelRowReference(folder_tree_store_r, Gtk::TreeModel::Path(new_row_iter));
      }
      else (*new_row_iter)[folder_model_columns.icon] = folder_icon_r;

      bool adjust_mapping = false;
      if (source_pathname != new_pathname) {// we can have the new pathname the same
                                            // as the old (source) pathname - say if folder
                                            // is being moved at same level - this block deals
                                          // with a case where they are not the same
      remap_faxes_in_folder(source_pathname, new_pathname);
      // and make sure that move_child_folders_for_level() also adjusts mapping
      adjust_mapping = true;
      }

      // now move all the children of the moved folder to rejoin their parent
      move_child_folders_for_level(folder_drag_source_row_iter->children(), new_row_iter->children(),
                           source_pathname, new_pathname, adjust_mapping);
      folder_tree_store_r->erase(folder_drag_source_row_iter);
      folder_drag_source_row_iter = folder_tree_store_r->children().end();

      write_path();
    }
  }
}

bool FaxListManager::is_valid_drop_path(const std::string& old_pathname,
                              const std::string& new_pathname) {

  // check that the folder is not being dropped on one of its children -
  // with drag and drop syncing issues this can happen
  if (new_pathname.find(old_pathname) == 0
      && new_pathname.size() > old_pathname.size()) { // we can have the new pathname the same
                                                      // as the old pathname - say if folder
                                                      // is being moved at same level
    return false;
  }
  return true;
}

void FaxListManager::remap_faxes_in_folder(const std::string& old_pathname,
                                 const std::string& new_pathname) {

  
  // this method adjusts the mapping for the faxes in a moved folder
  // before calling it check that old_pathname != new_pathname
  // otherwise this method will just waste time

  // first get a listing of the faxes in the folder being moved
  std::vector<std::string> fax_list;
  std::pair<FolderToFaxMap::iterator,
            FolderToFaxMap::iterator> result(folder_to_fax_map.equal_range(old_pathname));
  FolderToFaxMap::iterator map_iter;
  for (map_iter = result.first; map_iter != result.second; ++map_iter) {
    fax_list.push_back(map_iter->second);
  }

  // erase old elements
  folder_to_fax_map.erase(result.first, result.second);

  // insert new ones
  std::vector<std::string>::iterator vec_iter;
  for (vec_iter = fax_list.begin(); vec_iter != fax_list.end(); ++vec_iter) {
    folder_to_fax_map.insert(FolderToFaxMap::value_type(new_pathname, *vec_iter));
    fax_to_folder_map[*vec_iter] = new_pathname;
  }
}

void FaxListManager::move_child_folders_for_level(const Gtk::TreeModel::Children& source_level,
                                      const Gtk::TreeModel::Children& dest_level,
                                      const std::string& source_level_pathname,
                                      const std::string& dest_level_pathname,
                                      bool adjust_mapping) {

  if (source_level && !source_level.empty()) {
    Gtk::TreeModel::Children::iterator source_row_iter;
    for (source_row_iter = source_level.begin(); source_row_iter != source_level.end();
       ++source_row_iter) {

      std::string node_name = Glib::ustring((*source_row_iter)[folder_model_columns.name]);
      std::string full_source_pathname(source_level_pathname);
      full_source_pathname += PATH_DIVIDER;
      full_source_pathname += node_name;
      std::string full_dest_pathname(dest_level_pathname);
      full_dest_pathname += PATH_DIVIDER;
      full_dest_pathname += node_name;

      Gtk::TreeModel::Children::iterator dest_row_iter = folder_tree_store_r->append(dest_level);
      (*dest_row_iter)[folder_model_columns.name] = Glib::ustring(node_name);
      // as we are not at the root level we know folder_model_columns.root_only must be false
      (*dest_row_iter)[folder_model_columns.root_only] = false;
      (*dest_row_iter)[folder_model_columns.icon] = folder_icon_r;

      if (adjust_mapping) remap_faxes_in_folder(full_source_pathname, full_dest_pathname);
      
      // now recursively work the way up children of this node (if any)
      move_child_folders_for_level(source_row_iter->children(), dest_row_iter->children(),
                           full_source_pathname, full_dest_pathname, adjust_mapping);
    }
  }
}

std::string FaxListManager::get_pathname_for_folder(Gtk::TreeModel::iterator folder_iter) {

  std::string pathname;
  
  for (; folder_iter; folder_iter = folder_iter->parent()) {
    std::string temp;
    temp += PATH_DIVIDER;
    // we don't need a Glib conversion function here - we will use/store the Path to file
    // in UTF-8 - the std::string is just a transparent container for the encoding
    temp += Glib::ustring((*folder_iter)[folder_model_columns.name]);
    pathname.insert(0, temp);
  }
  return pathname;
}

void FaxListManager::display_faxes_slot(void) {

  fax_list_store_r->clear();
  Gtk::TreeModel::iterator row_iter = folder_tree_view.get_selection()->get_selected();
  if (!row_iter) return;


  std::pair<FolderToFaxMap::iterator,
            FolderToFaxMap::iterator> result(folder_to_fax_map.equal_range(get_pathname_for_folder(row_iter)));
  
  if (result.first == result.second) return; // folder without a fax in it

  std::string dir(prog_config.working_dir);
  if (mode == FaxListEnum::received) dir += "/faxin/";
  else dir += "/faxsent/";

  std::string filename;
  std::string line;
  std::ifstream file;
  FolderToFaxMap::iterator iter;

  for (iter = result.first; iter != result.second; ++iter) {
    Gtk::TreeModel::Row item_row = *fax_list_store_r->append();
    item_row[fax_model_columns.name] = iter->second;

    // now see if there is a description
    filename = dir;
    filename += iter->second;
    filename += "/Description";

#ifdef HAVE_IOS_NOCREATE
    file.open(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
    file.open(filename.c_str(), std::ios::in);
#endif

    if (file) {
      while (std::getline(file, line) && line.empty());
    }
    if (!line.empty()) {
      try {
      item_row[fax_model_columns.fax_description] = Glib::locale_to_utf8(line);
      }
      catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in FaxListDialog::display_faxes_slot()\n");
      }
    }

    filename = "";
    line = "";
    file.close();
    file.clear();
  }
}

void FaxListManager::empty_trash_folder(void) {

  std::string trash_path;
  trash_path = PATH_DIVIDER;
  trash_path += gettext("Trash");

  std::pair<FolderToFaxMap::iterator,
            FolderToFaxMap::iterator> result(folder_to_fax_map.equal_range(trash_path));

  if (result.first == result.second) return; // no faxes in trash folder

  std::string dirname(prog_config.working_dir);
  if (mode == FaxListEnum::received) dirname += "/faxin/";
  else dirname += "/faxsent/";

  FolderToFaxMap::iterator map_iter;
  for (map_iter = result.first; map_iter != result.second; ++map_iter) {
    std::string faxdir(dirname);
    faxdir += map_iter->second;

    struct dirent* direntry;
    struct stat statinfo;

    DIR* dir_p;
    if ((dir_p = opendir(faxdir.c_str())) == 0) {
      std::string msg("Can't open directory ");
      msg += faxdir;
      msg += '\n';
      write_error(msg.c_str());
    }

    else {
      chdir(faxdir.c_str());
      while ((direntry = readdir(dir_p)) != 0) {
      stat(direntry->d_name, &statinfo);
      if (S_ISREG(statinfo.st_mode)) unlink(direntry->d_name);
      }

      while (closedir(dir_p) == -1 && errno == EINTR);
      // reset current directory
      std::string temp(prog_config.working_dir + "/faxin");
      chdir(temp.c_str());

      if (rmdir(faxdir.c_str())) {
      std::string msg("Can't delete directory ");
      msg += faxdir;
      msg += "\nThe contents should have been deleted\n"
             "and it should now be empty -- please check\n";
      write_error(msg.c_str());
      }
    }
    
    // erase this fax from the fax to folder map
    FaxToFolderMap::iterator fax_map_iter = fax_to_folder_map.find(map_iter->second);
    if (fax_map_iter != fax_to_folder_map.end()) fax_to_folder_map.erase(fax_map_iter);
    else write_error("Database mapping error in FaxListManager::empty_trash_folder()\n");
  }

  // erase all elements in folder to fax map for the trash folder
  folder_to_fax_map.erase(result.first, result.second);

  // if we are displaying the Trash folder contents then clear the fax tree view
  // (the "Empty trash folder" button shouldn't really be sensitive unless we have
  // selected the Trash folder in the folder tree view, but we might as well check
  // here in case the programme implementation changes - this method will work
  // whichever approach is taken with the GUI
  Gtk::TreeModel::iterator row_iter = folder_tree_view.get_selection()->get_selected();
  if (row_iter && Gtk::TreeModel::Path(row_iter) == trash_row_ref.get_path()) {
    fax_list_store_r->clear();
  }

  // and write out the paths
  write_path();
}

RowPathList::size_type FaxListManager::is_fax_selected(void) {

  SelectedRowsHandle rows_handle(fax_tree_view);
  return rows_handle.size();
}

bool FaxListManager::is_selected_folder_empty(void) {

  bool return_val = false;
  Gtk::TreeModel::iterator row_iter = folder_tree_view.get_selection()->get_selected();
  if (row_iter) {
    std::string pathname(get_pathname_for_folder(row_iter));
    std::pair<FolderToFaxMap::iterator,
            FolderToFaxMap::iterator> result(folder_to_fax_map.equal_range(pathname));
    if (result.first == result.second) { // no faxes in folder
      const Gtk::TreeModel::Children& children = row_iter->children();
      if (!children || children.empty()) return_val = true; // and no child folders
    }
  }
  return return_val;
}

bool FaxListManager::is_selected_folder_permanent(void) {

  bool return_val = false;
  Gtk::TreeModel::iterator row_iter = folder_tree_view.get_selection()->get_selected();
  if (row_iter) {

    std::string folder_name = Glib::ustring((*row_iter)[folder_model_columns.name]);
    std::string base_name;
    if (mode == FaxListEnum::received) base_name = gettext("Inbox");
    else base_name = gettext("Sent box");

    if (folder_name == base_name
      || folder_name == std::string(gettext("Trash"))) {
      return_val = true;
    }
  }
  return return_val;
}

bool FaxListManager::show_trash_folder_icon(void) {

  // we want to show the icon if the trash folder is selected
  // and it is not empty
  bool return_val = false;
  Gtk::TreeModel::iterator row_iter = folder_tree_view.get_selection()->get_selected();
  if (row_iter && Gtk::TreeModel::Path(row_iter) == trash_row_ref.get_path()) {
    // trash folder selected
    // now check if it has faxes in it
    std::string trash_path;
    trash_path = PATH_DIVIDER;
    trash_path += gettext("Trash");
    std::pair<FolderToFaxMap::iterator,
              FolderToFaxMap::iterator> result(folder_to_fax_map.equal_range(trash_path));

    if (result.first != result.second) return_val = true; // faxes in trash folder
  }
  return return_val;
}

void FaxListManager::make_folder(const Glib::ustring& folder_name, bool test_valid) {

  if (!test_valid || folder_name_validator.validate(folder_name).first) {
    Gtk::TreeModel::Row row = *folder_tree_store_r->append();
    row[folder_model_columns.name] = folder_name;
    // since we are creating a new folder it is not root only
    // (only Inbox and Sent box are root only)
    row[folder_model_columns.root_only] = false;
    row[folder_model_columns.icon] = folder_icon_r;
    folder_name_validator.insert_folder_name(folder_name);
    write_path();
  }
}

void FaxListManager::delete_folder(void) {
  // we should have called is_folder_empty() and is_selected_folder_permanent()
  // before getting here, so all we need to do is to delete the row in the
  // folder tree store
  Gtk::TreeModel::iterator row_iter = folder_tree_view.get_selection()->get_selected();
  if (row_iter) {

    folder_name_validator.erase_folder_name(Glib::ustring((*row_iter)[folder_model_columns.name]));
    folder_tree_store_r->erase(row_iter);
    write_path();
  }
}

void FaxListManager::delete_fax(void) {

  RowRefList row_ref_list;
  SelectedRowsHandle rows_handle(fax_tree_view);
  rows_handle.get_ref_list(fax_list_store_r, row_ref_list);

  if (!row_ref_list.empty() && row_ref_list.front().is_valid()) {
    
    std::string dirname(prog_config.working_dir);
    if (mode == FaxListEnum::received) dirname += "/faxin/";
    else dirname += "/faxsent/";

    RowRefList::iterator refs_iter;
    for (refs_iter = row_ref_list.begin();
       refs_iter != row_ref_list.end(); ++refs_iter) {

      Gtk::TreeModel::iterator row_iter =  fax_list_store_r->get_iter(refs_iter->get_path());
      if (row_iter) {

      // get the name of the fax to be deleted
      // we don't need to use a Glib conversion function here - we know the
      // fax name is just plain ASCII numbers
      std::string faxname = Glib::ustring((*row_iter)[fax_model_columns.name]);
      std::string faxdir(dirname);
      faxdir += faxname;

      struct dirent* direntry;
      struct stat statinfo;

      DIR* dir_p;
      if ((dir_p = opendir(faxdir.c_str())) == 0) {
        std::string msg("Can't open directory ");
        msg += faxdir;
        msg += '\n';
        write_error(msg.c_str());
      }

      else {
        chdir(faxdir.c_str());
        while ((direntry = readdir(dir_p)) != 0) {
          stat(direntry->d_name, &statinfo);
          if (S_ISREG(statinfo.st_mode)) unlink(direntry->d_name);
        }

        while (closedir(dir_p) == -1 && errno == EINTR);
        // reset current directory
        std::string temp(prog_config.working_dir + "/faxin");
        chdir(temp.c_str());

        if (rmdir(faxdir.c_str())) {
          std::string msg("Can't delete directory ");
          msg += faxdir;
          msg += "\nThe contents should have been deleted\n"
                 "and it should now be empty -- please check\n";
          write_error(msg.c_str());
        }
        else {

          // now adjust the mapping of faxes to folders (and vice-versa)
          std::string pathname(fax_to_folder_map[faxname]);
          std::pair<FolderToFaxMap::iterator,
                    FolderToFaxMap::iterator> result(folder_to_fax_map.equal_range(pathname));
  
          if (result.first == result.second) {
            write_error("Equal range: database mapping error in FaxListManager::delete_fax()\n");
            return;
          }

          FolderToFaxMap::iterator folder_map_iter = result.first;
          bool found_it = false;

          while (!found_it && folder_map_iter != result.second) {
            if (folder_map_iter->second == faxname) {
            found_it = true;
            folder_to_fax_map.erase(folder_map_iter);
            }
            else ++folder_map_iter;
          }
          if (!found_it) {
            write_error("Fax to delete not found: database mapping error in FaxListManager::delete_fax()\n");
          }

          FaxToFolderMap::iterator fax_map_iter = fax_to_folder_map.find(faxname);
          if (fax_map_iter != fax_to_folder_map.end()) fax_to_folder_map.erase(fax_map_iter);
          else write_error("Database mapping error in FaxListManager::delete_fax()\n");
          
          // and delete the fax in the fax list
          fax_list_store_r->erase(row_iter);

        }
      }
      }
      else write_error("Database mapping error in FaxListManager::delete_fax()\n");
    }
    // and write out the paths
    write_path();
  }
}

void FaxListManager::refresh(void) {

  populate_fax_list();
}

void FaxListManager::write_path(void) {
  std::string dir(prog_config.working_dir);
  if (mode == FaxListEnum::received) dir += "/faxin/";
  else dir += "/faxsent/";

  // first write the path list file for the folders
  std::string filename(dir);
  filename += "PathList";
  std::ofstream file(filename.c_str(), std::ios::out);

  if (file) write_folderpath_for_level(folder_tree_store_r->children(), file);
  else write_error("Cannot open PathList file for writing in FaxListManager::write_path()\n");
  
  file.close();
  file.clear();

  // now write out the individual fax path files

  FaxToFolderMap::iterator iter;
  for (iter = fax_to_folder_map.begin(); iter != fax_to_folder_map.end(); ++iter) {

    std::string filename(dir);
    // we don't need to use a Glib conversion function here - we know the
    // fax name is just plain ASCII numbers
    filename += iter->first;
    filename += "/Path";
    file.open(filename.c_str(), std::ios::out);
    if (file) file << iter->second << std::endl;

    else {
      std::string message("Cannot open file ");
      message += filename;
      message += '\n';
      write_error(message.c_str());
    }
    file.close();
    file.clear();
  }
}

void FaxListManager::write_folderpath_for_level(const Gtk::TreeModel::Children& level,
                                    std::ofstream& file) {

  if (level && !level.empty()) {
    Gtk::TreeModel::Children::iterator row_iter;
    for (row_iter = level.begin(); row_iter != level.end(); ++row_iter) {
      
      std::string pathname = get_pathname_for_folder(row_iter);
      file << pathname << '\n';

      // now recursively work the way up children of this node (if any)
      write_folderpath_for_level(row_iter->children(), file);
    }
  }
}

void FaxListManager::describe_fax(const Glib::ustring& description) {

  SelectedRowsHandle rows_handle(fax_tree_view);

  if (!rows_handle.is_empty()) {
    Gtk::TreeModel::iterator row_iter = fax_list_store_r->get_iter(rows_handle.front());

    if (row_iter) {
      (*row_iter)[fax_model_columns.fax_description] = description;

      std::string filename(prog_config.working_dir);
      if (mode == FaxListEnum::received) filename += "/faxin/";
      else filename += "/faxsent/";

      // we don't need to use a Glib conversion function here - we know the
      // fax name is just plain ASCII numbers
      filename += Glib::ustring((*row_iter)[fax_model_columns.name]);
      filename += "/Description";
      std::ofstream file(filename.c_str(), std::ios::out);
      // this try()/catch() block is ultra-cautious - something has gone seriously
      // wrong with the program if the UTF-8 conversion fails since the source is
      // a Gtk::Entry object
      try {
      if (file) file << Glib::locale_from_utf8(description);
    
      else {
        std::string msg("Can't open file ");
        msg += filename;
        msg += '\n';
        write_error(msg.c_str());
      }
      }
      catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in FaxListDialog::describe_fax()\n");
      }
    }
  }
}

bool FaxListManager::are_selected_faxes_in_trash_folder(void) {

  Gtk::TreeModel::iterator row_iter = folder_tree_view.get_selection()->get_selected();
  if (row_iter && Gtk::TreeModel::Path(row_iter) == trash_row_ref.get_path()) return true;
  return false;
}

void FaxListManager::move_selected_faxes_to_trash_folder(void) {

  // pretend we are doing a drag and drop

  // this is the destination of the drop
  Gtk::TreeModel::iterator trash_row_iter = folder_tree_store_r->get_iter(trash_row_ref.get_path());

  // these are the faxes to be dropped
  SelectedRowsHandle rows_handle(fax_tree_view);
  rows_handle.get_ref_list(fax_list_store_r, fax_drag_source_row_refs);

  // now move the fax using the pretend drop
  if (trash_row_iter) move_fax(trash_row_iter);
  else write_error("Selection error in FaxListManager::move_selected_fax_to_trash_folder()\n");
}

Glib::ustring FaxListManager::get_fax_number(void) {

  Glib::ustring return_val;

  SelectedRowsHandle rows_handle(fax_tree_view);
  if (!rows_handle.is_empty()) {
    Gtk::TreeModel::iterator row_iter = fax_list_store_r->get_iter(rows_handle.front());
    if (row_iter) return_val = (*row_iter)[fax_model_columns.name];
  }
  return return_val;
}

Glib::ustring FaxListManager::get_fax_description(void) {

  Glib::ustring return_val;

  SelectedRowsHandle rows_handle(fax_tree_view);
  if (!rows_handle.is_empty()) {
    Gtk::TreeModel::iterator row_iter = fax_list_store_r->get_iter(rows_handle.front());
    if (row_iter) return_val = (*row_iter)[fax_model_columns.fax_description];
  }
  return return_val;
}

Glib::ustring FaxListManager::get_folder_name(void) {

  Glib::ustring return_val;
  Gtk::TreeModel::iterator row_iter = folder_tree_view.get_selection()->get_selected();
  if (row_iter) return_val = (*row_iter)[folder_model_columns.name];
  
  return return_val;
}

Generated by  Doxygen 1.6.0   Back to index