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

mainwindow.cpp

/* Copyright (C) 2001 to 2004 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 <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <errno.h>

#include <ctime>
#include <cstdlib>
#include <cstring>

#include <gtkmm/menu.h>
#include <gtkmm/style.h>
#include <gtkmm/main.h>
#include <gtkmm/stock.h>
#include <gtkmm/image.h>
#include <gtkmm/enums.h>
#include <gdkmm/pixbuf.h>
#include <gdkmm/color.h>
#include <gdkmm/colormap.h>
#include <gdkmm/gc.h>
#include <glibmm/main.h>
#include <glibmm/convert.h>

#include "mainwindow.h"
#include "dialogs.h"
#include "addressbook.h"
#include "file_list.h"
#include "settings.h"
#include "socket_notify.h"
#include "menu_icons.h"

#if GTKMM_VERSION >= 24
#include <gtk/gtkicontheme.h>
#include <gtkmm/icontheme.h>
#include "gtk_icon_info_handle.h"
#endif

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


#define TIMER_INTERVAL 200              // number of milliseconds between timer events for the main timer
#define LOGFILE_TIMER_INTERVAL 60000    // number of milliseconds between flushing of logfile

extern "C" void close_signalhandler(int);
extern "C" void usr2_signalhandler(int);
static volatile sig_atomic_t close_flag = false;
static volatile sig_atomic_t usr2_flag = false;


Pipe_fifo MainWindow::error_pipe(Pipe_fifo::non_block);
bool MainWindow::connected_to_stderr = false;
static Glib::Mutex* write_error_mutex_p;

MainWindow::MainWindow(const std::string& messages,bool start_hidden,
                   bool start_in_standby, const char* filename):
                       // start_hidden and start_in_standby have a default value of
                   // false and filename has a default value of 0
                              Gtk::Window(Gtk::WINDOW_TOPLEVEL), standard_size(24),
                        obscured(false), minimised(false),
                        drawing_area_alignment(Gtk::ALIGN_RIGHT,
                                         Gtk::ALIGN_CENTER,
                                         0, 1),
                              file_entry_table(3, 3, false), win_table(5, 3, false),
                        file_entry_frame(gettext("Fax to send")),
                        file_button(gettext("File  ")),
                        socket_button(gettext("Socket")),
                        fax_method_label(gettext("Fax entry method: ")),
                        single_file_button(gettext("Single file")),
                        multiple_file_button(gettext("Multiple files")),
                        socket_list_button(gettext("Socket list")),
                        number_button(gettext("Tel number: ")),
                        send_button(gettext("Send fax")),
                        receive_answer_button(gettext("Answer call")),
                        receive_takeover_button(gettext("Take over call")),
                        receive_standby_button(gettext("Standby")),
                        stop_button(gettext("Stop")),
                              status_line(standard_size),
                        tray_item(*this) {

  // catch any relevant Unix signals for an orderly closedown
  // catch SIGQUIT, SIGTERM SIGINT SIGHUP
  // it is safe to use signal() for these
  signal(SIGQUIT, close_signalhandler);
  signal(SIGTERM, close_signalhandler);
  signal(SIGINT, close_signalhandler);
  signal(SIGHUP, close_signalhandler);

  // ignore SIGPIPE
  struct sigaction sig_act_pipe;
  sig_act_pipe.sa_handler = SIG_IGN;
  // we don't need to mask off any signals
  sigemptyset(&sig_act_pipe.sa_mask);
  sig_act_pipe.sa_flags = 0;
  sigaction(SIGPIPE, &sig_act_pipe, 0);

  // set up SIGUSR2 signal handler
  struct sigaction sig_act_usr2;
  sig_act_usr2.sa_handler = usr2_signalhandler;
  // we don't need to mask off any signals
  sigemptyset(&sig_act_usr2.sa_mask);
#ifdef SA_RESTART
  sig_act_usr2.sa_flags = SA_RESTART;
#endif
  sigaction(SIGUSR2, &sig_act_usr2, 0);

  // we don't need to set a child signal handle - the default
  // (SIG_DFL) is fine - we want to ignore it as we will reap
  // exit status in EfaxController::timer_event(), which calls
  // waitpid()

  write_error_mutex_p = new Glib::Mutex;

  get_longest_button_text();

  Gtk::RadioButton::Group class_group(file_button.get_group());
  socket_button.set_group(class_group);
  file_button.set_active(true);

  fax_method_radio_box.pack_start(file_button, false, false, 0);
  fax_method_radio_box.pack_start(socket_button, false, false, 0);
  fax_method_radio_frame.add(fax_method_radio_box);

  drawing_area_alignment.add(drawing_area);

  fax_method_box.pack_start(fax_method_label, false, false, 0);
  fax_method_box.pack_start(fax_method_radio_frame, false, false, 0);
  fax_method_box.pack_start(drawing_area_alignment, true, true, 0);

  file_entry_table.attach(file_entry, 0, 3, 0, 1, Gtk::EXPAND | Gtk::FILL,
                    Gtk::SHRINK, standard_size/3, standard_size/3);
  file_entry_table.attach(fax_method_box, 0, 3, 1, 2, Gtk::EXPAND | Gtk::FILL,
                    Gtk::SHRINK, standard_size/3, standard_size/3);
  file_entry_table.attach(single_file_button, 0, 1, 2, 3, Gtk::EXPAND,
                    Gtk::SHRINK, standard_size/3, standard_size/3);
  file_entry_table.attach(multiple_file_button, 1, 2, 2, 3, Gtk::EXPAND,
                    Gtk::SHRINK, standard_size/3, standard_size/3);
  file_entry_table.attach(socket_list_button, 2, 3, 2, 3, Gtk::EXPAND,
                    Gtk::SHRINK, standard_size/3, standard_size/3);
  
  file_entry_frame.add(file_entry_table);

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

  win_table.attach(file_entry_frame, 0, 3, 0, 1, Gtk::EXPAND | Gtk::FILL,
               Gtk::SHRINK, standard_size/3, standard_size/3);
  win_table.attach(number_box, 0, 3, 1, 2, Gtk::EXPAND | Gtk::FILL,
               Gtk::SHRINK, standard_size/3, standard_size/3);
  win_table.attach(text_window, 0, 3, 2, 3, Gtk::EXPAND | Gtk::FILL,
               Gtk::EXPAND | Gtk::FILL, standard_size/3, standard_size/3);

  win_table.attach(send_button, 0, 1, 3, 4, Gtk::EXPAND,
       Gtk::SHRINK, standard_size/3, standard_size/3);
  win_table.attach(receive_answer_button, 1, 2, 3, 4, Gtk::EXPAND,
       Gtk::SHRINK, standard_size/3, standard_size/3);
  win_table.attach(receive_takeover_button, 2, 3, 3, 4, Gtk::EXPAND,
       Gtk::SHRINK, standard_size/3, standard_size/3);

  win_table.attach(receive_standby_button, 0, 1, 4, 5, Gtk::EXPAND,
       Gtk::SHRINK, standard_size/3, standard_size/3);
  win_table.attach(stop_button, 2, 3, 4, 5, Gtk::EXPAND,
       Gtk::SHRINK, standard_size/3, standard_size/3);

  file_button.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::set_file_items_sensitive_slot));
  socket_button.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::set_socket_items_sensitive_slot));

  single_file_button.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::get_file_slot));
  multiple_file_button.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::file_list_slot));
  socket_list_button.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::socket_list_slot));
  number_button.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::addressbook_slot));
  send_button.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::sendfax_slot));
  receive_answer_button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::receive_slot),
                                     EfaxController::receive_answer));
  receive_takeover_button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::receive_slot),
                                       EfaxController::receive_takeover));
  receive_standby_button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::receive_slot),
                                      EfaxController::receive_standby));
  stop_button.signal_clicked().connect(sigc::mem_fun(efax_controller, &EfaxController::stop_slot));

  single_file_button.set_flags(Gtk::CAN_DEFAULT);
  multiple_file_button.set_flags(Gtk::CAN_DEFAULT);
  socket_list_button.set_flags(Gtk::CAN_DEFAULT);
  number_button.set_flags(Gtk::CAN_DEFAULT);
  send_button.set_flags(Gtk::CAN_DEFAULT);
  receive_answer_button.set_flags(Gtk::CAN_DEFAULT);
  receive_takeover_button.set_flags(Gtk::CAN_DEFAULT);
  receive_standby_button.set_flags(Gtk::CAN_DEFAULT);
  stop_button.set_flags(Gtk::CAN_DEFAULT);

  // set up the menu bar
  {
    // first get the about icon

    bool have_about_icon = false;
    Glib::RefPtr<Gdk::Pixbuf> about_pixbuf_r;

#if GTKMM_VERSION >= 24
    Glib::RefPtr<Gtk::IconTheme> icon_theme_r = Gtk::IconTheme::get_default();

    // use the C function to look-up the icon - the gtkmm wrapper does not
    // check whether look-up succeeded.  In addition, Gtk::IconInfo::get_filename()
    // incorrectly returns a Glib::ustring object instead of a std::string object,
    // without converting its contents to a valid UTF-8 encoded string

    Gtk_icon_info_scoped_handle icon_info_h(
                            gtk_icon_theme_lookup_icon(icon_theme_r->gobj(),
                                             "stock_about",
                                             16, GtkIconLookupFlags(0)));
    if (icon_info_h.get()) {

      // we could use Glib::convert_const_gchar_ptr_to_stdstring, but this is not
      // part of the publicly documented interface
      const char* icon_path_p = (const char*)gtk_icon_info_get_filename(icon_info_h.get());
      if (icon_path_p) {
      try {
        about_pixbuf_r = Gdk::Pixbuf::create_from_file(std::string(icon_path_p));
        have_about_icon = true;
      }
      catch (Gdk::PixbufError&) {
        write_error("Pixbuf error in MainWindow::MainWindow()\n");
      }
      }
    }
#endif

    if (!have_about_icon) {
      about_pixbuf_r = Gdk::Pixbuf::create_from_xpm_data(about_xpm);
    }

    Gtk::Image* image_p;
    using namespace Gtk::Menu_Helpers;
    // Create the file menu
    Gtk::Menu* file_menu_p = manage(new Gtk::Menu);
    // now fill the file menu
    MenuList& file_list = file_menu_p->items();

    image_p = manage(new Gtk::Image(Gtk::Stock::INDEX, Gtk::ICON_SIZE_MENU));
    file_list.push_back(ImageMenuElem(gettext("List _received faxes"), *image_p,
                              sigc::bind(sigc::mem_fun(*this, &MainWindow::fax_list_slot), 
                                     FaxListEnum::received)));
    image_p = manage(new Gtk::Image(Gtk::Stock::INDEX, Gtk::ICON_SIZE_MENU));
    file_list.push_back(ImageMenuElem(gettext("_List sent faxes"), *image_p,
                              sigc::bind(sigc::mem_fun(*this, &MainWindow::fax_list_slot), 
                                     FaxListEnum::sent)));
    file_list.push_back(SeparatorElem());
    file_list.push_back(MenuElem(gettext("Queued _faxes from socket"),
                         sigc::mem_fun(*this, &MainWindow::socket_list_slot)));
    // save this menu item for future use
    socket_list_item_p = &file_list.back();
    image_p = manage(new Gtk::Image(Gtk::Stock::OPEN, Gtk::ICON_SIZE_MENU));
    file_list.push_back(ImageMenuElem(gettext("_Enter single file"), *image_p,
                              sigc::mem_fun(*this, &MainWindow::get_file_slot)));
    // save this menu item for future use
    single_file_item_p = &file_list.back();
    image_p = manage(new Gtk::Image(Gtk::Stock::OPEN, Gtk::ICON_SIZE_MENU));
    file_list.push_back(ImageMenuElem(gettext("Enter _multiple files"), *image_p,
                              sigc::mem_fun(*this, &MainWindow::file_list_slot)));
    // save this menu item for future use
    multiple_file_item_p = &file_list.back();
    file_list.push_back(SeparatorElem());
    file_list.push_back(MenuElem(gettext("_Address book"),
                         sigc::mem_fun(*this, &MainWindow::addressbook_slot)));
    file_list.push_back(SeparatorElem());
    image_p = manage(new Gtk::Image(Gtk::Stock::PREFERENCES, Gtk::ICON_SIZE_MENU));
    file_list.push_back(ImageMenuElem(gettext("_Settings"), *image_p,
                              sigc::mem_fun(*this, &MainWindow::settings_slot)));
    file_list.push_back(SeparatorElem());
    image_p = manage(new Gtk::Image(Gtk::Stock::QUIT, Gtk::ICON_SIZE_MENU));
    file_list.push_back(ImageMenuElem(gettext("_Quit"), *image_p,
                              sigc::mem_fun(*this, &MainWindow::close_slot)));
    // Create the help menu
    Gtk::Menu* help_menu_p = manage(new Gtk::Menu);
    // now fill the help menu
    MenuList& help_list = help_menu_p->items();
    
    image_p = manage(new Gtk::Image(about_pixbuf_r));
    help_list.push_back(ImageMenuElem(gettext("About efax-_gtk"), *image_p,
                         sigc::bind(sigc::mem_fun(*this, &MainWindow::about_slot), true)));
    image_p = manage(new Gtk::Image(about_pixbuf_r));
    help_list.push_back(ImageMenuElem(gettext("About _efax"), *image_p,
                         sigc::bind(sigc::mem_fun(*this, &MainWindow::about_slot), false)));
    help_list.push_back(MenuElem(gettext("_Translations"),
                         sigc::mem_fun(*this, &MainWindow::translations_slot)));
    help_list.push_back(SeparatorElem());
    image_p = manage(new Gtk::Image(Gtk::Stock::HELP, Gtk::ICON_SIZE_MENU));
    help_list.push_back(ImageMenuElem(gettext("_Help"), *image_p,
                              sigc::mem_fun(*this, &MainWindow::helpfile_slot)));

    // now fill the menu bar
    MenuList& bar_list = menu_bar.items();
    bar_list.push_front(MenuElem(gettext("_Help"), *help_menu_p));
    bar_list.push_front(MenuElem(gettext("_File"), *file_menu_p));
  }

  window_box.pack_start(menu_bar, false, false);
  window_box.pack_start(win_table, true, true);
  window_box.pack_start(status_line, false, false);

  add(window_box);

  win_table.set_border_width(standard_size/3);

  std::vector<Gtk::Widget*> focus_chain;
  focus_chain.push_back(&file_entry_frame);
  focus_chain.push_back(&number_box);
  focus_chain.push_back(&send_button);
  focus_chain.push_back(&receive_answer_button);
  focus_chain.push_back(&receive_takeover_button);
  focus_chain.push_back(&receive_standby_button);
  focus_chain.push_back(&stop_button);
  win_table.set_focus_chain(focus_chain);

  drawing_area.set_size_request(standard_size, standard_size);

  efax_controller.ready_to_quit_notify.connect(sigc::mem_fun(*this, &MainWindow::quit_slot));

  Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::timer_event_handler), TIMER_INTERVAL);
  Glib::signal_io().connect(sigc::mem_fun(*this, &MainWindow::read_error_slot),
                      error_pipe.get_read_fd(), Glib::IO_IN);

  efax_controller.stdout_message.connect(sigc::mem_fun(text_window, &MessageText::write_black_slot));
  efax_controller.write_state.connect(sigc::mem_fun(status_line, &StatusLine::write_status));
  efax_controller.remove_from_socket_server_filelist.connect(sigc::mem_fun(*this,
                      &MainWindow::remove_from_socket_server_filelist));

  drawing_area.signal_expose_event().connect(sigc::mem_fun(*this, &MainWindow::draw_fax_from_socket_notifier), false);

  // we want to minimise the effect on efax, so make writing to the error pipe non-blocking
  error_pipe.make_write_non_block();

  set_icon(prog_config.window_icon_r);

  set_file_items_sensitive_slot();

  get_window_settings();
                
  if (filename) {
    // this will also call set_file_items_sensitive_slot() if the call to
    // get_window_settings has set socket_button active
    file_button.set_active(true);
    file_entry.set_text(filename);
    set_focus(number_entry);
  }
  else set_focus(single_file_button);

  // enable visibility events, so that the action when left clicking on the
  // tray icon is correct
  add_events(Gdk::VISIBILITY_NOTIFY_MASK);

  // now we will either show everything, or realise the window
  // and its children and keep it hidden in the system tray
  if (start_hidden) {
    realize();
    show_all_children(true);
    hide();
    Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::start_hidden_check_handler), 5000);
  }
 else show_all();

  if (!messages.empty()) {
    text_window.write_red_slot(messages.c_str());
    text_window.write_red_slot("\n\n");
  }

  // make sure that we have the faxin, faxout, and faxsent
  chdir(prog_config.working_dir.c_str());
  mkdir("faxin", S_IRUSR | S_IWUSR | S_IXUSR);
  mkdir("faxout", S_IRUSR | S_IWUSR | S_IXUSR);
  mkdir("faxsent", S_IRUSR | S_IWUSR | S_IXUSR);

  // set the working directory for the parent process
  chdir("faxin");

  // if there is no config file installed, then bring up the settings dialog
  if (!prog_config.found_rcfile) {
    // we don't want to use MainWindow::settings_slot(), or the absence of
    // a configuration file will be reported twice -- not a big deal, but ...
    // so pass true as the last parameter to skip detection of settings file
    SettingsDialog dialog(standard_size, *this, true);
    dialog.accepted.connect(sigc::mem_fun(*this, &MainWindow::settings_changed_slot));
    dialog.exec();
  }

  // this connects the stdout facilities of Socket_server to MessageText
  socket_server.stdout_message.connect(sigc::mem_fun(text_window, &MessageText::write_black_slot));

  // this connects the SigC object in Socket_server to a method in this class
  // which will update the Socket_list object (if such a dialog is displayed)
  socket_server.filelist_changed_notify.connect(sigc::mem_fun(*this,
                                 &MainWindow::socket_filelist_changed_notify_slot));

  // and this connects the SigC object which indicates a fax has been received
  // from the socket for sending to the method which will pop up a "fax to send"
  // notify dialog
  socket_server.fax_to_send_notify.connect(sigc::mem_fun(*this,
                                 &MainWindow::fax_to_send_notify_slot));

  // this connects the SigC object which indicates that a fax has been received
  // from the modem by the EfaxController object
  efax_controller.fax_received_notify.connect(sigc::mem_fun(*this,
                                        &MainWindow::fax_received_notify_slot));

  // start the socket server
  if (prog_config.sock_server && !prog_config.sock_server_port.empty()) {
    socket_server.start(std::string(prog_config.sock_server_port),
                  prog_config.other_sock_client_address);
  }

  tray_item.left_button_pressed.connect(sigc::mem_fun(*this,
                                &MainWindow::tray_icon_left_clicked_slot));
  tray_item.menu_item_chosen.connect(sigc::mem_fun(*this,
                                &MainWindow::tray_icon_menu_slot));
  tray_item.get_state.connect(sigc::mem_fun(efax_controller, &EfaxController::get_state));
  efax_controller.write_state.connect(sigc::mem_fun(tray_item, &TrayItem::set_tooltip_slot));

  // register our own button icon size for stock icons to match size of externally
  // defined icons used in this program
  Gtk::IconSize::register_new("EFAX_GTK_BUTTON_SIZE", 22, 22);

  // the notifed_fax pair is used by sendfax_slot() if no number is shown
  // to dial, for use if the option to send on an open connection without
  // dialling is refused and sendfax_slot() was called by a
  // SocketNotifyDialog::sendfax_sig signal, so that a SocketNotifyDialog
  // dialog can be brought up again for the user to have another chance to
  // choose what he/she wants to do.  When the second member of the
  // notified_fax pair is not 0, then that indicates that sendfax_slot()
  // was called by a SocketNotifyDialog object (it is normally set to 0 and
  // only holds another value when set in MainWindow::fax_to_send_dialog())
  notified_fax.second = 0;

  // if the -r option has been chosen, start the program in Receive Standby mode
  if (start_in_standby) receive_slot(EfaxController::receive_standby);
}

MainWindow::~MainWindow(void) {
  save_window_settings();
  delete write_error_mutex_p;
}


bool MainWindow::on_key_press_event(GdkEventKey* event_p) {
  if (event_p->keyval == GDK_F1) helpfile_slot();
  else Gtk::Window::on_key_press_event(event_p);
  return true; // processing stops here
}

void MainWindow::get_window_settings(void) {

  int width = 0;
  int height = 0;
  int socket_val = 0;

  std::string file_name(prog_config.working_dir + "/" MAINWIN_SAVE_FILE);
  std::ifstream file(file_name.c_str(), std::ios::in);

  if (!file) write_error("Can't get mainwindow settings from file\n");
  else {
    file >> width >> height >> socket_val;

    if (width > 0 && height > 0) {
      socket_button.set_active(socket_val);
      set_default_size(width, height);
    }
  }
}

void MainWindow::save_window_settings(void) {

  std::string file_name(prog_config.working_dir + "/" MAINWIN_SAVE_FILE);
  std::ofstream file(file_name.c_str(), std::ios::out);

  if (!file) write_error("Can't save mainwindow settings to file\n");
  else {

    int width;
    int height;
    get_size(width, height);

    int socket_val = socket_button.get_active();
    
    file << width << ' ' << height << ' ' << socket_val << std::endl;
  }
}

bool MainWindow::read_error_slot(Glib::IOCondition) {

  char pipe_buffer[PIPE_BUF + 1];
  ssize_t result;

  while ((result = error_pipe.read(pipe_buffer, PIPE_BUF)) > 0) {
    pipe_buffer[result] = 0;
    text_window.write_red_slot(pipe_buffer);
  }
  return true; // retain connection for further reads
}

bool MainWindow::on_delete_event(GdkEventAny* event_p) {
  if (tray_item.is_embedded()) hide();
  else close_slot();
  return true;
}

void MainWindow::close_slot(void) {

  // close down socket server
  socket_server.stop();
  // make sure we close down efax() if it is active
  // (the EfaxController::ready_to_quit_notify signal is connected to
  // quit_slot() below and will end the main program loop once any efax
  // session running has been dealt with)
  efax_controller.efax_closedown();
}

void MainWindow::quit_slot(void) {

  Gtk::Main::quit();
}

void MainWindow::sendfax_slot(void) {

  // Fax_item is defined in efax_controller.h
  Fax_item fax_item;

  try {
    fax_item.number = Glib::locale_from_utf8(number_entry.get_text());
  }
  catch (Glib::ConvertError&) {
    write_error("UTF-8 conversion error in sendfax_slot()\n");
    beep();
    return;
  }

  // eliminate leading or trailing spaces so that we can check for an empty string
  strip(fax_item.number);

  if (fax_item.number.empty()) {
    PromptDialog dialog(gettext("No fax number specified.  Do you want to send the fax "
                        "on an open connection?"),
                  gettext("Telephone number"), standard_size, *this);
    if (!dialog.exec()) {
      if (notified_fax.second) {
      // we must have got here by a SocketNotifyDialog::sendfax_sig signal
      set_files_slot("");
      selected_socket_list_file = "";
      fax_to_send_dialog(notified_fax);
      }
      return;
    }
  }
  notified_fax.second = 0;

  present();

  fax_item.is_socket_file = false;

  std::string files;
  try {
    files = Glib::filename_from_utf8(file_entry.get_text());
  }
  catch (Glib::ConvertError&) {
    write_error("UTF-8 conversion error in sendfax_slot()\n");
    beep();
    return;
  }

  const char* separators = ",;";
  // split the files string into separate file names
  std::string::size_type pos1, pos2;
  pos1 = files.find_first_not_of(separators);
  while (pos1 != std::string::npos) {
    pos2 = files.find_first_of(separators, pos1);
    if (pos2 != std::string::npos) {
      std::string file_item(files.substr(pos1, pos2 - pos1));
      strip(file_item);
      fax_item.file_list.push_back(file_item);
      pos1 = files.find_first_not_of(separators, pos2);
    }
    else {
      std::string file_item(files.substr(pos1));
      strip(file_item);
      fax_item.file_list.push_back(file_item);
      pos1 = std::string::npos;
    }
  }  

  if (!prog_config.found_rcfile) {
    text_window.write_red_slot("Can't send fax -- no efax-gtkrc configuration file found\n\n");
    beep();
  }
  
  else if (prog_config.lock_file.empty()) { // if there is no lock file, then it means no
                                            // serial device has been specified (the lock
                                            // file dir defaults to /var/lock)
    text_window.write_red_slot("Can't send fax -- no valid serial device specified\n\n");
    beep();
  }

  else if (fax_item.file_list.empty()) beep();

  else {

    // if this is a fax received from the socket and entered via the socket faxes
    // list, then "*** " will form the first part of the "file" displayed in the file
    // entry box - so test for it to see if we are sending a file received via the
    // socket rather than a regular file
    if (fax_item.file_list.size() > 0
      && fax_item.file_list[0].substr(0,4) == std::string("*** ")) { 

      fax_item.file_list.clear();
      fax_item.file_list.push_back(selected_socket_list_file);
      fax_item.is_socket_file = true;
    }

    efax_controller.sendfax(fax_item);
  }
}

void MainWindow::receive_slot(EfaxController::State mode) {

  if (!prog_config.found_rcfile) {
    text_window.write_red_slot("Can't receive fax -- no efax-gtkrc configuration file found\n\n");
    beep();
  }
  
  else if (prog_config.lock_file.empty()) { // if there is no lock file, then it means no
                                            // serial device has been specified (the lock
                                            // file dir defaults to /var/lock)
    text_window.write_red_slot("Can't receive fax -- no valid serial device specified\n\n");
    beep();
  }

  else efax_controller.receive(mode);
}

bool MainWindow::timer_event_handler(void) {

  if (usr2_flag) {
    usr2_flag = false;
    hide(); // so window will come up in another workspace if necessary
    present();
  }
  if (close_flag) {
    close_slot(); // we must have picked up an external kill signal
                  // so we need an orderly close down
    close_flag = false;
  }
  efax_controller.timer_event();
  return true; // we want a multi-shot timer
}

bool MainWindow::start_hidden_check_handler(void) {

  if (!tray_item.is_embedded()) {
    // the user has goofed - he has set the program to start hidden in the system tray
    // but has no system tray running!
    write_error(gettext("The program has been started with the -s option but there is no system tray!\n"));
    present();
  }
  return false; // this only fires once
}

void MainWindow::get_file_slot(void) {
  FileReadSelectDialog file_dialog(standard_size, false, *this);
  std::vector<Glib::ustring> file_result = file_dialog.exec();
  if (!file_result.empty()) {
    set_files_slot(file_result[0]);
    number_entry.grab_focus();
  }
}

void MainWindow::set_files_slot(const Glib::ustring& files) {
  file_entry.set_text(files);
}

void MainWindow::fax_list_slot(FaxListEnum::Mode mode) {

  if (mode == FaxListEnum::received) {
    if (!FaxListDialog::get_is_fax_received_list()) {
      received_fax_list_p = new FaxListDialog(mode, standard_size);
    }
    else {
      received_fax_list_p->refresh_slot();
      received_fax_list_p->present();
    }
  }
  else if (mode == FaxListEnum::sent) {
    if (!FaxListDialog::get_is_fax_sent_list()) {
      sent_fax_list_p = new FaxListDialog(mode, standard_size);
    }
    else {
      sent_fax_list_p->refresh_slot();
      sent_fax_list_p->present();
    }
  }
  // there is no memory leak -- FaxListDialog is modeless and will delete its own memory
  // when it is closed
}

void MainWindow::socket_list_slot(void) {

  // this method will launch the dialog listing queued faxes received from
  // the socket_server (a SocketListDialog object)

  if (!SocketListDialog::get_is_socket_list()) {

    // get the filenames pair - the mutex lock will automatically release when filenames_pair (and
    // so the shared pointer holding the lock) goes out of scope at the end of the if block (we want to keep
    // the shared_ptr alive until after we have set up the connection to update the socket list
    std::pair<Shared_ptr<FilenamesList>, Shared_ptr<Glib::Mutex::Lock> > filenames_pair(socket_server.get_filenames());
    socket_list_p = new SocketListDialog(filenames_pair, standard_size);
    // now connect up the dialog to relevant slots and signals
    update_socket_list_connection = update_socket_list.connect(sigc::mem_fun(*socket_list_p, &SocketListDialog::set_socket_list_rows));
    socket_dialog_connection = close_socket_list_dialog.connect(sigc::mem_fun(*socket_list_p, &SocketListDialog::close_slot));
    socket_list_p->selected_fax.connect(sigc::mem_fun(*this,
                         &MainWindow::enter_socket_file_slot));
    socket_list_p->remove_from_socket_server_filelist.connect(sigc::mem_fun(*this,
                         &MainWindow::remove_from_socket_server_filelist));
    socket_list_p->dialog_closed.connect(sigc::mem_fun(*this,
                         &MainWindow::socket_filelist_closed_slot));

    // there is no memory leak -- SocketListDialog is modeless and will delete its own memory
    // when it is closed
  }
  else socket_list_p->present();
}

void MainWindow::socket_filelist_changed_notify_slot(void) {

  // this slot is connected to the socket_server object, and is called
  // by a Glib::Dispatcher object (filelist_changed_notify) whenever the
  // socket server gets a new fax to send

  // update_socket_list is a signal, connected to SocketListDialog::set_socket_list_rows
  // and it will enter the new fax file received from the socket server into the list of
  // queued faxes to be sent maintained by the SocketListDialog object

  update_socket_list(socket_server.get_filenames());

  draw_fax_from_socket_notifier(0);
}

void MainWindow::socket_filelist_closed_slot(void) {

  update_socket_list_connection.disconnect();
  socket_dialog_connection.disconnect();

}

void MainWindow::fax_to_send_notify_slot() {
  fax_to_send_dialog(socket_server.get_fax_to_send());
}

void MainWindow::fax_to_send_dialog(const std::pair<std::string, unsigned int>& fax_pair) {

  if (prog_config.sock_popup) {

    if (is_sensitive()
      && (efax_controller.get_state() == EfaxController::inactive
          || (efax_controller.get_state() == EfaxController::receive_standby
            && !efax_controller.is_receiving_fax()))) {
      notified_fax = fax_pair;
      SocketNotifyDialog* dialog_p = new SocketNotifyDialog(standard_size, fax_pair,
                                              *this, false); // not modal
      dialog_p->fax_name_sig.connect(sigc::mem_fun(*this,
                                    &MainWindow::enter_socket_file_slot));
      dialog_p->fax_number_sig.connect(sigc::mem_fun(*this,
                                      &MainWindow::set_number_slot));
      dialog_p->sendfax_sig.connect(sigc::mem_fun(*this,
                                     &MainWindow::sendfax_slot));
      dialog_p->present();

    }

    else {
      InfoDialog* dialog_p = new InfoDialog(gettext("A print job has been received on socket"),
                                  gettext("efax-gtk socket"),
                                  standard_size, Gtk::MESSAGE_INFO,
                                  *this, !is_sensitive());
      dialog_p->present();
    }
  }
  // there is no memory leak - the exec() method has not been called so the dialog
  // is self-owning and will delete itself when it is closed
}

void MainWindow::fax_received_notify_slot(void) {

  if (prog_config.fax_received_exec
      && !prog_config.fax_received_prog.empty()) {

    // get the program name for the exec() call below (because this is a
    // multi-threaded program, we must do this before fork()ing because
    // we use functions to get the argument which is not async-signal-safe)
    std::string cmd;
    try {
      cmd.assign(Glib::filename_from_utf8(prog_config.fax_received_prog));
    }
    catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in MainWindow::fax_received_notify_slot()\n");
      cmd.assign(prog_config.fax_received_prog);
    }
    // we can use the raw output of cmd.c_str() here - it will remain valid at the execlp() call
    const char* prog_name = cmd.c_str();

    pid_t pid = fork();
    
    if (pid == -1) {
      write_error("Fork error - exiting\n");
      std::exit(FORK_ERROR);
    }
    if (!pid) {  // child process - as soon as everything is set up we are going to do an exec()
      
      // now we have forked, we can connect MainWindow::error_pipe to stderr
      connect_to_stderr();

      execlp (prog_name, prog_name, prog_config.receive_dirname, 0);

      // if we reached this point, then the execlp() call must have failed
      // report error and end process - use _exit() and not exit()
      write_error("Can't find the program to execute when a fax is received.\n"
              "Please check your installation and the PATH environmental variable\n");
      _exit(EXEC_ERROR); 
    } // end of child process
  }

  if (prog_config.fax_received_popup) {
    InfoDialog* dialog_p = new InfoDialog(gettext("A fax has been received by efax-gtk"), 
                                gettext("efax-gtk: fax received"),
                                standard_size, Gtk::MESSAGE_INFO,
                                *this, !is_sensitive());
    dialog_p->present();
    // there is no memory leak - the exec() method has not been called so the dialog
    // is self-owning and will delete itself when it is closed
  }
}

void MainWindow::enter_socket_file_slot(const std::pair<Glib::ustring, std::string>& fax_to_insert) {

  // this slot is connected to the SocketListDialog::selected_fax signal and is
  // called (that is, whenever the send fax button is pressed in a dialog object of
  // that class)

  // it is also connected to the SocketNotifyDialog::fax_name_sig signal, and is called
  // when the send fax button is pressed in a dialog object of that class

  // fax_to_insert.first is the fax label for the fax selected in the SocketListDialog object
  // and fax_to_insert.second is the real (temporary) file name obtained from mkstemp()
 
  // now show the fax label in the "Files to fax" box

  // in case we are called from SocketNotifyDialog::fax_name_sig (when it
  // is possible that the file selection buttons will be active) make
  // sure that the socket button is active
  // (we do not have to close either file selection dialogs, because if
  // they were open when fax_to_send_notify_slot() was called then
  // it would not bring up a SocketNotifyDialog object, and if a file selection
  // dialog was opened up after a SocketNotifyDialog object was brought up,
  // it would render the SocketNotifyDialog inoperative by making its parent
  // (this object) insensitive, so we could not reach here until it is closed)
  socket_button.set_active(true);

  // now enter the particulars of the selected file in MainWindow::selected_socket_list_file
  // (we will use this later to send the fax in MainWindow::sendfax_slot())
  selected_socket_list_file = fax_to_insert.second;

  Glib::ustring socket_file_label("*** ");
  socket_file_label += fax_to_insert.first;
  socket_file_label += " ***";
  set_files_slot(socket_file_label);
}

void MainWindow::remove_from_socket_server_filelist(const std::string& file) {
  socket_server.remove_file(file);
  // if we have stored the file name for sending, delete it
  // and remove from the "Fax to send" box
  if (file == selected_socket_list_file) {
    selected_socket_list_file = "";
    set_files_slot("");
  }
}

void MainWindow::set_file_items_sensitive_slot(void) {

  single_file_item_p->set_sensitive(true);
  multiple_file_item_p->set_sensitive(true);
  single_file_button.set_sensitive(true);
  multiple_file_button.set_sensitive(true);
  file_entry.set_editable(true);

  socket_list_item_p->set_sensitive(false);
  socket_list_button.set_sensitive(false);

  set_focus(single_file_button);

  // we now need to close the socket list dialog (if it is open), empty
  // the std::string object holding particulars of the currently selected
  // print job received from the socket server and any print job in the
  // Gtk::Entry object for that print job
  close_socket_list_dialog();
  selected_socket_list_file = "";
  set_files_slot("");
}

void MainWindow::set_socket_items_sensitive_slot(void) {

  single_file_item_p->set_sensitive(false);
  multiple_file_item_p->set_sensitive(false);
  single_file_button.set_sensitive(false);
  multiple_file_button.set_sensitive(false);
  file_entry.set_editable(false);

  socket_list_item_p->set_sensitive(true);
  socket_list_button.set_sensitive(true);

  set_focus(socket_list_button);
}

void MainWindow::addressbook_slot(void) {

  if (!AddressBook::get_is_address_list()) {
    AddressBook* dialog_p = new AddressBook(standard_size, *this);

    dialog_p->accepted.connect(sigc::mem_fun(*this, &MainWindow::set_number_slot));
    // there is no memory leak -- AddressBook will delete its own memory
    // when it is closed
  }
}

void MainWindow::file_list_slot(void) {

  FileListDialog* dialog_p = new FileListDialog(standard_size, *this);

  dialog_p->accepted.connect(sigc::mem_fun(*this, &MainWindow::set_files_slot));
  // there is no memory leak -- FaxListDialog is modeless and will delete its own memory
  // when it is closed
}

void MainWindow::settings_slot(void) {

  if (efax_controller.get_state() != EfaxController::inactive) {
    InfoDialog* dialog_p;
    Glib::ustring message(gettext("Can't change settings unless "
                          "the program is inactive\n"
                          "Press the Stop button to make it inactive"));
    dialog_p = new InfoDialog(message, "Change settings", standard_size, Gtk::MESSAGE_WARNING, *this);

    // there is no memory leak -- exec() was not called so InfoDialog
    // will delete its own memory when it is closed
  }

  else {
    SettingsDialog* dialog_p = new SettingsDialog(standard_size, *this);

    dialog_p->accepted.connect(sigc::mem_fun(*this, &MainWindow::settings_changed_slot));
    // there is no memory leak -- SettingsDialog will delete its own memory
    // when it is closed
  }
}

void MainWindow::settings_changed_slot(const Glib::ustring& messages) {
  text_window.reset_logfile();
  if (!messages.empty()) {
    try {
      text_window.write_red_slot(Glib::locale_from_utf8(messages).c_str());
      text_window.write_red_slot("\n");
    }
    catch  (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in MainWindow::settings_changed_slot()\n");
    }
  }
  if ((!prog_config.sock_server || prog_config.sock_server_port.empty())
      && socket_server.is_server_running()) {
    socket_server.stop();
  }
  else if (prog_config.sock_server
      && !prog_config.sock_server_port.empty()
      && !socket_server.is_server_running()) {
    socket_server.start(std::string(prog_config.sock_server_port),
                  prog_config.other_sock_client_address);
  }
  else if (socket_server.get_port() != std::string(prog_config.sock_server_port)
         || socket_server.get_other_sock_client_address() != prog_config.other_sock_client_address) {
    socket_server.stop();
    socket_server.start(std::string(prog_config.sock_server_port),
                  prog_config.other_sock_client_address);
  }
}

void MainWindow::helpfile_slot(void) {

  if (!HelpDialog::get_is_helpfile()) {
    helpfile_p = new HelpDialog(standard_size);
  }

  else helpfile_p->present();

  // there is no memory leak -- HelpDialog is modeless and will delete its own memory
  // when it is closed
}

void MainWindow::set_number_slot(const Glib::ustring& number) {

  number_entry.set_text(number);
  if (file_entry.get_text_length()) {
    // reset this window as sensitive to make grab_focus() work
    set_sensitive(true);
    send_button.grab_focus();
  }
}

void MainWindow::about_slot(bool efax_gtk) {
  InfoDialog* dialog_p;

  if (efax_gtk) {
    Glib::ustring message("efax-gtk-" VERSION "\n");
    message += gettext("Copyright (C) 2001 - 2005 Chris Vine\n\n"
                   "This program is released under the "
                   "GNU General Public License, version 2");
    dialog_p = new InfoDialog(message, gettext("About efax-gtk"),
                        standard_size, Gtk::MESSAGE_INFO, *this);
  }
  else {
    Glib::ustring message = gettext("This program is a front end for efax.\n"
                            "efax is a program released under the "
                            "GNU General Public License, version 2 by Ed Casas.\n\n"
                            "The copyright to efax is held by Ed Casas "
                            "(the copyright to this program is held by Chris Vine)");
    dialog_p = new InfoDialog(message, gettext("About efax"),
                        standard_size, Gtk::MESSAGE_INFO, *this);
  }

  // there is no memory leak -- exec() was not called so InfoDialog
  // will delete its own memory when it is closed
}

void MainWindow::translations_slot(void) {

  Glib::ustring message = gettext("Italian - Luca De Rugeriis\n");
  message += gettext("Polish - Pawel Suwinski\n");
  message += gettext("Bulgarian - Zdravko Nikolov\n");
  message += gettext("Russian - Pavel Vainerman\n");
  message += gettext("Hebrew - Assaf Gillat\n");
  message += gettext("Greek - Hellenic Linux Users Group");
  new InfoDialog(message, gettext("efax-gtk: Translations"),
             standard_size, Gtk::MESSAGE_INFO, *this);

  // there is no memory leak -- InfoDialog is modeless and will delete its own memory
  // when it is closed
}

void MainWindow::get_longest_button_text(void) {
  std::vector<Glib::ustring> text_vec;
  text_vec.push_back(gettext("Single file"));
  text_vec.push_back(gettext("Multiple files"));
  text_vec.push_back(gettext("Socket list"));
  text_vec.push_back(gettext("Tel number: "));
  text_vec.push_back(gettext("Send fax"));
  text_vec.push_back(gettext("Answer call"));
  text_vec.push_back(gettext("Take over call"));
  text_vec.push_back(gettext("Standby"));
  text_vec.push_back(gettext("Stop"));

  Gtk::Button button;
  std::vector<Glib::ustring>::const_iterator temp_iter;
  std::vector<Glib::ustring>::const_iterator max_width_iter;
  int width;
  int height;
  int max_width;
  for (temp_iter = text_vec.begin(), max_width_iter = text_vec.begin(),
       width = 0, height = 0, max_width = 0;
       temp_iter != text_vec.end(); ++temp_iter) {

    button.create_pango_layout(*temp_iter)->get_pixel_size(width, height);

    if (width > max_width) {
      max_width = width;
      max_width_iter = temp_iter;
    }
  }
  max_text = *max_width_iter;
}

bool MainWindow::on_visibility_notify_event(GdkEventVisibility* event_p) {

  if(event_p->state == GDK_VISIBILITY_FULLY_OBSCURED) obscured = true;
  else obscured = false;
  return Gtk::Window::on_visibility_notify_event(event_p);
}

bool MainWindow::on_window_state_event(GdkEventWindowState* event_p) {

  if(event_p->new_window_state == GDK_WINDOW_STATE_ICONIFIED) minimised = true;
  else minimised = false;
  return Gtk::Window::on_window_state_event(event_p);
}

void MainWindow::tray_icon_left_clicked_slot(void) {

  // test Gtk::Window::is_visible() as well as the 'obscured' variable - if we call
  // hide() on the window it does not count as a visibility notify event!
  if (is_visible() && !obscured && !minimised && is_sensitive()) hide();
  else present();
}

void MainWindow::tray_icon_menu_slot(int item) {

  switch(item) {

  case TrayItem::list_received_faxes:
    fax_list_slot(FaxListEnum::received);
    break;
  case TrayItem::list_sent_faxes:
    fax_list_slot(FaxListEnum::sent);
    break;
  case TrayItem::receive_answer:
    receive_slot(EfaxController::receive_answer);
    break;
  case TrayItem::receive_takeover:
    receive_slot(EfaxController::receive_takeover);
    break;
  case TrayItem::receive_standby:
    receive_slot(EfaxController::receive_standby);
    break;
  case TrayItem::stop:
    efax_controller.stop_slot();
    break;
  case TrayItem::quit:
    close_slot();
    break;
  default:
    break;
  }
}

void MainWindow::on_style_changed(const Glib::RefPtr<Gtk::Style>& prev_style) {

  // before we set the buttons, do anything else necessary with the new style
  Gtk::Window::on_style_changed(prev_style);

  //now set the buttons in MainWindow
  int width = 0;
  int height = 0;
  Gtk::Button button;

  button.set_style(get_style());
  button.create_pango_layout(max_text)->get_pixel_size(width, height);

  // add padding
  width += 18;
  height += 12;

  // have some sensible minimum width and height if a very small font chosen
  const int min_width = standard_size * 4;
  const int min_height = 30;
  if (width < min_width) width = min_width;
  if (height < min_height) height = min_height;

  single_file_button.set_size_request(width, height);
  multiple_file_button.set_size_request(width, height);
  socket_list_button.set_size_request(width, height);
  number_button.set_size_request(width, height);
  send_button.set_size_request(width, height);
  receive_answer_button.set_size_request(width, height);
  receive_takeover_button.set_size_request(width, height);
  receive_standby_button.set_size_request(width, height);
  stop_button.set_size_request(width, height);

  status_line.set_status_line(get_style(), height - 6);
}

bool MainWindow::draw_fax_from_socket_notifier(GdkEventExpose*) {

/* This method displays the "red circle" indicator which shows a fax
   file received from the socket is awaiting sending.  It is is called
   in the following circumstances:

   - it is connected to the signal_expose_event signal of
     MainWindow::drawing_area and is also called in
     MainWindow::socket_filelist_changed_notify_slot()

   - MainWindow::socket_filelist_changed_notify_slot() is connected to
     the Socket_server::filelist_changed_notify signal - the
     Socket_server::filelist_changed_notify signal is emitted by
     Socket_server:read_socket() when a new fax file to be sent
     is received via the socket and also by
     Socket_server::remove_file()

   - Socket_server::remove_file() is called by
     MainWindow::remove_from_socket_server_filelist()

   - MainWindow::remove_from_socket_server_filelist() is connected to
     the EfaxController::remove_from_socket_server_filelist signal and
     to SocketList::remove_from_socket_server_filelist()

   - the EfaxController::remove_from_socket_server_filelist signal is
     emitted by EfaxController::timer_event() when a fax derived from
     the socket list is successfully sent

   - the Socket_server::remove_from_socket_server_filelist signal is
     emitted by SocketServer::remove_file()

   - Socket_server::remove_file() is connected to the
     PromptDialog::accepted signal (emitted when a user elects to
     delete a queued fax from the list of files received for faxing
     from the socket)
*/

  if (drawing_area.is_realized()) { // if we started hidden in the system tray
                                    // the drawing area may not yet be realised
    Glib::RefPtr<Gdk::Window> window_r = drawing_area.get_window();

    if (!socket_server.get_filenames().first->empty()) {

      Gdk::Color red;
      red.set_rgb_p(0.7, 0, 0);
      Glib::RefPtr<Gdk::Colormap> colourmap_r = Gtk::Widget::get_default_colormap();
      colourmap_r->alloc_color(red);
      Glib::RefPtr<Gdk::GC> red_graphics_context_r = Gdk::GC::create(window_r);
      red_graphics_context_r->set_foreground(red);

      Glib::RefPtr<Gdk::GC> white_graphics_context_r = drawing_area.get_style()->get_white_gc();

      window_r->draw_arc(white_graphics_context_r,
                   true, 6, 6,
                   standard_size - 12, standard_size - 12,
                   0, 64 * 360);
    
      window_r->draw_arc(red_graphics_context_r,
                   true, 8, 8,
                   standard_size - 16, standard_size - 16,
                   0, 64 * 360);
    }

    else {
      Glib::RefPtr<Gdk::GC> background_graphics_context_r =
                          drawing_area.get_style()->get_bg_gc(drawing_area.get_state());
      window_r->draw_rectangle(background_graphics_context_r,
                         true, 0, 0,
                         standard_size, standard_size);
    }
  }
  // processing stops here
  return true;
}

