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

fax_list.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 <iomanip>
#include <cstring>
#include <cstdlib>

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <limits.h>
#include <signal.h>
#include <errno.h>

#include <gtkmm/image.h>
#include <gtkmm/tooltips.h>
#include <gtkmm/main.h>
#include <gtkmm/stock.h>
#include <gtkmm/enums.h>

#include <gdkmm/pixbuf.h>
#include <gdk/gdkkeysyms.h> // the key codes are here

#include <glibmm/convert.h>
#include <glibmm/timer.h>
#include <glibmm/thread.h>

#include "fax_list.h"
#include "dialogs.h"
#include "fax_list_icons.h"
#include "pipes.h"

#ifdef HAVE_PTHREAD_SIGMASK 
#include <pthread.h>
#endif

#ifdef HAVE_STRINGSTREAM
#include <sstream>
#else
#include <strstream>
#endif

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

int FaxListDialog::is_fax_received_list = 0;
int FaxListDialog::is_fax_sent_list = 0;



FaxListDialog::FaxListDialog(FaxListEnum::Mode mode_, const int standard_size_):
                              Gtk::Window(Gtk::WINDOW_TOPLEVEL),
                              mode(mode_), standard_size(standard_size_),
                        fax_basename_sem(1),
                              fax_list_manager(mode),
                        fax_list_box(false, 0), table(2, 1, false),
                              close_button(Gtk::Stock::CLOSE),
                        button_box(Gtk::BUTTONBOX_SPREAD) {

  // notify the existence of this object
  if (mode == FaxListEnum::received) is_fax_received_list++;
  else is_fax_sent_list++;

  folder_list_scroll_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  folder_list_scroll_window.set_shadow_type(Gtk::SHADOW_IN);
  fax_list_manager.insert_folder_tree_view(folder_list_scroll_window);

  fax_list_scroll_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  fax_list_scroll_window.set_shadow_type(Gtk::SHADOW_IN);
  fax_list_manager.insert_fax_tree_view(fax_list_scroll_window);

  // set up the tool bar
  tool_bar.set_orientation(Gtk::ORIENTATION_HORIZONTAL);
  tool_bar.set_toolbar_style(Gtk::TOOLBAR_ICONS);

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

  // first make the image widgets
  Gtk::Image* print_image_p = manage(new Gtk::Image(Gtk::Stock::PRINT, efax_gtk_button_size));
  Gtk::Image* view_image_p = manage(new Gtk::Image(Gdk::Pixbuf::create_from_xpm_data(view_xpm)));
  Gtk::Image* describe_image_p = manage(new Gtk::Image(Gdk::Pixbuf::create_from_xpm_data(describe_xpm)));
  Gtk::Image* delete_fax_image_p = manage(new Gtk::Image(Gtk::Stock::DELETE, efax_gtk_button_size));
  Gtk::Image* empty_trash_image_p = manage(new Gtk::Image(Gtk::Stock::CLEAR, efax_gtk_button_size));
  Gtk::Image* add_folder_image_p = manage(new Gtk::Image(Gdk::Pixbuf::create_from_xpm_data(add_folder_xpm)));
  Gtk::Image* delete_folder_image_p = manage(new Gtk::Image(Gdk::Pixbuf::create_from_xpm_data(delete_folder_xpm)));
  Gtk::Image* refresh_image_p = manage(new Gtk::Image(Gtk::Stock::REFRESH, efax_gtk_button_size));

  tool_bar.set_tooltips(true);

#if GTKMM_VERSION >= 24

  // if using GTK+ 2.4 or higher, normalise the height of images
  // in the Gtk::Toolbar object
  view_image_p->set_size_request(-1, 26);
  describe_image_p->set_size_request(-1, 26);
  add_folder_image_p->set_size_request(-1, 26);
  delete_folder_image_p->set_size_request(-1, 26);

  tool_bar.set_toolbar_style(Gtk::TOOLBAR_ICONS);

  Gtk::Tooltips* tooltips_p = tool_bar.get_tooltips_object();
  tooltips_p->enable();

  print_button_p = manage(new Gtk::ToolButton(*print_image_p));
  tool_bar.append(*print_button_p, sigc::mem_fun(*this, &FaxListDialog::print_fax_prompt));
  print_button_p->set_sensitive(false);
  print_button_p->set_tooltip(*tooltips_p, gettext("Print selected fax"));

  view_button_p = manage(new Gtk::ToolButton(*view_image_p));
  tool_bar.append(*view_button_p, sigc::mem_fun(*this, &FaxListDialog::view_fax));
  view_button_p->set_sensitive(false);
  view_button_p->set_tooltip(*tooltips_p, gettext("View selected fax"));

  describe_button_p = manage(new Gtk::ToolButton(*describe_image_p));
  tool_bar.append(*describe_button_p, sigc::mem_fun(*this, &FaxListDialog::describe_fax_prompt));
  describe_button_p->set_sensitive(false);
  describe_button_p->set_tooltip(*tooltips_p, gettext("Add/amend description for selected fax"));

  delete_fax_button_p = manage(new Gtk::ToolButton(*delete_fax_image_p));
  tool_bar.append(*delete_fax_button_p, sigc::mem_fun(*this, &FaxListDialog::delete_fax));
  delete_fax_button_p->set_sensitive(false);
  delete_fax_button_p->set_tooltip(*tooltips_p, gettext("Delete selected fax"));

  empty_trash_button_p = manage(new Gtk::ToolButton(*empty_trash_image_p));
  tool_bar.append(*empty_trash_button_p, sigc::mem_fun(*this, &FaxListDialog::empty_trash_prompt));
  empty_trash_button_p->set_sensitive(false);
  empty_trash_button_p->set_tooltip(*tooltips_p, gettext("Empty trash folder"));

  Gtk::ToolButton* add_folder_button_p = manage(new Gtk::ToolButton(*add_folder_image_p));
  tool_bar.append(*add_folder_button_p, sigc::mem_fun(*this, &FaxListDialog::add_folder_prompt));
  add_folder_button_p->set_tooltip(*tooltips_p, gettext("Add new folder"));

  delete_folder_button_p = manage(new Gtk::ToolButton(*delete_folder_image_p));
  tool_bar.append(*delete_folder_button_p, sigc::mem_fun(*this, &FaxListDialog::delete_folder_prompt));
  delete_folder_button_p->set_sensitive(false);
  delete_folder_button_p->set_tooltip(*tooltips_p, gettext("Delete selected folder"));

  Gtk::ToolButton* refresh_button_p = manage(new Gtk::ToolButton(*refresh_image_p));
  tool_bar.append(*refresh_button_p, sigc::mem_fun(*this, &FaxListDialog::refresh_slot));
  refresh_button_p->set_tooltip(*tooltips_p, gettext("Refresh fax list"));

#else

  Gtk::Button* add_folder_button_p;
  Gtk::Button* refresh_button_p;
  {
    using namespace Gtk::Toolbar_Helpers;
    
    ToolList& tool_list = tool_bar.tools();
    tool_list.push_back(ButtonElem(*print_image_p, SigC::slot(*this, &FaxListDialog::print_fax_prompt),
                           gettext("Print selected fax")));
    print_button_p = static_cast<Gtk::Button*>(tool_list.back().get_widget());
    print_button_p->set_relief(Gtk::RELIEF_NONE);
    print_button_p->set_sensitive(false);

    tool_list.push_back(ButtonElem(*view_image_p, SigC::slot(*this, &FaxListDialog::view_fax),
                           gettext("View selected fax")));
    view_button_p = static_cast<Gtk::Button*>(tool_list.back().get_widget());
    view_button_p->set_relief(Gtk::RELIEF_NONE);
    view_button_p->set_sensitive(false);

    tool_list.push_back(ButtonElem(*describe_image_p, SigC::slot(*this, &FaxListDialog::describe_fax_prompt),
                           gettext("Add/amend description for selected fax")));
    describe_button_p = static_cast<Gtk::Button*>(tool_list.back().get_widget());
    describe_button_p->set_relief(Gtk::RELIEF_NONE);
    describe_button_p->set_sensitive(false);

    tool_list.push_back(ButtonElem(*delete_fax_image_p, SigC::slot(*this, &FaxListDialog::delete_fax),
                           gettext("Delete selected fax")));
    delete_fax_button_p = static_cast<Gtk::Button*>(tool_list.back().get_widget());
    delete_fax_button_p->set_relief(Gtk::RELIEF_NONE);
    delete_fax_button_p->set_sensitive(false);

    tool_list.push_back(ButtonElem(*empty_trash_image_p, SigC::slot(*this, &FaxListDialog::empty_trash_prompt),
                           gettext("Empty trash folder")));
    empty_trash_button_p = static_cast<Gtk::Button*>(tool_list.back().get_widget());
    empty_trash_button_p->set_relief(Gtk::RELIEF_NONE);
    empty_trash_button_p->set_sensitive(false);

    tool_list.push_back(ButtonElem(*add_folder_image_p, SigC::slot(*this, &FaxListDialog::add_folder_prompt),
                           gettext("Add new folder")));
    add_folder_button_p = static_cast<Gtk::Button*>(tool_list.back().get_widget());

    tool_list.push_back(ButtonElem(*delete_folder_image_p, SigC::slot(*this, &FaxListDialog::delete_folder_prompt),
                           gettext("Delete selected folder")));
    delete_folder_button_p = static_cast<Gtk::Button*>(tool_list.back().get_widget());
    delete_folder_button_p->set_relief(Gtk::RELIEF_NONE);
    delete_folder_button_p->set_sensitive(false);

    tool_list.push_back(ButtonElem(*refresh_image_p, SigC::slot(*this, &FaxListDialog::refresh_slot),
                           gettext("Refresh fax list")));
    refresh_button_p = static_cast<Gtk::Button*>(tool_list.back().get_widget());
  }

#endif

  button_box.add(close_button);

  list_pane.pack1(folder_list_scroll_window, false, true);
  list_pane.pack2(fax_list_scroll_window, true, true);

  list_pane.set_position(standard_size * 5);

  table.attach(list_pane, 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::EXPAND | Gtk::FILL,
       Gtk::SHRINK, standard_size/3, standard_size/3);

  fax_list_box.pack_start(tool_bar, false, false);
  fax_list_box.pack_start(table, true, true);

  // now connect up the signal which indicates a selection has been made
  fax_list_manager.selection_notify.connect(sigc::mem_fun(*this, &FaxListDialog::set_buttons_slot));

  // connect up close_button
  close_button.signal_clicked().connect(sigc::mem_fun(*this, &FaxListDialog::close_slot));
  close_button.set_flags(Gtk::CAN_DEFAULT);
  close_button.grab_focus();

  table.set_border_width(standard_size/3);

  if (mode == FaxListEnum::received) set_title(gettext("efax-gtk: Received fax list"));
  else set_title(gettext("efax-gtk: Sent fax list"));
  //set_position(Gtk::WIN_POS_CENTER);
  add(fax_list_box);

  set_default_size(standard_size * 18, standard_size * 16);

  set_icon(prog_config.window_icon_r);

  show_all();

#if GTKMM_VERSION < 24
  add_folder_button_p->set_relief(Gtk::RELIEF_NORMAL);
  refresh_button_p->set_relief(Gtk::RELIEF_NORMAL);
#endif
  add_folder_button_p->set_sensitive(true);
  refresh_button_p->set_sensitive(true);
}

