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

file_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 <sys/types.h>
#include <unistd.h>

#include <cstdlib>
#include <vector>
#include <string>

#include <gtkmm/image.h>
#include <gtkmm/stock.h>
#include <gtkmm/label.h>
#include <gtkmm/main.h>
#include <gtkmm/tooltips.h>
#include <gtkmm/button.h>
#include <gtkmm/enums.h>
#include <gdkmm/pixbuf.h>
#include <glibmm/convert.h>
#include <glibmm/timer.h>

#include <gtk/gtktoolbar.h>

#include "file_list.h"
#include "dialogs.h"
#include "file_list_icons.h"

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

int FileListDialog::is_file_list = 0;


FileListDialog::FileListDialog(const int standard_size_, Gtk::Window& window):
                               Gtk::Window(Gtk::WINDOW_TOPLEVEL),
                         standard_size(standard_size_), parent(window),
                         in_exec_loop(false), file_list_box(false, 0),
                         table(5, 2, false), ok_button(Gtk::Stock::OK),
                         cancel_button(Gtk::Stock::CANCEL),
                         button_box(Gtk::BUTTONBOX_END, standard_size/2) {

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

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

  // create the tree model:
  list_store_r = Gtk::ListStore::create(model_column);
  // connect it to the tree view
  tree_view.set_model(list_store_r);
  // add the column of the tree model to tree view
  tree_view.append_column(gettext("Files to fax"), model_column.file_name);
  // single line selection
  tree_view.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
  // make drag and drop work
  tree_view.set_reorderable(true);

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

  // first make the image widgets
  Gtk::Image* add_image_p = manage(new Gtk::Image(Gdk::Pixbuf::create_from_xpm_data(add_xpm)));
  Gtk::Image* view_image_p = manage(new Gtk::Image(Gdk::Pixbuf::create_from_xpm_data(view_xpm)));
  Gtk::Image* remove_image_p = manage(new Gtk::Image(Gdk::Pixbuf::create_from_xpm_data(remove_xpm)));
  Gtk::HBox* add_hbox_p = manage(new Gtk::HBox);
  Gtk::Label* add_label_p = manage(new Gtk::Label(gettext("Add files to list")));

  add_hbox_p->pack_start(*add_image_p, Gtk::PACK_SHRINK, 4);
  add_hbox_p->pack_start(*add_label_p, Gtk::PACK_SHRINK, 4);

  tool_bar.set_tooltips(true);
  // extract the Gtk::Tooltips object from Gtk:Toolbar (this is OK as
  // in Gtk-2.4 the 'tooltips' member of struct _GtkToolbar is declared public
  // and so is a guaranteed part of the GTK+ interface)
  Gtk::Tooltips* tooltips_p = Glib::wrap(tool_bar.gobj()->tooltips);

#if GTKMM_VERSION >= 24

  // if using GTK+ 2.4 or higher, normalise the height of images in the
  // Gtk::Toolbar object (I have no idea why this should be necessary)
  add_image_p->set_size_request(-1, 26);
  view_image_p->set_size_request(-1, 26);
  remove_image_p->set_size_request(-1, 26);

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

  remove_button_p = manage(new Gtk::ToolButton(*remove_image_p));
  tool_bar.append(*remove_button_p, sigc::mem_fun(*this, &FileListDialog::remove_file));
  remove_button_p->set_sensitive(false);
  remove_button_p->set_tooltip(*tooltips_p, gettext("Remove selected file from list"));

  Gtk::ToolButton* add_button_p = manage(new Gtk::ToolButton(*add_hbox_p));
  tool_bar.append(*add_button_p, sigc::mem_fun(*this, &FileListDialog::add_file));
  add_button_p->set_tooltip(*tooltips_p, gettext("Add files to the file list for faxing"));

#else
  {
    using namespace Gtk::Toolbar_Helpers;
    
    add_button.add(*add_hbox_p);

    ToolList& tool_list = tool_bar.tools();
    tool_list.push_back(ButtonElem(*view_image_p, SigC::slot(*this, &FileListDialog::view_file),
                           gettext("View selected file")));
    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(*remove_image_p, SigC::slot(*this, &FileListDialog::remove_file_prompt),
                           gettext("Remove selected file from list")));
    remove_button_p = static_cast<Gtk::Button*>(tool_list.back().get_widget());
    remove_button_p->set_relief(Gtk::RELIEF_NONE);
    remove_button_p->set_sensitive(false);

    tool_list.push_back(Element(add_button, gettext("Add files to the file list for faxing")));
    add_button.signal_clicked().connect(SigC::slot(*this, &FileListDialog::add_file));
  }
