PrefsWindow.C

Go to the documentation of this file.
00001 /*!@file GUI/PrefsWindow.C */
00002 
00003 // //////////////////////////////////////////////////////////////////// //
00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005   //
00005 // by the University of Southern California (USC) and the iLab at USC.  //
00006 // See http://iLab.usc.edu for information about this project.          //
00007 // //////////////////////////////////////////////////////////////////// //
00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected //
00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency //
00010 // in Visual Environments, and Applications'' by Christof Koch and      //
00011 // Laurent Itti, California Institute of Technology, 2001 (patent       //
00012 // pending; application number 09/912,225 filed July 23, 2001; see      //
00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status).     //
00014 // //////////////////////////////////////////////////////////////////// //
00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit.       //
00016 //                                                                      //
00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can   //
00018 // redistribute it and/or modify it under the terms of the GNU General  //
00019 // Public License as published by the Free Software Foundation; either  //
00020 // version 2 of the License, or (at your option) any later version.     //
00021 //                                                                      //
00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope  //
00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the   //
00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      //
00025 // PURPOSE.  See the GNU General Public License for more details.       //
00026 //                                                                      //
00027 // You should have received a copy of the GNU General Public License    //
00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write   //
00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,   //
00030 // Boston, MA 02111-1307 USA.                                           //
00031 // //////////////////////////////////////////////////////////////////// //
00032 //
00033 // Primary maintainer for this file: Rob Peters <rjpeters at usc dot edu>
00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/GUI/PrefsWindow.C $
00035 // $Id: PrefsWindow.C 9412 2008-03-10 23:10:15Z farhan $
00036 //
00037 
00038 #ifndef GUI_PREFSWINDOW_C_DEFINED
00039 #define GUI_PREFSWINDOW_C_DEFINED
00040 
00041 #include "GUI/PrefsWindow.H"
00042 
00043 #include "Component/ModelComponent.H"
00044 #include "Component/ModelParam.H"
00045 #include "GUI/XWinManaged.H"
00046 #include "Image/Image.H"
00047 #include "Image/Pixels.H"
00048 #include "Image/DrawOps.H"
00049 #include "Util/log.H"
00050 #include "Util/sformat.H"
00051 
00052 #include <X11/keysym.h>
00053 
00054 /// Template class for numeric preference items based on OModelParam or NModelParam
00055 template <class MP, class T>
00056 class PrefItemMPNum : public PrefItem
00057 {
00058 private:
00059   nub::ref<ModelComponent> comp; // hold a reference to the param's
00060                                  // owner so that the param won't be
00061                                  // destroyed until after we are
00062   MP* param;
00063 
00064   template <class U>
00065   static U adjustHelper(U val, int v)
00066   { return val + v; }
00067 
00068   static bool adjustHelper(bool val, int v)
00069   { return (int(val) + v) % 2; }
00070 
00071 public:
00072   PrefItemMPNum(PrefsWindow* pwin, MP* p, ModelComponent* c,
00073                 bool pwinTakesOwnership)
00074     : PrefItem(pwin, p->getNameWithSpaces().c_str(), pwinTakesOwnership),
00075       comp(c),
00076       param(p)
00077   {}
00078 
00079   void set(T v) { if (param->getVal() != v) { param->setVal(v); this->isChanged = true; } }
00080   T get() const { return param->getVal(); }
00081 
00082   virtual void fromString(const std::string& s)
00083   {
00084     if (s.size() == 0) return; // ignore empty strings
00085     const T oldval = param->getVal();
00086     try { param->setValString(s); }
00087     catch (std::exception& e) { REPORT_CURRENT_EXCEPTION; } // report+swallow error
00088     if (oldval != param->getVal()) this->isChanged = true;
00089   }
00090 
00091   virtual std::string toString() const
00092   { return param->getValString(); }
00093 
00094   virtual void adjust(int v)
00095   { this->set(adjustHelper(this->get(), v)); }
00096 
00097   virtual bool isDisabled() const
00098   { return param->getInactive(); }
00099 };
00100 
00101 /// Template class for non-numeric preference items based on OModelParam or NModelParam
00102 class PrefItemMPStr : public PrefItem
00103 {
00104 private:
00105   nub::ref<ModelComponent> comp; // hold a reference to the param's
00106                                  // owner so that the param won't be
00107                                  // destroyed until after we are
00108 
00109   ModelParamBase* param;
00110 
00111 public:
00112   PrefItemMPStr(PrefsWindow* pwin, ModelParamBase* p, ModelComponent* c,
00113                 bool pwinTakesOwnership)
00114     : PrefItem(pwin, p->getNameWithSpaces().c_str(), pwinTakesOwnership),
00115       comp(c),
00116       param(p)
00117   {}
00118 
00119   virtual void fromString(const std::string& s)
00120   {
00121     if (s.size() == 0) return; // ignore empty strings
00122     const std::string oldval = param->getValString();
00123     try { param->setValString(s); }
00124     catch (std::exception& e) { REPORT_CURRENT_EXCEPTION; } // report+swallow error
00125     if (oldval != param->getValString()) this->isChanged = true;
00126   }
00127 
00128   virtual std::string toString() const
00129   { return param->getValString(); }
00130 
00131   virtual void adjust(int v)
00132   { /* do nothing; adjustments not supported for non-numeric params */ }
00133 
00134   virtual bool isDisabled() const
00135   { return param->getInactive(); }
00136 };
00137 
00138 // ######################################################################
00139 PrefItem::PrefItem(PrefsWindow* pwin, const std::string& nm,
00140                    bool pwinTakesOwnership)
00141   :
00142   name(nm), isChanged(true), wasDisabled(false)
00143 {
00144   if (pwin != 0)
00145     pwin->addItem(this, pwinTakesOwnership);
00146   else if (pwinTakesOwnership)
00147     // we can't have the PrefsWindow take ownership if we don't
00148     // actually have a PrefsWindow:
00149     LFATAL("I can't pass ownership of PrefItem '%s' "
00150            "to a null PrefsWindow*", nm.c_str());
00151 }
00152 
00153 // ######################################################################
00154 PrefItem::~PrefItem()
00155 {}
00156 
00157 // ######################################################################
00158 std::string PrefItem::getName() const
00159 {
00160   return this->name;
00161 }
00162 
00163 // ######################################################################
00164 bool PrefItem::isValueChanged()
00165 {
00166   const bool ret =
00167     this->isChanged || (this->wasDisabled != this->isDisabled());
00168   this->isChanged = false;
00169   this->wasDisabled = this->isDisabled();
00170   return ret;
00171 }
00172 
00173 // ######################################################################
00174 PrefItemStr::PrefItemStr(PrefsWindow* pwin, const std::string& nm,
00175                          const std::string& v,
00176                          const bool pwinTakesOwnership)
00177   : PrefItem(pwin, nm, pwinTakesOwnership),
00178     itsHist(), itsMaxSize(100), itsPos(0),
00179     disabled(false)
00180 {
00181   itsHist.push_back(v);
00182   itsPos = itsHist.size() - 1;
00183 }
00184 
00185 // ######################################################################
00186 void PrefItemStr::set(const std::string& v)
00187 {
00188   ASSERT(itsPos < itsHist.size());
00189   if (itsHist[itsPos] != v)
00190     {
00191       if (itsHist.back() != v)
00192         {
00193           itsHist.push_back(v);
00194           while (itsHist.size() > itsMaxSize)
00195             itsHist.pop_front();
00196         }
00197       itsPos = itsHist.size() - 1;
00198       this->isChanged = true;
00199     }
00200 }
00201 
00202 // ######################################################################
00203 std::string PrefItemStr::get() const
00204 {
00205   ASSERT(itsPos < itsHist.size());
00206   return itsHist[itsPos];
00207 }
00208 
00209 // ######################################################################
00210 std::string PrefItemStr::getName() const
00211 {
00212   ASSERT(itsPos < itsHist.size());
00213 
00214   return sformat("%s#%02"ZU, this->name.c_str(), itsPos);
00215 }
00216 
00217 // ######################################################################
00218 void PrefItemStr::fromString(const std::string& s)
00219 { this->set(s); }
00220 
00221 // ######################################################################
00222 std::string PrefItemStr::toString() const
00223 { return this->get(); }
00224 
00225 // ######################################################################
00226 void PrefItemStr::adjust(int val)
00227 {
00228   ASSERT(itsHist.size() > 0);
00229 
00230   // adjust the current history position by val, wrapping around in
00231   // either direction as necessary
00232 
00233   if (val > 0)
00234     {
00235       itsPos += val;
00236       itsPos = itsPos % itsHist.size();
00237     }
00238   else if (val < 0)
00239     {
00240       const size_t sub = -val;
00241       for (size_t i = 0; i < sub; ++i)
00242         {
00243           if (itsPos == 0) itsPos = itsHist.size() - 1;
00244           else --itsPos;
00245         }
00246     }
00247 
00248   this->isChanged = true;
00249 }
00250 
00251 // ######################################################################
00252 PrefsWindow::PrefsWindow(const std::string& wintitle,
00253                          const SimpleFont& font)
00254   :
00255   itsWinTitle(wintitle),
00256   itsWin(0),
00257   itsFont(font),
00258   itsItems(),
00259   itsEditBuffer(),
00260   itsCurrentItem(0),
00261   itsState(SCROLLING),
00262   itsNameWidth(1),
00263   itsNumWidth(10),
00264   itsDirty(true)
00265 {}
00266 
00267 // ######################################################################
00268 PrefsWindow::~PrefsWindow()
00269 {
00270   delete itsWin;
00271 
00272   while (itsOwnedItems.size() > 0)
00273     {
00274       delete itsOwnedItems.back();
00275       itsOwnedItems.pop_back();
00276     }
00277 }
00278 
00279 // ######################################################################
00280 void PrefsWindow::setValueNumChars(const int n)
00281 {
00282   if (n < 1)
00283     LFATAL("expected n to be at least 1, but got n=%d", n);
00284 
00285   if (n != itsNumWidth)
00286     {
00287       itsNumWidth = n;
00288       itsDirty = true;
00289     }
00290 }
00291 
00292 // ######################################################################
00293 void PrefsWindow::setFont(const SimpleFont& font)
00294 {
00295   if (font != itsFont)
00296     {
00297       itsDirty = true;
00298       itsFont = font;
00299     }
00300 }
00301 
00302 // ######################################################################
00303 void PrefsWindow::update()
00304 {
00305   if (itsItems.size() == 0)
00306     // don't show any window or try to handle any events if we don't
00307     // have any pref items
00308     return;
00309 
00310   if (itsWin == 0)
00311     {
00312       this->redraw();
00313       LINFO("Redraw");
00314       ASSERT(itsWin != 0);
00315       return;
00316     }
00317 
00318   for (size_t i = 0; i < itsItems.size(); ++i)
00319     {
00320       if (itsItems[i]->isValueChanged())
00321         {
00322           itsDirty = true;
00323 
00324           // don't break the loop early here even though we already
00325           // know that itsDirty will be set to true, because we want
00326           // to call isValueChanged() on every item so that we clear
00327           // the 'isChanged' field in ALL items that have changed,
00328           // rather than just the first one; otherwise, we will only
00329           // catch one change per update() and we will end up with a
00330           // series of multiple redraws when we could have done it all
00331           // in a single redraw call
00332         }
00333     }
00334 
00335   KeySym ks;
00336   std::string s;
00337   while ((ks = itsWin->getLastKeySym(&s)) != NoSymbol)
00338     {
00339       if (this->handleKeysym(ks, s))
00340         itsDirty = true;
00341     }
00342 
00343   XButtonEvent ev;
00344   while (itsWin->getLastButtonEvent(&ev))
00345     {
00346       if (this->handleButtonPress(&ev))
00347         itsDirty = true;
00348     }
00349 
00350   if (itsDirty)
00351     {
00352       this->redraw();
00353     }
00354 
00355   // postcondition for update() is that we leave ourselves in a
00356   // non-dirty state...
00357   ASSERT(itsDirty == false);
00358 }
00359 
00360 // ######################################################################
00361 void PrefsWindow::addPrefForParam(ModelParamBase* mp, ModelComponent* comp)
00362 {
00363   // OK, OK, we are relying on a bunch of ugly dynamic_cast<>s here;
00364   // but somehow we have to dig down and figure out what the
00365   // underlying numeric type is, in order to be able to support
00366   // numeric adjust() on the pref items that we create. A possible
00367   // alternative would be to just move the adjust() function into the
00368   // ModelParamBase class, in which case we wouldn't need to care
00369   // about the exact type here; we could just treat everything as a
00370   // ModelParamBase.
00371 
00372 #define HANDLE_NUM_PARAM_TYPE(T)                                        \
00373       if (OModelParam<T>* p = dynamic_cast<OModelParam<T>*>(mp))        \
00374         {                                                               \
00375           new PrefItemMPNum<OModelParam<T>, T>                          \
00376             (this, p, comp,                                             \
00377              /* takeOwnership = */ true);                               \
00378           return;                                                       \
00379         }                                                               \
00380       if (NModelParam<T>* p = dynamic_cast<NModelParam<T>*>(mp))        \
00381         {                                                               \
00382           new PrefItemMPNum<NModelParam<T>, T>                          \
00383             (this, p, comp,                                             \
00384              /* takeOwnership = */ true);                               \
00385           return;                                                       \
00386         }
00387 
00388   HANDLE_NUM_PARAM_TYPE(int);
00389   HANDLE_NUM_PARAM_TYPE(float);
00390   HANDLE_NUM_PARAM_TYPE(double);
00391   HANDLE_NUM_PARAM_TYPE(unsigned int);
00392   HANDLE_NUM_PARAM_TYPE(long);
00393   HANDLE_NUM_PARAM_TYPE(unsigned long);
00394   HANDLE_NUM_PARAM_TYPE(byte);
00395   HANDLE_NUM_PARAM_TYPE(bool);
00396 
00397   // ok, the param doesn't correspond to a known numeric type, so
00398   // let's just treat it as a string param:
00399   new PrefItemMPStr(this, mp, comp, /* takeOwnership = */ true);
00400 
00401 #undef HANDLE_NUM_PARAM_TYPE
00402 }
00403 
00404 // ######################################################################
00405 void PrefsWindow::addPrefsForComponent(ModelComponent* comp, bool recurse)
00406 {
00407   if (recurse)
00408     {
00409       const uint n = comp->numSubComp();
00410       for (uint i = 0; i < n; ++i)
00411         this->addPrefsForComponent(comp->subComponent(i).get(), recurse);
00412     }
00413 
00414   const size_t n = comp->getNumModelParams();
00415   for (size_t i = 0; i < n; ++i)
00416     {
00417       ModelParamBase* mp = comp->getModelParam(i);
00418 
00419       if (!mp->allowsOnlineChanges())
00420         continue;
00421 
00422       this->addPrefForParam(mp, comp);
00423     }
00424 }
00425 
00426 // ######################################################################
00427 void PrefsWindow::addItem(PrefItem* item, bool takeOwnership)
00428 {
00429   if (item->getName().length() > size_t(itsNameWidth))
00430     itsNameWidth = item->getName().length();
00431 
00432   itsItems.push_back(item);
00433 
00434   if (takeOwnership)
00435     itsOwnedItems.push_back(item);
00436 }
00437 
00438 // ######################################################################
00439 bool PrefsWindow::handleKeysym(KeySym ks, const std::string& s)
00440 {
00441   LDEBUG("keysym is %s", XKeysymToString(ks));
00442 
00443   bool dirty = true;
00444 
00445   if (itsState == EDITING)
00446     {
00447       switch (ks)
00448         {
00449         case XK_Return: case XK_KP_Enter:
00450           // try to accept the new value, then exit EDITING mode
00451           {
00452             if (itsEditBuffer.size() > 0)
00453               {
00454                 itsItems[itsCurrentItem]->fromString(itsEditBuffer);
00455                 itsEditBuffer = "";
00456               }
00457 
00458             itsState = SCROLLING;
00459           }
00460           break;
00461 
00462         case XK_Escape:
00463           // exit EDITING mode without changing the value
00464           {
00465             itsEditBuffer = "";
00466             itsState = SCROLLING;
00467           }
00468           break;
00469 
00470         case XK_Delete: case XK_BackSpace:
00471           // delete the last character in the current edit buffer
00472           {
00473             if (itsEditBuffer.size() > 0)
00474               {
00475                 itsEditBuffer.resize(itsEditBuffer.size() - 1);
00476               }
00477           }
00478           break;
00479 
00480         default:
00481           {
00482              if (s.length() == 1 && isprint(s[0]))
00483               {
00484                 itsEditBuffer += s[0];
00485               }
00486             else
00487               dirty = false;
00488           }
00489           break;
00490         }
00491     }
00492   else if (itsState == SCROLLING)
00493     {
00494       switch (ks)
00495         {
00496         case XK_Up:
00497           itsCurrentItem =
00498             (itsCurrentItem + itsItems.size() - 1) % itsItems.size();
00499           break;
00500         case XK_Down:
00501           itsCurrentItem =
00502             (itsCurrentItem + 1) % itsItems.size();
00503           break;
00504 
00505         case XK_Left:
00506           if (!itsItems[itsCurrentItem]->isDisabled())
00507             itsItems[itsCurrentItem]->adjust(-1);
00508           break;
00509 
00510         case XK_Right:
00511           if (!itsItems[itsCurrentItem]->isDisabled())
00512             itsItems[itsCurrentItem]->adjust(1);
00513           break;
00514 
00515         case XK_less:
00516           if (!itsItems[itsCurrentItem]->isDisabled())
00517             itsItems[itsCurrentItem]->adjust(-10);
00518           break;
00519 
00520         case XK_greater:
00521           if (!itsItems[itsCurrentItem]->isDisabled())
00522             itsItems[itsCurrentItem]->adjust(10);
00523           break;
00524 
00525         case XK_Return: case XK_KP_Enter:
00526           if (!itsItems[itsCurrentItem]->isDisabled())
00527             itsState = EDITING;
00528           break;
00529 
00530         default:
00531           dirty = false;
00532           break;
00533         }
00534     }
00535 
00536   return dirty;
00537 }
00538 
00539 // ######################################################################
00540 bool PrefsWindow::handleButtonPress(XButtonEvent* ev)
00541 {
00542   bool dirty = true;
00543 
00544   if (itsState == SCROLLING)
00545     {
00546       switch (ev->button)
00547         {
00548         case Button1:
00549           {
00550             const int n = (ev->y - 1) / (itsFont.h() + 2);
00551             itsCurrentItem = clampValue(n, 0, int(itsItems.size() - 1));
00552           }
00553           break;
00554 
00555         case Button2:
00556         case Button3:
00557           // do nothing
00558           dirty = false;
00559           break;
00560 
00561         case Button4: // mousewheel-up/scroll-up
00562           if (itsCurrentItem > 0)
00563             --itsCurrentItem;
00564           break;
00565         case Button5: // mousewheel-down/scroll-down
00566           if (itsItems.size() > 0
00567               && itsCurrentItem + 1 < itsItems.size())
00568             ++itsCurrentItem;
00569           break;
00570         }
00571     }
00572 
00573   return dirty;
00574 }
00575 
00576 // ######################################################################
00577 void PrefsWindow::redraw()
00578 {
00579   const Dims d((itsNameWidth + itsNumWidth + 10) * itsFont.w() + 2,
00580                2 + itsItems.size() * (itsFont.h() + 2));
00581 
00582   Image<PixRGB<byte> > img(d, ZEROS);
00583 
00584   for (size_t i = 0; i < itsItems.size(); ++i)
00585     {
00586       std::string line;
00587       PixRGB<byte> col;
00588 
00589       if (i == itsCurrentItem)
00590         {
00591           if (itsState == EDITING)
00592             {
00593               col = PixRGB<byte>(255, 0, 0);
00594 
00595               line = sformat(">> %*s ?? %-*s <<",
00596                              itsNameWidth, itsItems[i]->getName().c_str(),
00597                              itsNumWidth, itsEditBuffer.c_str());
00598             }
00599           else
00600             {
00601               col = itsItems[i]->isDisabled()
00602                 ? PixRGB<byte>(127, 127, 127) : PixRGB<byte>(0, 255, 0);
00603 
00604               line = sformat(">> %*s -> %-*s <<",
00605                              itsNameWidth, itsItems[i]->getName().c_str(),
00606                              itsNumWidth, itsItems[i]->toString().c_str());
00607             }
00608         }
00609       else
00610         {
00611           col =
00612             itsState == EDITING
00613             ? PixRGB<byte>(64, 64, 64)
00614             : PixRGB<byte>(255, 255, 255);
00615 
00616           if (itsItems[i]->isDisabled())
00617             col /= 2;
00618 
00619           line = sformat("   %*s    %-*s   ",
00620                          itsNameWidth, itsItems[i]->getName().c_str(),
00621                          itsNumWidth, itsItems[i]->toString().c_str());
00622         }
00623 
00624       writeText(img, Point2D<int>(1, 1 + i*(itsFont.h()+2)),
00625                 line.c_str(), col, PixRGB<byte>(0, 0, 0), itsFont);
00626     }
00627 
00628   if (itsWin == 0)
00629     itsWin = new XWinManaged(img.getDims(), -1, -1, itsWinTitle.c_str());
00630   else if (itsWin->getDims() != img.getDims())
00631     itsWin->setDims(img.getDims());
00632 
00633   ASSERT(itsWin->getDims() == img.getDims());
00634 
00635   itsWin->drawImage(img);
00636 
00637   itsDirty = false;
00638 }
00639 
00640 // ######################################################################
00641 /* So things look consistent in everyone's emacs... */
00642 /* Local Variables: */
00643 /* mode: c++ */
00644 /* indent-tabs-mode: nil */
00645 /* End: */
00646 
00647 #endif // GUI_PREFSWINDOW_C_DEFINED
Generated on Sun May 8 08:40:41 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3