FaxListDialog::~FaxListDialog(void) {
  // notify the destruction of this object
  if (mode == FaxListEnum::received) is_fax_received_list--;
  else is_fax_sent_list--;
}

void FaxListDialog::close_slot(void) {
  hide_all();
  delete this; // this is completely safe as the dialog is self-owning and modeless
}


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

void FaxListDialog::set_buttons_slot(void) {

  // see if fax is selected
  RowPathList::size_type fax_count = fax_list_manager.is_fax_selected();

  if (fax_count) {
    delete_fax_button_p->set_sensitive(true);
#if GTKMM_VERSION < 24
    delete_fax_button_p->set_relief(Gtk::RELIEF_NORMAL);
#endif
  }
  else {
    delete_fax_button_p->set_sensitive(false);
#if GTKMM_VERSION < 24
    delete_fax_button_p->set_relief(Gtk::RELIEF_NONE);
#endif
  }

  if (fax_count == 1) {
    print_button_p->set_sensitive(true);
    view_button_p->set_sensitive(true);
    describe_button_p->set_sensitive(true);

#if GTKMM_VERSION < 24
    print_button_p->set_relief(Gtk::RELIEF_NORMAL);
    view_button_p->set_relief(Gtk::RELIEF_NORMAL);
    describe_button_p->set_relief(Gtk::RELIEF_NORMAL);
#endif
  }
  else {
    print_button_p->set_sensitive(false);
    view_button_p->set_sensitive(false);
    describe_button_p->set_sensitive(false);

#if GTKMM_VERSION < 24
    print_button_p->set_relief(Gtk::RELIEF_NONE);
    view_button_p->set_relief(Gtk::RELIEF_NONE);
    describe_button_p->set_relief(Gtk::RELIEF_NONE);
#endif
  }

  // see if folder is selected
  if (fax_list_manager.is_folder_selected()) {
    if (!fax_list_manager.is_selected_folder_permanent()) {
      delete_folder_button_p->set_sensitive(true);
#if GTKMM_VERSION < 24
      delete_folder_button_p->set_relief(Gtk::RELIEF_NORMAL);
#endif
    }
    else {
      delete_folder_button_p->set_sensitive(false);
#if GTKMM_VERSION < 24
      delete_folder_button_p->set_relief(Gtk::RELIEF_NONE);
#endif
    }
    if (fax_list_manager.show_trash_folder_icon()) {
      empty_trash_button_p->set_sensitive(true);
#if GTKMM_VERSION < 24
      empty_trash_button_p->set_relief(Gtk::RELIEF_NORMAL);
#endif
    }
    else {
      empty_trash_button_p->set_sensitive(false);
#if GTKMM_VERSION < 24
      empty_trash_button_p->set_relief(Gtk::RELIEF_NONE);
#endif
    }
  }
}