#endif

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

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

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

  tooltips_p->set_tip(up_button, gettext("Move file up"));
  tooltips_p->set_tip(down_button, gettext("Move file down"));

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

  Gtk::Label* dummy1_p = manage(new Gtk::Label);
  Gtk::Label* dummy2_p = manage(new Gtk::Label);

  table.attach(*dummy1_p, 0, 1, 0, 1,
                Gtk::SHRINK, Gtk::EXPAND, 0, 0);
  table.attach(up_button, 0, 1, 1, 2,
                Gtk::SHRINK, Gtk::SHRINK, standard_size/2, standard_size/3);
  table.attach(down_button, 0, 1, 2, 3,
                Gtk::SHRINK, Gtk::SHRINK, standard_size/2, standard_size/3);
  table.attach(*dummy2_p, 0, 1, 3, 4,
                Gtk::SHRINK, Gtk::EXPAND, 0, 0);
  table.attach(file_list_scroll_window, 1, 2, 0, 4, Gtk::EXPAND | Gtk::FILL,
         Gtk::EXPAND | Gtk::FILL, standard_size/3, standard_size/3);
  table.attach(button_box, 0, 2, 4, 5, Gtk::EXPAND | Gtk::FILL,
       Gtk::SHRINK, standard_size/3, standard_size/3);

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

  ok_button.signal_clicked().connect(sigc::mem_fun(*this, &FileListDialog::ok_slot));
  cancel_button.signal_clicked().connect(sigc::mem_fun(*this, &FileListDialog::finish));
  up_button.signal_clicked().connect(sigc::mem_fun(*this, &FileListDialog::move_up));
  down_button.signal_clicked().connect(sigc::mem_fun(*this, &FileListDialog::move_down));

  // now connect up the signal which indicates a selection has been made
  tree_view.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FileListDialog::set_buttons_slot));

  ok_button.set_flags(Gtk::CAN_DEFAULT);
  cancel_button.set_flags(Gtk::CAN_DEFAULT);
  up_button.unset_flags(Gtk::CAN_FOCUS);
  down_button.unset_flags(Gtk::CAN_FOCUS);


  table.set_border_width(standard_size/3);

  set_title(gettext("efax-gtk: File list"));
  set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
  add(file_list_box);

  set_transient_for(parent);
  set_type_hint(Gdk::WINDOW_TYPE_HINT_DIALOG);
  parent.set_sensitive(false);
  set_modal(true);
  set_default_size(standard_size * 15, standard_size * 12);

  set_icon(prog_config.window_icon_r);

  show_all();

#if GTKMM_VERSION >= 24
  add_button_p->set_sensitive(true);
  tool_bar.set_size_request(add_button_p->get_width() + view_button_p->get_width()
                      + remove_button_p->get_width() + 12, add_button_p->get_height());
#else
  add_button.set_relief(Gtk::RELIEF_NORMAL);
  add_button.set_sensitive(true);
#endif
}

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

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

void FileListDialog::ok_slot(void) {
  accepted(get_files());
  finish();
}

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

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

Glib::ustring FileListDialog::get_files(void) {
  Glib::ustring text;

  Gtk::TreeModel::iterator row_iter;
  for (row_iter = list_store_r->children().begin();
       row_iter != list_store_r->children().end();
       ++row_iter) {
    if (row_iter != list_store_r->children().begin()) text += ", ";
    text += (*row_iter)[model_column.file_name];
  }
  return text;
}

void FileListDialog::move_up(void) {

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

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

    Glib::ustring selected_name = (*selected_row_iter)[model_column.file_name];
    Glib::ustring prev_name = (*prev_row_iter)[model_column.file_name];
      
    (*selected_row_iter)[model_column.file_name] = prev_name;
    (*prev_row_iter)[model_column.file_name] = selected_name;
    tree_view.get_selection()->select(prev_row_iter);
  }
  else beep();
}

