//$Id: XFileSel.cpp,v 1.28 2006/06/05 16:46:02 markus Rel $
//PROJECT : XDirComp
//SUBSYSTEM : XFileSel
//REFERENCES :
//TODO :
//BUGS :
//REVISION : $Revision: 1.28 $
//AUTHOR : Markus Schwab
//CREATED : 14.9.1999
//COPYRIGHT : Copyright (C) 1999 - 2006
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "dircomp-cfg.h"
#include <string>
#include <gtkmm/box.h>
#include <gtkmm/table.h>
#include <gtkmm/entry.h>
#include <gtkmm/label.h>
#include <gtkmm/button.h>
#include <gtkmm/treeview.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/scrolledwindow.h>
#include "YGP/Trace.h"
#include "YGP/Check.h"
#include "YGP/PathSrch.h"
#include "YGP/FileRExp.h"
#include "XFileSel.h"
static const char _INCLUDE ('+');
static const char _EXCLUDE ('-');
//-----------------------------------------------------------------------------
/// Constructor; adds all controls to the dialog
/// \param files: String containing the files to in/exclude
/// \param title: Title of dialog
//-----------------------------------------------------------------------------
XFileSelection::XFileSelection (std::string& files, const Glib::ustring& title)
: XDialog (title, OKCANCEL)
, actions (new Gtk::VBox) , listActions (new Gtk::VBox)
, lblFiles (new Gtk::Label (Glib::locale_to_utf8 (_("File:"))))
, file (new Gtk::Entry ()) , tblFile (new Gtk::Table ())
, include (new Gtk::Button (Glib::locale_to_utf8 (_("_Include")), true))
, exclude (new Gtk::Button (Glib::locale_to_utf8 (_("E_xclude")), true))
, delEntry (new Gtk::Button (Glib::locale_to_utf8 (_("D_elete")), true))
, upEntry (new Gtk::Button (Glib::locale_to_utf8 (_("_Up")), true))
, downEntry (new Gtk::Button (Glib::locale_to_utf8 (_("_Down")), true))
, target (files), vFiles (Gtk::TreeStore::create (cols))
, treeFiles (new Gtk::TreeView (vFiles))
, scroll (new Gtk::ScrolledWindow ()) {
TRACE9 ("XFileSelection::XFileSelection (std::string&, const std::string&)");
Check3 (lblFiles); Check3 (file);
Check3 (treeFiles); Check3 (tblFile); Check3 (include); Check3 (exclude);
Check3 (actions); Check (listActions); Check3 (delEntry); Check3 (upEntry);
Check3 (downEntry);
// Action-buttons
struct {
Gtk::Button* pButton;
commands id; }
buttons[] = { { include, INCLUDE }, { exclude, EXCLUDE }, { upEntry, UP },
{downEntry, DOWN }, { delEntry, DELETE } }, *pAct = buttons;
while (pAct < buttons + (sizeof (buttons) / sizeof (buttons[0]))) {
TRACE5 ("XFileSelection::XFileSelection (std::string&, const std::string&) - Create button"
<< pAct->id);
Check3 (pAct->pButton);
pAct->pButton->set_sensitive (false);
pAct->pButton->set_flags (Gtk::CAN_DEFAULT);
pAct->pButton->set_size_request (90, 35);
pAct->pButton->signal_clicked ().connect
(bind (mem_fun (*this, &XFileSelection::command), pAct->id));
pAct++->pButton->show ();
} // end-while
// Create other controls
actions->pack_start (*include, false, false, 0);
actions->pack_start (*exclude, false, false, 0);
actions->show ();
listActions->pack_start (*upEntry, false, false, 0);
listActions->pack_start (*downEntry, false, false, 0);
listActions->pack_start (*delEntry, false, false, 0);
listActions->show ();
treeFiles->append_column (Glib::locale_to_utf8 (_("Selected files")), cols.files);
treeFiles->set_size_request (200, 250);
treeFiles->get_column (0)->set_min_width (100);
treeFiles->show ();
scroll->add (*treeFiles);
scroll->set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
scroll->show ();
Glib::RefPtr <Gtk::TreeSelection> sel (treeFiles->get_selection ());
sel->signal_changed ().connect (mem_fun (*this, &XFileSelection::rowSelected));
sel->set_select_function (mem_fun (*this, &XFileSelection::canSelect));
lblFiles->set_alignment (0.0, 0.5);
lblFiles->show ();
file->show ();
file->signal_changed ().connect (mem_fun (*this, &XFileSelection::entryChanged));
tblFile->attach (*scroll, 0, 2, 0, 1, Gtk::EXPAND | Gtk::FILL,
Gtk::EXPAND | Gtk::FILL, 5, 5);
tblFile->attach (*listActions, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 5, 5);
tblFile->attach (*lblFiles, 0, 1, 1, 2, Gtk::SHRINK, Gtk::SHRINK, 5, 5);
tblFile->attach (*file, 1, 2, 1, 2, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 5, 5);
tblFile->attach (*actions, 2, 3, 1, 2, Gtk::SHRINK, Gtk::SHRINK, 5, 5);
tblFile->show ();
get_vbox ()->pack_start (*tblFile, true, true, 5);
file->grab_focus ();
YGP::PathSearch ps (files);
std::string node;
// Insert all existing file-entries into the list
char type = '?';
Gtk::TreeRow parent;
while (!(node = ps.getNextNode ()).empty ()) {
TRACE8 ("XFileSelection::XFileSelection (std::string&, std::string&) - "
"Checking node " << node);
if ((node[0] != _INCLUDE) && (node[0] != _EXCLUDE)) {
std::string err (Glib::locale_to_utf8 (_("Ignoring invalid input `%1'")));
err.replace (err.find ("%1"), 2, node);
Gtk::MessageDialog dlg (err, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
dlg.run ();
continue;
}
if (type != node[0]) {
parent = *makeParentEntry (type = node[0], vFiles->children ().end ());
parent[cols.type] = type;
}
Gtk::TreeRow child (*vFiles->append (parent.children ()));
child[cols.files] = node.substr (1);
} // end-while nodes available
treeFiles->expand_all ();
show ();
}
//-----------------------------------------------------------------------------
/// Destructor
//-----------------------------------------------------------------------------
XFileSelection::~XFileSelection () {
TRACE9 ("XFileSelection::~XFileSelection ()");
}
//-----------------------------------------------------------------------------
//Purpose : Callback after pressing the OK button
//-----------------------------------------------------------------------------
void XFileSelection::okEvent () {
target = "";
for (Gtk::TreeModel::iterator p (vFiles->children ().begin ());
p != vFiles->children ().end (); ++p)
for (Gtk::TreeModel::iterator c (p->children ().begin ());
c != p->children ().end (); ++c) {
TRACE8 ("XFileSelection::okEvent () - Adding: "
<< char ((*p)[cols.type]) << std::string ((*c)[cols.files]));
target += (*p)[cols.type];
target += (*c)[cols.files];
target += YGP::PathSearch::PATHSEPARATOR;
} // end-for all children
XDialog::okEvent ();
}
//-----------------------------------------------------------------------------
/// Performs the action of the selected button
/// \param action: ID of pressed button
//-----------------------------------------------------------------------------
void XFileSelection::command (commands id) {
TRACE9 ("XFileSelection::command (commands): " << (int)id);
switch (id) {
case INCLUDE:
case EXCLUDE: {
Check3 (file->get_text_length ());
YGP::FileRegularExpr regexp (file->get_text ().c_str ());
try {
regexp.checkIntegrity ();
}
catch (std::invalid_argument& e) {
std::string err (Glib::locale_to_utf8 (_("Invalid name for directories!\n\nReason: %1")));
err.replace (err.find ("%1"), 2, e.what ());
Gtk::MessageDialog dlg (err, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
dlg.run ();
break;
}
char type ((id == INCLUDE) ? _INCLUDE : _EXCLUDE);
Gtk::TreeRow parent (*vFiles->children ().begin ());
if ((parent == vFiles->children ().end ())
|| (parent = vFiles->children ()[vFiles->children ().size () - 1],
(parent[cols.type] != type)))
parent = *makeParentEntry (type, vFiles->children ().end ());
Gtk::TreeRow child (*vFiles->append (parent.children ()));
child[cols.files] = file->get_text ();
treeFiles->expand_row (Gtk::TreePath (parent), true);
treeFiles->get_selection ()->select (child);
break; }
case UP:
moveSelectedRowUp ();
break;
case DOWN:
moveSelectedRowDown ();
break;
case DELETE: {
Gtk::TreeIter selRow (treeFiles->get_selection ()->get_selected ());
Gtk::TreeIter parent (selRow->parent ());
vFiles->erase (selRow);
if (parent->children ().empty ())
vFiles->erase (parent);
break; }
default:
Check (0);
} // end-switch
}
//-----------------------------------------------------------------------------
/// Entryfield for files changed -> adapt button-status
//-----------------------------------------------------------------------------
void XFileSelection::entryChanged () {
Check3 (file);
bool status (file->get_text_length ());
include->set_sensitive (status);
exclude->set_sensitive (status);
}
//-----------------------------------------------------------------------------
/// Handling of row in file-list selected
//-----------------------------------------------------------------------------
void XFileSelection::rowSelected () {
TRACE5 ("XFileSelection::rowSelected ()");
Check3 (upEntry); Check3 (downEntry); Check3 (delEntry);
Gtk::TreeIter selRow (treeFiles->get_selection ()->get_selected ());
if (selRow) {
Gtk::TreeIter parent (selRow->parent ());
Check3 (parent);
upEntry->set_sensitive ((parent != vFiles->children ().begin ())
|| (selRow != parent->children ().begin ()));
downEntry->set_sensitive (++selRow || ++parent);
delEntry->set_sensitive (true);
}
else {
delEntry->set_sensitive (false);
upEntry->set_sensitive (false);
downEntry->set_sensitive (false);
}
}
//-----------------------------------------------------------------------------
/// Creates a parent entry for the list of the specified type
/// \param type: Type of parent node (In/Exclude)
/// \returns Gtk::TreeIter to created entry
/// \pre type must be either _INCLUDE or _EXCLUDE
//-----------------------------------------------------------------------------
Gtk::TreeIter XFileSelection::makeParentEntry (char type,
Gtk::TreeIter pos) {
Check1 ((type == _INCLUDE) || (type == _EXCLUDE));
Gtk::TreeIter parent (vFiles->insert (pos));
(*parent)[cols.files] = Glib::locale_to_utf8 (_((type == _INCLUDE)
? N_("Include") : N_("Exclude")));
(*parent)[cols.type] = type;
return *parent;
}
//-----------------------------------------------------------------------------
/// Searches for the previous to the act entry in a list
/// \param list: List to search in
/// \param act: Actual position (must be a member of the list)
/// \returns Gtk::TreeIter: Previous element to act
/// \pre - act element of list
/// - act not the first element
//-----------------------------------------------------------------------------
Gtk::TreeIter XFileSelection::getPrevious (const Gtk::TreeNodeChildren& list,
const Gtk::TreeIter act) const {
Check1 (list.begin () != act);
Gtk::TreeIter prevRow (list.begin ());
Gtk::TreeIter work (prevRow);
while (++work != act) {
Check1 (work != list.end ());
prevRow = work;
}
return prevRow;
}
//-----------------------------------------------------------------------------
/// Moves the selected row one line up; which might mean into an other
/// group of a different type
//-----------------------------------------------------------------------------
void XFileSelection::moveSelectedRowUp () {
Gtk::TreeIter selRow (treeFiles->get_selection ()->get_selected ());
Gtk::TreeIter parent (selRow->parent ());
if (selRow != parent->children ().begin ()) { // Not first child?
vFiles->iter_swap (selRow, getPrevious (parent->children (), selRow));
rowSelected ();
}
else {
TRACE3 ("XFileSelection::moveSelectedRowUp () - Crossing nodes");
// Get previous (parent) entry
Check3 (parent != vFiles->children ().begin ());
Gtk::TreeIter prevNode (getPrevious (vFiles->children (), parent));
Check3 ((*prevNode)[cols.type] != (*parent)[cols.type]);
Check3 (!prevNode->children ().empty ());
Gtk::TreeIter newEntry;
Gtk::TreeIter newParent;
// First find/make new node for last child of prevNode (which will be
// deleted afterwards - along with the parent if it only had 1 kid)
TRACE6 ("XFileSelection::moveSelectedRowUp () - Needs new parent?");
newParent = parent;
if (!++newParent
|| (parent->children ().size () > 1))
newParent = makeParentEntry ((*prevNode)[cols.type], parent);
newEntry = (vFiles->append (newParent->children ()));
treeFiles->expand_row (Gtk::TreePath (newParent), true);
TRACE6 ("XFileSelection::moveSelectedRowUp () - Copy last entry of prev");
Gtk::TreeRow t(prevNode->children ()[prevNode->children ().size () - 1]);
(*newEntry)[cols.files] = std::string (t[cols.files]);
// Check if previous node has more than one child or is the first entry
if ((prevNode->children ().size () > 1)
|| (prevNode == vFiles->children ().begin ()))
newParent = (*makeParentEntry ((*parent)[cols.type], newParent));
else {
// If not: Add selected row to pre-previous parent
TRACE9 ("XFileSelection::moveSelectedRowUp () - Prev has only 1 kid");
newParent = getPrevious (vFiles->children (), prevNode);
Check3 ((*newParent)[cols.type] == (*parent)[cols.type]);
Check3 (!newParent->children ().empty ());
}
vFiles->erase (t);
if (prevNode->children ().empty ())
vFiles->erase (prevNode);
// After the final preparation: Append the moved line to the new parent
Check3 (newParent);
newEntry = (vFiles->append (newParent->children ()));
(*newEntry)[cols.files] = std::string ((*selRow)[cols.files]);
treeFiles->expand_row (Gtk::TreePath (newParent), true);
vFiles->erase (selRow);
if (parent->children ().empty ())
vFiles->erase (parent);
treeFiles->get_selection ()->select (newEntry);
}
}
//-----------------------------------------------------------------------------
/// Moves the selected row one line down; which might mean into an
/// other group of a different type
//-----------------------------------------------------------------------------
void XFileSelection::moveSelectedRowDown () {
Gtk::TreeIter selRow (treeFiles->get_selection ()->get_selected ());
Gtk::TreeIter parent (selRow->parent ());
Gtk::TreeIter nextRow (selRow);
if (++nextRow) { // Not last child?
vFiles->iter_swap (selRow, nextRow);
rowSelected ();
}
else {
TRACE3 ("XFileSelection::moveSelectedRowDown () - Crossing nodes");
// Get next (parent) entry (where to move selected line into
Gtk::TreeIter nextParent (parent);
++nextParent;
Check3 (nextParent);
Check3 ((*nextParent)[cols.type] != (*parent)[cols.type]);
Check3 (!nextParent->children ().empty ());
Gtk::TreeIter newEntry;
Gtk::TreeIter newParent (parent);
// First find/make new node for first child of nextParent (which will be
// deleted afterwards - along with the parent if it only had 1 kid)
TRACE6 ("XFileSelection::moveSelectedRowDown () - Needs new parent?");
if ((parent == vFiles->children ().begin ())
|| (parent->children ().size () > 1))
newParent = makeParentEntry ((*nextParent)[cols.type], nextParent);
else
newParent = getPrevious (vFiles->children (), parent);
newEntry = (vFiles->append (newParent->children ()));
treeFiles->expand_row (Gtk::TreePath (newParent), true);
TRACE6 ("XFileSelection::moveSelectedRowDown () - Copy last entry of prev");
Gtk::TreeRow t (*nextParent->children ().begin ());
(*newEntry)[cols.files] = std::string (t[cols.files]);
// Check if next node has more than one child or is the last entry
newParent = nextParent;
++newParent;
if ((nextParent->children ().size () > 1) || !newParent)
newParent = (*makeParentEntry ((*parent)[cols.type], nextParent));
vFiles->erase (t);
if (nextParent->children ().empty ())
vFiles->erase (nextParent);
// After the final preparation: Prepend the moved line to the new parent
Check3 (newParent);
newEntry = (vFiles->prepend (newParent->children ()));
(*newEntry)[cols.files] = std::string ((*selRow)[cols.files]);
treeFiles->expand_row (Gtk::TreePath (newParent), true);
vFiles->erase (selRow);
if (parent->children ().empty ())
vFiles->erase (parent);
treeFiles->get_selection ()->select (newEntry);
}
}
//-----------------------------------------------------------------------------
/// Tests, if the passed line can be selected by the user. This is
/// true only for leaf nodes (containing the files to in/exclude).
/// \param model: List-model
/// \param path: Path to entry in model which should be checked
/// \param bool: Flag, if row is actually selected
/// \returns bool: True, if the passed row can be selected
//-----------------------------------------------------------------------------
bool XFileSelection::canSelect (const Glib::RefPtr<Gtk::TreeModel>& model,
const Gtk::TreeModel::Path& path, bool) const {
const Gtk::TreeModel::iterator iter (model->get_iter (path));
return iter->parent ();
}
syntax highlighted by Code2HTML, v. 0.9.1