void FaxListDialog::delete_fax(void) {

  RowPathList::size_type fax_count;
  if ((fax_count = fax_list_manager.is_fax_selected())) {

    if (fax_list_manager.are_selected_faxes_in_trash_folder()) {

      Glib::ustring msg;

      if (fax_count == 1) {
      msg = gettext("Permanently delete fax ");
      msg += fax_list_manager.get_fax_number() + gettext("?\n");
      msg += gettext("\n(NOTE: This will permanently delete the fax\n"
                   "from the file system)");
      }
      else {
      msg = gettext("Permanently delete selected faxes?\n"
                  "\n(NOTE: This will permanently delete the faxes\n"
                   "from the file system)");
      }

      PromptDialog* dialog_p = new PromptDialog(msg, gettext("efax-gtk: Delete fax"),
                                    standard_size, *this);
      dialog_p->accepted.connect(sigc::mem_fun(fax_list_manager, &FaxListManager::delete_fax));
      // there is no memory leak -- the memory will be deleted when PromptDialog closes
    }
    else fax_list_manager.move_selected_faxes_to_trash_folder();
  }
}

void FaxListDialog::describe_fax_prompt(void) {

  if (fax_list_manager.is_fax_selected() == 1) {
    DescriptionDialog* dialog_p = new DescriptionDialog(standard_size,
                                          fax_list_manager.get_fax_description(),
                                          *this);
    dialog_p->accepted.connect(sigc::mem_fun(fax_list_manager, &FaxListManager::describe_fax));
    // there is no memory leak -- the memory will be deleted when DescriptionDialog closes
  }
}

void FaxListDialog::empty_trash_prompt(void) {

  Glib::ustring msg(gettext("Empty trash folder?\n"
                      "\n(NOTE: This will permanently delete all the faxes\n"
                      "in the Trash folder from the file system)"));
  PromptDialog* dialog_p = new PromptDialog(msg, gettext("efax-gtk: Trash folder"),
                                  standard_size, *this);
  dialog_p->accepted.connect(sigc::mem_fun(fax_list_manager, &FaxListManager::empty_trash_folder));
  // there is no memory leak -- the memory will be deleted when PromptDialog closes
}

void FaxListDialog::add_folder_prompt(void) {

  AddFolderDialog* dialog_p = new AddFolderDialog(standard_size, *this);
  dialog_p->accepted.connect(sigc::mem_fun(*this, &FaxListDialog::add_folder));
  // there is no memory leak -- the memory will be deleted when DescriptionDialog closes

}