void MainWindow::strip(std::string& text) {

  // erase any trailing space or tab
  while (!text.empty() && text.find_last_of(" \t") == text.size() - 1) {
    text.resize(text.size() - 1);
  }
  // erase any leading space or tab
  while (!text.empty() && (text[0] == ' ' || text[0] == '\t')) {
    text.erase(0, 1);
  }
}

MessageText::MessageText(void) {

  text.set_wrap_mode(Gtk::WRAP_WORD);
  text.set_editable(false);
  text.unset_flags(Gtk::CAN_FOCUS);
  add(text);
  set_shadow_type(Gtk::SHADOW_IN);
  set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);

  Gdk::Color red;
  red.set_rgb_p(0.8, 0, 0);
  Glib::RefPtr<Gdk::Colormap> colourmap_r = Gtk::Widget::get_default_colormap();
  colourmap_r->alloc_color(red);
  red_tag_r = Gtk::TextTag::create();
  red_tag_r->property_foreground_gdk() = red;
  text.get_buffer()->get_tag_table()->add(red_tag_r);

  // save the current working directory before MainWindow::MainWindow can get
  // at it, in case it is needed in future by reset_logfile

#ifndef PATH_MAX
#define PATH_MAX 1023
#endif
  char* path_buffer;
  int count;
  void* success;
  for (count = 1, success = 0; !success && count < 10; count++) {
    path_buffer = new char[(PATH_MAX * count) + 1];
    success = getcwd(path_buffer, PATH_MAX * count);
    if (success) {
      starting_dirname = path_buffer;
      starting_dirname += '/';
    }
    delete[] path_buffer;
  }

  // now open the log file if required
  if (!prog_config.logfile_name.empty()) {

    try {
      std::string temp(Glib::filename_from_utf8(prog_config.logfile_name));
      if (temp[0] != '/') temp.insert(0, starting_dirname); // provide an absolute path name
      logfile.open(temp.c_str(), std::ios::app | std::ios::out);
      if (!logfile) {
      {
        // lock the Prog_config object while we modify it
        Glib::Mutex::Lock lock(*prog_config.mutex_p);
        prog_config.logfile_name = "";
      }
      std::string message("Can't open logfile ");
      message += temp;
      message += '\n';
      write_red_slot(message.c_str());
      }
      else {
      struct std::tm* time_p;
      std::time_t time_count;
      std::time(&time_count);
      time_p = std::localtime(&time_count);
      
      const char date_description_format[] = "%H%M %Z %d %b %Y";
      const int max_description_datesize = 126;
      char date_description[max_description_datesize];
      std::strftime(date_description, max_description_datesize, date_description_format, time_p);

      logfile << "\n\n***********************\n"
                 "Beginning fax log at " << date_description << '\n' << std::endl;
      // set a timeout to flush every one minute
      timer_slot_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this,
                                         &MessageText::flush_logfile_timer_slot), LOGFILE_TIMER_INTERVAL);
      }

      // save the logfile name in the local version of logfile_name 
      // in case reset_logfile() is called later so we can test whether it has changed
      logfile_name = Glib::filename_from_utf8(prog_config.logfile_name);
    }
    catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in MessageText::MessageText()\n");
      beep();
    }
  }
}

