//$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 #include #include #include #include #include #include #include #include #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 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& model, const Gtk::TreeModel::Path& path, bool) const { const Gtk::TreeModel::iterator iter (model->get_iter (path)); return iter->parent (); }