void FaxListDialog::add_folder(const Glib::ustring& folder_name) {

  std::pair<bool, Glib::ustring> result = fax_list_manager.is_folder_name_valid(folder_name);
  if (!result.first) {
    new InfoDialog(result.second, 
               gettext("efax-gtk: Add folder"),
               standard_size, Gtk::MESSAGE_WARNING,
               *this);
    // there is no memory leak -- the memory will be deleted when InfoDialog closes
  }    

  else fax_list_manager.make_folder(folder_name, false);
}

void FaxListDialog::delete_folder_prompt(void) {

  // the "Delete folder" button will not be sensitive if
  // fax_list_manager.is_selected_folder_permanent() returns true
  // but test it here again just in case
  if (fax_list_manager.is_selected_folder_permanent()) beep();

  else if (fax_list_manager.is_folder_selected()) {

    if (!fax_list_manager.is_selected_folder_empty()) {
      new InfoDialog(gettext("Empty the folder by deleting any child folders\n"
                             "and drag-and-dropping or deleting any contained\n"
                       "faxes before deleting folder"), 
                 gettext("efax-gtk: Delete folder"),
                 standard_size, Gtk::MESSAGE_WARNING,
                 *this);
      // there is no memory leak -- the memory will be deleted when InfoDialog closes
    }

    else {
      Glib::ustring msg(gettext("Delete folder: "));
      msg += fax_list_manager.get_folder_name() + (gettext("?"));
    
      PromptDialog* dialog_p = new PromptDialog(msg, gettext("efax-gtk: Delete folder"),
                                    standard_size, *this);
      dialog_p->accepted.connect(sigc::mem_fun(fax_list_manager, &FaxListManager::delete_folder));
      // there is no memory leak -- the memory will be deleted when PromptDialog closes
    }
  }
}

void FaxListDialog::print_fax_prompt(void) {

  // we do not need a mutex before reading prog_config.print_cmd
  // as although read in FaxListDialog::get_print_from_stdin_parms(),
  // the thread which executes that method cannot start until the prompt
  // dialog below is launched
  if(fax_list_manager.is_fax_selected() == 1
     && !prog_config.print_cmd.empty()) {
    // we do not need a mutex before reading prog_config.print_popup either, even though
    // it is also read in FaxListDialog::print_fax_thread(), because it is of type
    // bool which can be safely concurrently read in different threads and in any
    // event it is read in FaxListDialog::print_fax_thread() in a separate process
    // after fork()ing so there isn't even concurrent access
    if (prog_config.print_popup) {
      
      Glib::ustring msg(gettext("Print fax "));
      msg += fax_list_manager.get_fax_number() + gettext("?");
      
      PromptDialog* dialog_p = new PromptDialog(msg, gettext("efax-gtk: Print fax"), standard_size, *this);
      dialog_p->accepted.connect(sigc::mem_fun(*this, &FaxListDialog::print_fax));
      // there is no memory leak -- the memory will be deleted when PromptDialog closes
    }
    else print_fax();
  }
}

std::pair<const char*, char* const*> FaxListDialog::get_print_from_stdin_parms(void) {

  // lock the Prog_config object to stop it being modified or accessed in the initial (GUI)
  // thread while we are accessing it here
  Glib::Mutex::Lock lock(*prog_config.mutex_p);
  std::vector<std::string> print_parms;
  std::string print_cmd;
  std::string print_name;
  std::string::size_type end_pos;
  try {
    print_cmd = Glib::filename_from_utf8(prog_config.print_cmd);
  }
  catch (Glib::ConvertError&) {
    write_error("UTF-8 conversion error in FaxListDialog::print_from_stdin()\n");
    return std::pair<const char*, char* const*>(0,0);
  }
  
  if ((end_pos = print_cmd.find_first_of(' ')) != std::string::npos) { // we have parms
    print_name.assign(print_cmd, 0, end_pos);
    print_parms.push_back(print_name);
    // find start of next parm
    std::string::size_type start_pos = print_cmd.find_first_not_of(' ', end_pos);
    while (start_pos != std::string::npos) {
      end_pos = print_cmd.find_first_of(' ', start_pos);
      if (end_pos != std::string::npos) {
      print_parms.push_back(print_cmd.substr(start_pos, end_pos - start_pos));
      start_pos = print_cmd.find_first_not_of(' ', end_pos); // prepare for next interation
      }
      else {
      print_parms.push_back(print_cmd.substr(start_pos, 
                                     print_cmd.size() - start_pos));
      start_pos = end_pos;
      }
    }
  }

  else { // just a print command without parameters to be passed
    print_name = print_cmd;
    print_parms.push_back(print_name);
  }

  char** exec_parms = new char*[print_parms.size() + 1];

  char** temp_pp = exec_parms;
  std::vector<std::string>::const_iterator iter;
  for (iter = print_parms.begin(); iter != print_parms.end(); ++iter, ++temp_pp) {
    *temp_pp = new char[iter->size() + 1];
    std::strcpy(*temp_pp, iter->c_str());
  }
  
  *temp_pp = 0;

  char* prog_name = new char[print_name.size() + 1];
  std::strcpy(prog_name, print_name.c_str());

  return std::pair<const char*, char* const*>(prog_name, exec_parms);
}