MessageText::~MessageText(void) {
  struct std::tm* time_p;
  std::time_t time_count;
  std::time(&time_count);
  time_p = std::localtime(&time_count);
  
  const char date_description_format[] = "%H%M %Z %d %b %Y";
  const int max_description_datesize = 126;
  char date_description[max_description_datesize];
  std::strftime(date_description, max_description_datesize, date_description_format, time_p);
  
  logfile << "\n\nEnding fax log at " << date_description << '\n'
        << "***********************\n" << std::endl;
  logfile.close();
  logfile.clear();
}

void MessageText::reset_logfile(void) {

  // check pre-conditions
  try {
    if (logfile_name == Glib::filename_from_utf8(prog_config.logfile_name)) return; // no change!
  }
  catch (Glib::ConvertError&) {
    write_error("UTF-8 conversion error in MessageText::reset_logfile()\n");
    beep();
    return;
  }

  // proceed
  // first close the log file if required
  if (!logfile_name.empty()) {

    struct std::tm* time_p;
    std::time_t time_count;
    std::time(&time_count);
    time_p = std::localtime(&time_count);
  
    const char date_description_format[] = "%H%M %Z %d %b %Y";
    const int max_description_datesize = 126;
    char date_description[max_description_datesize];
    std::strftime(date_description, max_description_datesize, date_description_format, time_p);
  
    logfile << "\n\nEnding fax log at " << date_description << '\n'
          << "***********************\n" << std::endl;
    logfile.close();
    logfile.clear();
    // and now disconnect the old timer connection
    timer_slot_connection.disconnect();
  }

  // now open the new log file if required
  if (!prog_config.logfile_name.empty()) {

    // we do not need to check the conversion here - if prog_config.logfile_name
    // is not valid UTF-8, then it would have already thrown above
    std::string temp(Glib::filename_from_utf8(prog_config.logfile_name));
    if (temp[0] != '/') temp.insert(0, starting_dirname); // provide an absolute path name

    logfile.open(temp.c_str(), std::ios::app | std::ios::out);
    if (!logfile) {
      {
      // lock the Prog_config object while we modify it
      Glib::Mutex::Lock lock(*prog_config.mutex_p);
      prog_config.logfile_name = "";
      }
      std::string message("Can't open logfile ");
      message += temp;
      message += '\n';
      write_red_slot(message.c_str());
    }
    else {
      struct std::tm* time_p;
      std::time_t time_count;
      std::time(&time_count);
      time_p = std::localtime(&time_count);
      
      const char date_description_format[] = "%H%M %Z %d %b %Y";
      const int max_description_datesize = 126;
      char date_description[max_description_datesize];
      std::strftime(date_description, max_description_datesize, date_description_format, time_p);

      logfile << "\n\n***********************\n"
                 "Beginning fax log at " << date_description << '\n' << std::endl;
      // set a timeout to flush every one minute
      timer_slot_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this,
                                         &MessageText::flush_logfile_timer_slot), LOGFILE_TIMER_INTERVAL);
    }
  }
  // save the logfile name in the local version of logfile_name 
  // in case reset_logfile() is called again later
  // we don't need to check the conversion here, as if it was invalid it
  // would have thrown above
  logfile_name = Glib::filename_from_utf8(prog_config.logfile_name);
}