void FileListDialog::move_down(void) {

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

      Glib::ustring selected_name = (*selected_iter)[model_column.file_name];
      Glib::ustring succeding_name = (*succeding_iter)[model_column.file_name];
      
      (*selected_iter)[model_column.file_name] = succeding_name;
      (*succeding_iter)[model_column.file_name] = selected_name;
      tree_view.get_selection()->select(succeding_iter);
    }
    else beep();
  }
  else beep();
}

void FileListDialog::set_buttons_slot(void) {

  // see if anything is selected
  if (tree_view.get_selection()->get_selected()) {
    view_button_p->set_sensitive(true);
    remove_button_p->set_sensitive(true);

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

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

void FileListDialog::add_file(void) {

  FileReadSelectDialog file_dialog(standard_size, true, *this);
  std::vector<Glib::ustring> file_result = file_dialog.exec();
  if (!file_result.empty()) {
    std::vector<Glib::ustring>::const_iterator iter;
    for (iter = file_result.begin(); iter != file_result.end(); ++iter) {
      // get a list store row to insert the file name
      Gtk::TreeModel::Row row = *(list_store_r->append());
      row[model_column.file_name] = *iter;
    }
  }
}

std::pair<const char*, char* const*> FileListDialog::get_view_file_parms(const string& file_name) {

  std::vector<std::string> view_parms;
  std::string view_cmd;
  std::string view_name;
  std::string::size_type end_pos;
  try {
    // lock the Prog_config object to stop it being accessed in
    // FaxListDialog::get_ps_viewer_parms() while we are accessing it here
    // (this is ultra cautious as it is only copied/checked for emptiness
    // there, and the GUI interface is insensitive if we are here)
    Glib::Mutex::Lock lock(*prog_config.mutex_p);
    view_cmd = Glib::filename_from_utf8(prog_config.ps_view_cmd);
  }
  catch (Glib::ConvertError&) {
    write_error("UTF-8 conversion error in FileListDialog::get_view_file_parms()\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 view command without parameters to be passed
    view_name = view_cmd;
    view_parms.push_back(view_name);
  }

  view_parms.push_back(file_name);

  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 FileListDialog::view_file(void) {

  Gtk::TreeModel::iterator row_iter = tree_view.get_selection()->get_selected();
  
  bool is_ps_view_cmd_empty;
  { // lock the Prog_config object to stop it being accessed in
    // FaxListDialog::get_ps_viewer_parms() while we are accessing it here
    // (this is ultra cautious as it is only copied/checked for emptiness
    // there, and the GUI interface is insensitive if we are here)
    Glib::Mutex::Lock lock(*prog_config.mutex_p);
    is_ps_view_cmd_empty = prog_config.ps_view_cmd.empty();
  }
  if (row_iter && !is_ps_view_cmd_empty) {
    
    std::string file_name;
    try {
      file_name = Glib::filename_from_utf8((*row_iter)[model_column.file_name]);
    }
    catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in FileListDialog::view_file()\n");
      beep();
      return;
    }

    // get the arguments 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 arguments which are not async-signal-safe)
    std::pair<const char*, char* const*> view_file_parms(get_view_file_parms(file_name));

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

      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()

      connect_to_stderr();

      execvp(view_file_parms.first, view_file_parms.second);

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

void FileListDialog::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 FileListDialog::remove_file_prompt(void) {

  Gtk::TreeModel::iterator row_iter = tree_view.get_selection()->get_selected();
  if (row_iter) {
    Glib::ustring msg(gettext("Remove file "));
    msg += (*row_iter)[model_column.file_name] + gettext(" from the list?");
    
    PromptDialog* dialog_p = new PromptDialog(msg, gettext("Remove file"), standard_size, *this);
    dialog_p->accepted.connect(sigc::mem_fun(*this, &FileListDialog::remove_file));
    // there is no memory leak -- the memory will be deleted when PromptDialog closes
  }
}

void FileListDialog::remove_file(void) {

  Gtk::TreeModel::iterator row_iter = tree_view.get_selection()->get_selected();
  if (row_iter) list_store_r->erase(row_iter);
}

Generated by  Doxygen 1.6.0   Back to index