std::pair<const char*, char* const*> FaxListDialog::get_fax_to_ps_parms
                                       (const std::string& basename, bool allow_shrink) {

  // lock the Prog_config object to stop it being modified or accessed in the initial (GUI)
  // thread while we are accessing it here
  Glib::Mutex::Lock lock(*prog_config.mutex_p);

  // set up the parms for efix
  std::vector<std::string> efix_parms;
  std::string temp;
    
  efix_parms.push_back("efix-0.9a");
  // shut up efix (comment out next line and uncomment following one if errors to be reported)
  efix_parms.push_back("-v");
  //efix_parms.push_back("-ve");
  efix_parms.push_back("-r300");
  efix_parms.push_back("-ops");
  temp = "-p";
  temp += prog_config.page_dim;
  efix_parms.push_back(temp);

#ifdef HAVE_STRINGSTREAM
  if (allow_shrink && prog_config.print_shrink.compare("100")) { // if print_shrink is not 100
    temp = "-s0.";
    temp += prog_config.print_shrink;
    efix_parms.push_back(temp);

    std::ostringstream strm;
    if (!prog_config.page_size.compare("a4")) {

      strm << "-d";
      float val = 210 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << static_cast<int>(val + 0.5);
      val = 297 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << ',' << static_cast<int>(val + 0.5) << "mm";
      
      efix_parms.push_back(strm.str());
    }
    
    else if (!prog_config.page_size.compare("letter")) {

      strm << "-d";
      float val = 216 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << static_cast<int>(val + 0.5);
      val = 279 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << ',' << static_cast<int>(val + 0.5) << "mm";
      
      efix_parms.push_back(strm.str());
    }

    else if (!prog_config.page_size.compare("legal")) {

      strm << "-d";
      float val = 216 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << static_cast<int>(val + 0.5);
      val = 356 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << ',' << static_cast<int>(val + 0.5) << "mm";
      
      efix_parms.push_back(strm.str());
    }
  }

  int partnumber = 1;
  std::ostringstream strm;
  strm << basename.c_str() << std::setfill('0')
       << std::setw(3) << partnumber;
  int result = access(strm.str().c_str(), R_OK);
    
  while (!result) {
    efix_parms.push_back(strm.str());
      
    partnumber++;
    strm.str("");
    strm << basename.c_str() << std::setfill('0')
       << std::setw(3) << partnumber;
    result = access(strm.str().c_str(), R_OK);
  }

#else
  if (allow_shrink && prog_config.print_shrink.compare("100")) { // if print_shrink is not 100
    temp = "-s0.";
    temp += prog_config.print_shrink;
    efix_parms.push_back(temp);

    std::ostrstream strm;
    if (!prog_config.page_size.compare("a4")) {

      strm << "-d";
      float val = 210 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << static_cast<int>(val + 0.5);
      val = 297 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << ',' << static_cast<int>(val + 0.5) << "mm" << std::ends;
      
      const char* dim = strm.str();
      efix_parms.push_back(dim);
      delete[] dim;
    }
    
    else if (!prog_config.page_size.compare("letter")) {

      strm << "-d";
      float val = 216 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << static_cast<int>(val + 0.5);
      val = 279 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << ',' << static_cast<int>(val + 0.5) << "mm" << std::ends;
      
      const char* dim = strm.str();
      efix_parms.push_back(dim);
      delete[] dim;
    }

    else if (!prog_config.page_size.compare("legal")) {

      strm << "-d";
      float val = 216 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << static_cast<int>(val + 0.5);
      val = 356 * (100 - std::atoi(prog_config.print_shrink.c_str()))/200.0;
      strm << ',' << static_cast<int>(val + 0.5) << "mm" << std::ends;
      
      const char* dim = strm.str();
      efix_parms.push_back(dim);
      delete[] dim;
    }
  }

  int partnumber = 1;
  std::ostrstream strm;
  strm << basename.c_str() << std::setfill('0')
       << std::setw(3) << partnumber << std::ends;
  const char* file_name = strm.str();
  int result = access(file_name, R_OK);
    
  while (!result) {
    efix_parms.push_back(file_name);
    delete[] file_name;
      
    partnumber++;
    std::ostrstream strm;
    strm << basename.c_str() << std::setfill('0')
       << std::setw(3) << partnumber << std::ends;
    file_name = strm.str();
    result = access(file_name, R_OK);
  }
  delete[] file_name;
#endif

  char** exec_parms = new char*[efix_parms.size() + 1];

  char** temp_pp = exec_parms;
  std::vector<std::string>::const_iterator iter;
  for (iter = efix_parms.begin(); iter != efix_parms.end(); ++iter, ++temp_pp) {
    *temp_pp = new char[iter->size() + 1];
    std::strcpy(*temp_pp, iter->c_str());
  }
  
  *temp_pp = 0;

  char* prog_name = new char[std::strlen("efix-0.9a") + 1];
  std::strcpy(prog_name, "efix-0.9a");

  return std::pair<const char*, char* const*>(prog_name, exec_parms);
}

void FaxListDialog::write_from_stdin(int dest_fd) {

  ssize_t read_result;
  ssize_t write_result;
  ssize_t written;
  char buffer[PIPE_BUF];

  while ((read_result = read(0, buffer, PIPE_BUF)) > 0
       || read_result == -1 && errno == EINTR) {
    if (read_result > 0) {
      written = 0;
      do {
      write_result = write(dest_fd, buffer + written, read_result);
      if (write_result > 0) {
        written += write_result;
        read_result -= write_result;
      }
      } while (read_result && (write_result != -1 || errno == EINTR));
    }
  }
  Glib::usleep(50000);
}