void MessageText::cleanify(std::string& message) {

  std::string::size_type chunk_start = 0;
  std::string::size_type chunk_end;
  while (chunk_start != std::string::npos) {
    std::string::size_type chunk_size;
    chunk_end = message.find('\n', chunk_start);

    // include in the search chunk any '\n' found
    if (chunk_end + 1 >= message.size()) chunk_end = std::string::npos;
    else if (chunk_end != std::string::npos) chunk_end++;

    // now search the relevant string/substring and erase from message
    if (chunk_end == std::string::npos) chunk_size = message.size() - chunk_start;
    else chunk_size = chunk_end - chunk_start;
    if (message.substr(chunk_start, chunk_size).find(" Copyright ") != std::string::npos
      || message.substr(chunk_start, chunk_size).find(" compiled ") != std::string::npos
      || message.substr(chunk_start, chunk_size).find("Error: terminating on signal 15") != std::string::npos) {
      message.erase(chunk_start, chunk_size);
      // if we have erased this chunk then we don't want to reset chunk_start
      // unless we have finished examining message
      if (chunk_end == std::string::npos) chunk_start = std::string::npos;
    }
    // if we didn't erase the last chunk, then we need to reset
    // chunk_start to beginning of next substring (or to string::npos)
    else chunk_start = chunk_end;
  }
}

