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