std::pair<const char*, char* const*> FaxListDialog::get_ps_viewer_parms(const char* filename) {

  // lock the Prog_config object to stop it being modified or accessed in the initial (GUI)
  // thread while we are accessing it here
  Glib::Mutex::Lock lock(*prog_config.mutex_p);

  std::vector<std::string> view_parms;
  std::string view_cmd;
  std::string view_name;
  std::string::size_type end_pos;
  try {
    view_cmd = Glib::filename_from_utf8(prog_config.ps_view_cmd);
  }
  catch (Glib::ConvertError&) {
    write_error("UTF-8 conversion error in FaxListDialog::ps_viewer()\n");
    return std::pair<const char*, char* const*>(0,0);
  }

  if ((end_pos = view_cmd.find_first_of(' ')) != std::string::npos) { // we have parms
    view_name.assign(view_cmd, 0, end_pos);
    view_parms.push_back(view_name);
    // find start of next parm
    std::string::size_type start_pos = view_cmd.find_first_not_of(' ', end_pos);
    while (start_pos != std::string::npos) {
      end_pos = view_cmd.find_first_of(' ', start_pos);
      if (end_pos != std::string::npos) {
      view_parms.push_back(view_cmd.substr(start_pos, end_pos - start_pos));
      start_pos = view_cmd.find_first_not_of(' ', end_pos); // prepare for next interation
      }
      else {
      view_parms.push_back(view_cmd.substr(start_pos, 
                                   view_cmd.size() - start_pos));
      start_pos = end_pos;
      }
    }
  }

  else { // just a print command without parameters to be passed
    view_name = view_cmd;
    view_parms.push_back(view_name);
  }

  view_parms.push_back(filename);

  char** exec_parms = new char*[view_parms.size() + 1];

  char** temp_pp = exec_parms;
  std::vector<std::string>::const_iterator iter;
  for (iter = view_parms.begin(); iter != view_parms.end(); ++iter, ++temp_pp) {
    *temp_pp = new char[iter->size() + 1];
    std::strcpy(*temp_pp, iter->c_str());
  }
  
  *temp_pp = 0;

  char* prog_name = new char[view_name.size() + 1];
  std::strcpy(prog_name, view_name.c_str());

  return std::pair<const char*, char* const*>(prog_name, exec_parms);
}

void FaxListDialog::print_fax(void) {

  if (fax_list_manager.is_fax_selected() == 1 && !prog_config.print_cmd.empty()) {
    // fax_basename_sem is initialised to a count of 1, so this will stop
    // fax_basename being overwritten until Sem_sync::post() is called in
    // print_fax_thread() or view_fax_thread() or on Glib::ThreadError being
    // caught
    fax_basename_sem.wait();

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

    // we don't need to use a Glib conversion function here - we know the
    // fax name is just plain ASCII numbers
    std::string fax_number(fax_list_manager.get_fax_number());
    fax_basename += fax_number;
    fax_basename += '/';
    fax_basename += fax_number;
    fax_basename += '.';

    // now block off the signals for which we have set handlers so that the worker
    // thread does not receive the signals, otherwise we will have memory synchronisation
    // issues in multi-processor systems - we will unblock in the initial (GUI) thread
    // as soon as the socket server thread has been launched
    sigset_t sig_mask;
    sigemptyset(&sig_mask);
    sigaddset(&sig_mask, SIGCHLD);
    sigaddset(&sig_mask, SIGQUIT);
    sigaddset(&sig_mask, SIGTERM);
    sigaddset(&sig_mask, SIGINT);
    sigaddset(&sig_mask, SIGHUP);
    sigaddset(&sig_mask, SIGUSR2);
#ifdef HAVE_PTHREAD_SIGMASK 
    pthread_sigmask(SIG_BLOCK, &sig_mask, 0);
#else
    while (sigprocmask(SIG_BLOCK, &sig_mask, 0) == -1 && errno == EINTR);
#endif

    try {
      Glib::Thread::create(sigc::mem_fun(*this, &FaxListDialog::print_fax_thread), false);
    }
    catch (Glib::ThreadError&) {
      write_error("Cannot start new print thread, fax will not be printed\n");
      fax_basename_sem.post();
    }
    // now unblock the signals so that the initial (GUI) thread can receive them
#ifdef HAVE_PTHREAD_SIGMASK 
    pthread_sigmask(SIG_UNBLOCK, &sig_mask, 0);
#else
    while (sigprocmask(SIG_UNBLOCK, &sig_mask, 0) == -1 && errno == EINTR);
#endif
  }
}

void FaxListDialog::print_fax_thread(void) {

  // get the arguments for the exec() calls below (because this is a
  // multi- threaded program, we must do this before fork()ing because
  // we use functions to get the arguments which are not async-signal-safe)
  std::pair<const char*, char* const*> print_from_stdin_parms(get_print_from_stdin_parms());
  std::pair<const char*, char* const*> fax_to_ps_parms(get_fax_to_ps_parms(fax_basename, true));

  // release main GUI thread in print_fax() or view_fax()
  // waiting for this use of fax_basename to finish
  fax_basename_sem.post();

  if (print_from_stdin_parms.first) { // this will be 0 if get_print_from_stdin_parms()
                                      // threw a Glib::ConvertError)

    // now launch a new process to control the printing process
    // the main program process needs to continue while the printing
    // is going on
    pid_t pid = fork();

    if (pid == -1) {
      write_error("Fork error - exiting\n");
      std::exit(FORK_ERROR);
    }
    if (!pid) { // child print process

      // make the call to connect_to_stderr() in this child process
      // dependent on calling a print confirmation popup dialog: if we have
      // elected not to bring up such a dialog, we are probably using a
      // print manager such as kprinter, and we can get confusing messages
      // from stderr from these
      // no mutex is required for the access to prog_config.print_popup
      // because we have fork()ed into a separate process
      if (prog_config.print_popup) connect_to_stderr();

      // now create a blocking pipe which the print processes can use to
      // communicate via stdout/stdin
      Pipe_fifo fork_pipe(Pipe_fifo::block);
      
      pid_t pid = fork();

      if (pid == -1) {
      write_error("Fork error - exiting\n");
      _exit(FORK_ERROR);
      }
      if (!pid) { // process to exec to print_from_stdin()
      
      fork_pipe.connect_to_stdin();
      execvp(print_from_stdin_parms.first, print_from_stdin_parms.second);
      // if we reached this point, then the execvp() call must have failed
      // write error and then end process - use _exit(), not exit()
      write_error("Can't find the print program - please check your installation\n"
                "and the PATH environmental variable\n");
      _exit(0);
      }

      // this is the process which will send postscript to stdout
      fork_pipe.connect_to_stdout();
      execvp(fax_to_ps_parms.first, fax_to_ps_parms.second);

      // if we reached this point, then the execvp() call must have failed
      // report error and then end process - use _exit(), not exit()
      write_error("Can't find the efix-0.9a program - please check your installation\n"
              "and the PATH environmental variable\n");
      _exit(0);
    }
    // release the memory allocated on the heap for
    // the redundant print_from_stdin_parms and fax_to_ps_parms
    // we are in the main parent process here - no worries about
    // only being able to use async-signal-safe functions
    delete_parms(print_from_stdin_parms);
  }
  delete_parms(fax_to_ps_parms);
}