void MessageText::write_black_slot(const char* message) {
  std::string temp(message);
  cleanify(temp);

  bool scrolling = false;
  if (get_vadjustment()->get_value() >=
      (get_vadjustment()->get_upper() - get_vadjustment()->get_page_size() - 1e-12)) {
    scrolling = true;
  }

  Glib::RefPtr<Gtk::TextBuffer> buffer_r = text.get_buffer();
  buffer_r->insert(buffer_r->end(), temp);
  if (scrolling) text.scroll_to_mark(buffer_r->create_mark("end", buffer_r->end()), 0.0);

  // now relay to standard output
  ssize_t result;
  ssize_t written = 0;
  ssize_t to_write_count = temp.size();
  const char* write_text = temp.c_str();
  do {
    result = ::write(1, write_text + written, to_write_count);
    if (result > 0) {
      written += result;
      to_write_count -= result;
    }
  } while (to_write_count && (result != -1 || errno == EINTR));

  // now log to file if required
  if (!prog_config.logfile_name.empty()) logfile << temp;
}

void MessageText::write_red_slot(const char* message) {
  std::string temp(message);
  cleanify(temp);

  bool scrolling = false;
  if (get_vadjustment()->get_value() >=
      (get_vadjustment()->get_upper() - get_vadjustment()->get_page_size() - 1e-12)) {
    scrolling = true;
  }

  Glib::RefPtr<Gtk::TextBuffer> buffer_r = text.get_buffer();
  buffer_r->insert_with_tag(buffer_r->end(), temp, red_tag_r);
  if (scrolling) text.scroll_to_mark(buffer_r->create_mark("end", buffer_r->end()), 0.0);

  // now relay to standard error
  ssize_t result;
  ssize_t written = 0;
  ssize_t to_write_count = temp.size();
  const char* write_text = temp.c_str();
  do {
    result = ::write(2, write_text + written, to_write_count);
    if (result > 0) {
      written += result;
      to_write_count -= result;
    }
  } while (to_write_count && (result != -1 || errno == EINTR));

  // now log to file if required
  if (!prog_config.logfile_name.empty()) logfile << temp;
}