void FaxListDialog::view_fax(void) {
 
  // we do not need a mutex before reading prog_config.ps_view_cmd
  // as although read in FaxListDialog::get_ps_viewer_parms(),
  // the thread which executes that method is launched below as
  // FaxListsDialog::view_fax_thread() and is therefore not running
  // at this stage
  if (fax_list_manager.is_fax_selected() == 1 && !prog_config.ps_view_cmd.empty()) {
    // fax_basename_sem is initialised to a count of 1, so this will stop
    // fax_basename being overwritten until Sem_sync::post() is called in
    // print_fax_thread() or view_fax_thread() or on Glib::ThreadError being
    // caught
    fax_basename_sem.wait();

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

    // we don't need to use a Glib conversion function here - we know the
    // fax name is just plain ASCII numbers
    std::string fax_number(fax_list_manager.get_fax_number());
    fax_basename += fax_number;
    fax_basename += '/';
    fax_basename += fax_number;
    fax_basename += '.';

    // now block off the signals for which we have set handlers so that the socket server
    // thread does not receive the signals, otherwise we will have memory synchronisation
    // issues in multi-processor systems - we will unblock in the initial (GUI) thread
    // as soon as the socket server thread has been launched
    sigset_t sig_mask;
    sigemptyset(&sig_mask);
    sigaddset(&sig_mask, SIGCHLD);
    sigaddset(&sig_mask, SIGQUIT);
    sigaddset(&sig_mask, SIGTERM);
    sigaddset(&sig_mask, SIGINT);
    sigaddset(&sig_mask, SIGHUP);
    sigaddset(&sig_mask, SIGUSR2);
#ifdef HAVE_PTHREAD_SIGMASK 
    pthread_sigmask(SIG_BLOCK, &sig_mask, 0);
#else
    while (sigprocmask(SIG_BLOCK, &sig_mask, 0) == -1 && errno == EINTR);
#endif

    try {
      Glib::Thread::create(sigc::mem_fun(*this, &FaxListDialog::view_fax_thread), false);
    }
    catch (Glib::ThreadError&) {
      write_error("Cannot start new view fax thread, fax cannot be viewed\n");
      fax_basename_sem.post();
    }
    // now unblock the signals so that the initial (GUI) thread can receive them
#ifdef HAVE_PTHREAD_SIGMASK 
    pthread_sigmask(SIG_UNBLOCK, &sig_mask, 0);
#else
    while (sigprocmask(SIG_UNBLOCK, &sig_mask, 0) == -1 && errno == EINTR);
#endif
  }
}

void FaxListDialog::view_fax_thread(void) {
 
  // get a temporary file
  char filename[] = "/tmp/efax-gtk-view.XXXXXX";
  int file_fd = mkstemp(filename);

  if (file_fd == -1) {
    write_error("Failed to make temporary file:\n"
            "please check permissions in /tmp directory");
    _exit(0);
  }

  // now create a pipe and proceed to fork to write the postscript to temporary file
  Pipe_fifo fork_pipe(Pipe_fifo::block);

  // get the arguments for the exec() call to convert the fax files to
  // PS format following the next fork() call (because this is a multi-
  // threaded program, we must do this before fork()ing because we use
  // functions to get the arguments which are not async-signal-safe)
  std::pair<const char*, char* const*> fax_to_ps_parms(get_fax_to_ps_parms(fax_basename, false));

  // release main GUI thread in print_fax() or view_fax()
  // waiting for this use of fax_basename to finish
  fax_basename_sem.post();

  // now fork to create the process which will write postscript to stdout
  pid_t pid = fork();

  if (pid == -1) {
    write_error("Fork error - exiting\n");
    _exit(FORK_ERROR);
  }
    
  if (!pid) { // child process which will send postscript to stdout
  
    // this is the child process to write to stdin
    // now we have fork()ed we can connect to stderr
    connect_to_stderr();

    fork_pipe.connect_to_stdout();
    execvp(fax_to_ps_parms.first, fax_to_ps_parms.second);

    // if we reached this point, then the execvp() call must have failed
    // report error and then end process - use _exit(), not exit()
    write_error("Can't find the efix-0.9a program - please check your installation\n"
            "and the PATH environmental variable\n");
    _exit(0);
  }
  // this is the parent process, which will convert the fax to Postscript
  // and then pipe it on stdout to write_from_stdin()
    
  // first, release the memory allocated on the heap for
  // the redundant fax_to_ps_parms
  // we are in the main parent process here - no worries about
  // only being able to use async-signal-safe functions
  delete_parms(fax_to_ps_parms);
    
  // now write stdin in (containing the generated PS text) to file
  fork_pipe.connect_to_stdin();
  write_from_stdin(file_fd);

  // the temporary file to be viewed has now been created
  // so launch a new process to control the viewing process
  // we will reset handlers and then fork() again having done
  // so, so that we can wait on the child of child

  // first get the arguments for the exec() call to launch the fax
  // viewer following the next fork() call (because this is a multi-
  // threaded program, we must do this before fork()ing because we use
  // functions to get the arguments which are not async-signal-safe)
  std::pair<const char*, char* const*> ps_viewer_parms(get_ps_viewer_parms(filename));

  if (ps_viewer_parms.first) { // this will be 0 if get_ps_viewer_parms()
                               // threw a Glib::ConvertError)
    
    pid = fork();
    
    if (pid == -1) {
      write_error("Fork error\n");
      std::exit(FORK_ERROR);
    }
    if (!pid) { // child view-control process
    
      // unblock signals as these are blocked for all worker threads
      // (the child process inherits the signal mask of the thread
      // creating it with the fork() call)
      sigset_t sig_mask;
      sigemptyset(&sig_mask);
      sigaddset(&sig_mask, SIGCHLD);
      sigaddset(&sig_mask, SIGQUIT);
      sigaddset(&sig_mask, SIGTERM);
      sigaddset(&sig_mask, SIGINT);
      sigaddset(&sig_mask, SIGHUP);
      sigaddset(&sig_mask, SIGUSR2);
      // this child process is single threaded, so we can use sigprocmask()
      // rather than pthread_sigmask() (and should do so as sigprocmask()
      // is guaranteed to be async-signal-safe)
      // this process will not be receiving interrupts so we do not need
      // to test for EINTR on the call to sigprocmask()
      sigprocmask(SIG_UNBLOCK, &sig_mask, 0);

      connect_to_stderr();

      // now fork() again
      pid_t pid = fork();

      if (pid == -1) {
      write_error("Fork error - exiting\n");
      _exit(FORK_ERROR);
      }
    
      if (!pid) { // child process to call ps_viewer() from
        
      execvp(ps_viewer_parms.first, ps_viewer_parms.second);

      // if we reached this point, then the execvp() call must have failed
      // report error and then end process - use _exit(), not exit()
      write_error("Can't find the ps viewer program - please check your installation\n"
                "and the PATH environmental variable\n");
      _exit(0);
      }
      // now we are back to the main viewing process again
      // delete the temporary file

      // first wait till the ps_viewer() process is closed by the user exiting
      // the postscript viewing program
      // Note that we have already fork()ed so this process will not be
      // receiving signals so we do not need to test for EINTR on the
      // call to wait()
      wait(0);
    
      unlink(filename);
      // now terminate the process
      _exit(0);
    }
    // release the memory allocated on the heap for
    // the redundant ps_viewer_parms
    // we are in the main parent process here - no worries about
    // only being able to use async-signal-safe functions
    delete_parms(ps_viewer_parms);
  }
}

void FaxListDialog::delete_parms(std::pair<const char*, char* const*> parms_pair) {

  delete[] parms_pair.first;
  char* const* temp_pp = parms_pair.second;
  for(; *temp_pp != 0; ++temp_pp) {
    delete[] *temp_pp;
  }
  delete[] parms_pair.second;
}

void FaxListDialog::refresh_slot(void) {

  fax_list_manager.refresh();
  set_buttons_slot();
}

EntryDialog::EntryDialog(const int standard_size, const Glib::ustring& entry_text,
                   const Glib::ustring& caption_text, const Glib::ustring& label_text,
                   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),
                       label(label_text), table(3, 1, false),
                             parent(window) {

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

  table.attach(label, 0, 1, 0, 1, Gtk::FILL | Gtk::EXPAND,
       Gtk::FILL | Gtk::EXPAND, standard_size/2, standard_size/4);

  table.attach(entry, 0, 1, 1, 2, Gtk::FILL | Gtk::EXPAND,
       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, &EntryDialog::selected), true));
  cancel_button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &EntryDialog::selected), false));

  ok_button.set_flags(Gtk::CAN_DEFAULT);
  cancel_button.set_flags(Gtk::CAN_DEFAULT);

  add(table);
  
  entry.set_text(entry_text);
  set_title(caption_text);
  set_transient_for(parent);
  set_type_hint(Gdk::WINDOW_TYPE_HINT_DIALOG);
  parent.set_sensitive(false);
  set_modal(true);

  entry.set_size_request(standard_size * 9, standard_size);
  set_border_width(standard_size/2);

  entry.grab_focus();

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

  set_icon(prog_config.window_icon_r);

  show_all();

  // we now need to deselect what is in the entry
  entry.select_region(0,0);
}

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

void EntryDialog::selected(bool accept) {
  parent.set_sensitive(true); // do this before we emit accepted()
  hide_all();
  if (accept) accepted(entry.get_text());
  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 EntryDialog::on_delete_event(GdkEventAny*) {
  selected(false);
  return true; // returning true prevents destroy sig being emitted
}

bool EntryDialog::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
}

DescriptionDialog::DescriptionDialog(const int standard_size,
                             const Glib::ustring& text,
                             Gtk::Window& window):
                      EntryDialog(standard_size,
                          text,
                          gettext("efax-gtk: Fax description"),
                          gettext("Fax description?"),
                          window) {}

AddFolderDialog::AddFolderDialog(const int standard_size,
                         Gtk::Window& window):
                      EntryDialog(standard_size,
                          "",
                          gettext("efax-gtk: Add folder"),
                          gettext("Folder name?\n"
                                "(Note this will be placed in the top\n"
                                "level and can be drag-and-dropped\n"
                                "into other folders)"),
                          window) {}

Generated by  Doxygen 1.6.0   Back to index