bool MessageText::flush_logfile_timer_slot(void) {
  logfile.flush();
  return true; // we want a multi-shot timer
}

StatusLine::StatusLine(const int size): 
                                       Gtk::HBox(false, 0),
                                       standard_size(size),
                                       status_label(gettext("Inactive")),
                                       help_label(gettext("Press F1 for help")) {

  set_border_width(2);
  status_label.set_size_request(standard_size * 7, -1);
  status_frame.set_shadow_type(Gtk::SHADOW_IN);
  status_frame.add(status_label);
  pack_start(status_frame, true, true, 0);
  
  help_label.set_size_request(standard_size * 6, -1);
  help_frame.set_shadow_type(Gtk::SHADOW_IN);
  help_frame.add(help_label);
  pack_end(help_frame, true, true, 0);

  show_all();
}

void StatusLine::set_status_line(const Glib::RefPtr<Gtk::Style>& new_style_r, int new_height) {

  if (new_height < standard_size + 2) new_height = standard_size + 2;
  set_size_request(-1, new_height);

  // make the status label display in red
  // Note: we need to take a copy of the existing style, as the existing
  // style is shared by all the program's widgets.  If we operate
  // directly on the Gtk::Style object returned (by Glib::RefPtr) by
  // Gtk::Widget::get_style(), say with Gtk::Style::set_fg(),
  // we will alter it for everything!
  Glib::RefPtr<Gtk::Style> reset_style_r(new_style_r->copy());
  Gdk::Color red;
  red.set_rgb_p(0.9, 0, 0);
  Glib::RefPtr<Gdk::Colormap> colourmap_r = Gtk::Widget::get_default_colormap();
  colourmap_r->alloc_color(red);
  reset_style_r->set_fg(Gtk::STATE_NORMAL, red);
  status_label.set_style(reset_style_r);
}

int connect_to_stderr(void) {
  int result = MainWindow::error_pipe.connect_to_stderr();
  if (!result) MainWindow::connected_to_stderr = true;
  return result;
} 

ssize_t write_error(const char* message) {
  // this writes to a pipe (which will provide asynchronous to
  // synchronous conversion) so it can be used by different threads
  // without upsetting GTK+. It also only uses async-signal-safe
  // system functions and is thread safe with only a mutex in this
  // function because (a) although Pipe_fifo::read() and
  // Pipe_fifo::write() check the value of Pipe_fifo::read_fd and
  // Pipe_fifo::write_fd respectively, and Pipe_fifo::open(),
  // Pipe_fifo::make_writeonly(), Pipe_fifo::make_readonly(),
  // Pipe_fifo::close(), Pipe_fifo::connect_to_stdin(),
  // Pipe_fifo::connect_to_stdout() and Pipe_fifo::connect_to_stderr()
  // change those values, all but the last one are never called on
  // MainWindow::error_pipe until (if at all) error_pipe goes out of
  // scope when global objects are destroyed as the program exits, and
  // (b) Pipe_fifo::connect_to_stderr() is only ever called after
  // fork()ing into a new single-threaded process

  // The pipe which is non-blocking - it will be emptied fairly promptly
  // but messages should be less than PIPE_BUF in length or any excess
  // will be lost unless the caller checks the return value and acts
  // accordingly

  Glib::Mutex::Lock lock(*write_error_mutex_p);

  ssize_t result = 0;
  ssize_t written = 0;
  ssize_t to_write_count = std::strlen(message);
  
  if (!MainWindow::connected_to_stderr) {
    do {
      result = MainWindow::error_pipe.write(message + written, to_write_count);
      if (result > 0) {
      written += result;
      to_write_count -= result;
      }
    } while (to_write_count && result != -1); // the pipe checks for EINTR itself
  }

  else {
    do {
      result = ::write(2, message + written, to_write_count);
      if (result > 0) {
      written += result;
      to_write_count -= result;
      }
    } while (to_write_count && (result != -1 || errno == EINTR));
  }
  return written;
}

void close_signalhandler(int) {
  close_flag = true;
}

void usr2_signalhandler(int) {
  usr2_flag = true;
}

Generated by  Doxygen 1.6.0   Back to index