#ifndef ICL_GUI_H #define ICL_GUI_H #include #include #include #include #include #include #include #include #include #include /** \cond */ class QLayout; /** \endcond */ namespace icl{ /** \cond */ class GUIWidget; /** \endcond */ /// Simple but powerful GUI Toolkit for the ICL \ingroup COMMON /** \section INTRO Introduction When working on computer-vision tasks, one whats to have some graphical user interface to show the current images. This can be achieved by the use of the ICLQt's ICLWidget and ICLDrawWidget classes. \n However in many cases this GUI components are not enough, e.g.: - a value shall be adjusted at run-time (using a slider, a spinbox or a simple textfield) - a mode shall be switched (using a radiobutton group or a combobox) - the application shall have a "pause"-button which stops the iteration thread temporarily - ... Actually all this capabilities can be implemented using the powerful Qt-Framework. But you will encounter some tricky problems then: - You must synchronize Qt's even-loop with the working thread - You have to handle user interaction using Qt's slots and signals - You have to create QObject classes using the Q_OBJECT macro and run Qt's meta-object- compiler (moc) on it. (Yet this isn't possible inside of an "examples" or "application"- folder of the ICL makefile structure) - ... - And not at least: You have to layout your GUI using QLayouts, QWidgets and QSizePolicys Of course, a Qt-nerd will say "OK, but where is the problem!", but most of the ICL- users including me long for a framework that allows you to "create a slider, and access its current value" with not more than 5 lines of additional code.\n The new ICL-GUI API enables you to create a slider with one expression, and to access its value with another expression. The following example will demonstrate this: \code #include using namespace icl; int main(int n, char**ppc){ QApplication app(n,ppc); GUI g("slider(0,100,50)[@out=the slider value]"); g.show(); return app.exec(); } \endcode To access the current value of this slider from the working thread you just have to call: \code int val = g.getValue("the slider value"); \endcode This will show the following GUI (with the very beautiful gnome desktop) \image html Image01_ASlider.jpg \section CG Complex GUI's Ok! now let us create some more complex GUI: \code #include #include // create a nice image, which should be shown (using iclQuick) Img8u image = cvt8u(scale(create("parrot"),0.2)); int main(int n, char**ppc){ QApplication app(n,ppc); // create the top level gui component (a vertical layouted box here) // gui objects can be filled with other objects using the "<<" operator GUI gui("vbox"); // add a slider to the hbox (range 0-100, current value=50) gui << "slider(0,100,50)[@out=width]"; // add another slider gui << "slider(0,100,50)[@out=height]"; // add an image component (ensure its size to be at least 10x10 cells // (a cell has a size of 15x15 pixels) The @handle=... statement makes // this component allocate a handle for it in its parent GUI object gui << "image[@handle=image@minsize=10x10]"; // show the top level gui (this will create all components internally // and recursively. Before the top-level "show" function is called, // the gui data is inaccessible gui.show(); // get the images drawing context (as an ImageHandle) and induce it // to show a new image gui.getValue("image") = ℑ // start Qt's event loop return app.exec(); } \endcode \image html Image02_FirstImage.jpg \section SBS Step by Step In the last section we have seen, that the GUI API of the ICL helps us to create gui-components very quickly and conveniently. Furthermore each GUI object provides a comfortable access function (getValue) to access the current values of all embedded GUI components.\n In this section we'll have a look on the general syntax for the creation of GUI components.\n Each GUI definition string can be divided in 3 parts, which are just concated: -# a type string -# a comma separated list of mandatory and type dependent parameters (in round brackets) -# a list of optional and general parameters (in angular brackets)
      TYPE(TYPE_DEPENDEND_PARAMS)[GENERAL_PARAMS]
      
Examples: \code GUI g1("slider(0,100,50)"); GUI g2("combo(red,green,blue,yellow,magenty,!cyan,white,black)[@size=3x1@label=colors]"); GUI g3("image"); GUI g3("draw[@minsize=20x20]"); GUI g4("label(input-image)"); ... \endcode As the examples show, the TYPE part alone is mandatory; the other 2 parts can be omitted in some cases: \subsection TYPES The Type String The mandatory type string defines what type of widget should be created. The correct syntax of the TYPE_DEPENDEND_PARAMS list depends on this string. Possible type strings are: - hbox a horizontal layouted container - vbox a vertical layouted container - border a vertical layouted container with a labeled border - button a push button - buttongroup a set of radio buttons (exclusive) - togglebutton a button that can be toggled and untoggled - label a label displaying static or dynamic content (integers, floats and string) - slider a integer-valued slider with given range - fslider a float-valued slider with given range - int an input field accepting integers within a given range - float an input field accepting float values within a given range - string a string input field accepting string with given max. length - disp a matrix of labels - image an ICLWidget component - draw an ICLDrawWidget component - draw3D an ICLDrawWidget3D component - combo a combo box - spinner a spin box (integer valued with given range) - fps label component showing current frames per second - multidraw tabbed widget of draw widget components accessible via string index \subsection TYPEPARAMS Type Dependent Parameters The 2nd part of the GUI definition string is a comma separated list of type dependent parameters. This list is bounded by round brackets; if it is empty you can write "()" or just omit this list completely. Some types define default values for some given parameters (in C++-style). The following list shows all components introduced above with their individual parameter list syntax: - hbox\n No params here! - vbox\n No params here! - border(string LABEL)\n LABEL defines the border label of that layout widget. Please Note: that labels and borders can also be added using a "@label=xxx" token in the list of general parameters. - button(string TEXT)\n TEXT is the button text - buttongroup(string TEXT1,string TEXT2,...)\n The parameters must be a comma separated list of strings. Each token of that list defines a single radio button with that text. One of these texts may have a "!"-prefix (e.g. "buttongroup(button1,!button2,button3)[...]". The corresponding button will be selected initially. If no "!"-prefix can be found, the first button is selected initially. - togglebutton(string UNTOGGLED_TEXT, string TOGGLED_TEXT)\n A toggle button switches its text depended on its current "toggle-state". If the button should not switch its text, UNTOGGLED_TEXT must be equal to TOGGLED_TEXT. - label(string TEXT="")\n Each label component can be used to show dynamic as well as static content. A label can be initialized with a given string (or an int/float as string). Later on this content can be changed by accessing this label from outside the GUI object. - slider(int MIN,int MAX,int CURRENT)\n A slider is created by a given range {MIN,...,MAX} and a given initial value CURRENT - fslider(float MIN,float MAX,float CURRENT) A fslider is created by a given range {MIN,...,MAX} and a given initial value CURRENT - int\n An integer input field is created by a given range {MIN,...,MAX} and a given initial value CURRENT. The text field will only allow inputs that are inside of this range. - float\n A float input field is created by a given range {MIN,...,MAX} and a given initial value CURRENT. The text field will only allow inputs that are inside of this range. - string(int MAX_LEN)\n A string input field is created with a given max length of allowed input-strings. - disp(unsigned int NW,unsigned int NH)\n Creates a display matrix of size NW x NH. To avoid empty matrices, NW*NH must be > 0. Each label of this matrix can later on be accessed to display integers, floats and strings. - image\n No parameters here! - draw\n No parameters here! - draw3D\n No parameters here! - combo(string ENTRY1,string ENTRY2,...)\n Creates a combox with given entries. The entry list is comma separated and must have at least on element. - spinner(int MIN,int MAX,int CURRENT)\n An integer valued Spin-Box with a given range {MIN,...,MAX} and a given initial value CURRENT. - fps(int TIME_WINDOW_SIZE)\n A Label component showing current frames per second averaged over last TIME_WINDOW_SIZE time steps. - multidraw(string buffermode=!one, string buffermethod=!deepcopy, CommaSepListOfTabs)\n Creates a tabbed widget containing an image widget on each tab to visualize multiple images simultaneously (selectable by mouse using tabs). Internally this is achieved using a single DrawWidget which is set up with different images. The calling variable buffermode (possible values are "!all" or "!one") allows to to set up the widget to buffer all images internally (dependent on value of buffermethod either using ImageBase::deepCopy (value "!deepcopy") or using a shallow pointer copy (value "!shallowcopy") ) or to buffer images only, when corresponding tab is really shown actually. Note that obviously the control parameters begin with a '!'-character!\n The multidraw component provides a powerful handle, which implements the array-index operator([]) with std::string-argument to manipulate the content of a certain tab only.\n If your application runs at high frame rate (e.g. 15Hz), it will be sufficient to use default settings (buffermode=one). Otherwise, if application runs slowly (e.g. only 2Hz, this) it will become more responsive if buffermode is set to "all". If images displayed are held permanently, it will speed up performance if buffermethod is set to "shallowcopy" then. \subsection GP General Parameters The 3rd part of the GUI definition string is a list of so called general params. "General" means here, that these params are available for all components, whereas some of these parameters must be compatible to the corresponding component.\n The list of general parameters is not comma separated because some of the parameter values are comma separated lists themselves. Instead, each entry must begin with the "@"-character and it ends with the following "@"-character or the closing angular bracket "]". The set of general params can be subdivided into two parts: - layouting parameters: These parameters affect the layout and the appearance of the widget which is created by that component (\@size=..., \@minsize=..., \@maxsize=.., \@label=...) - input and output interface definition: (\@inp=.., \@out=...\@handle=...) As mentioned above, each embedded GUI components current value(s) or can be accessed from outside by calling the "getValue()"-function template on the top level GUI object. To decide which value/handle should be accessed by that call, each input/and output of a GUI component is associated internally with a string-identifier. In addition each component can be controlled by a so called GUIHandle object which is implemented for each provided GUI component. By default, a gui component "xyz" provides a Handle class "XyzHandle". This handle is also allocated in the top-level GUI objects Data-Store, if the "@handle=NAME" token was defined in the definition string. The handles "NAME" is used as string identifier for the GUI-DataStore. (see the next subsection for more details and some examples) \subsection IO Input- and Output-Interface Depend on the type of a GUI component, the top level GUI gets additional input and output "pins". (Note: input pins have been replaced by the GUIHandles, but they remain in the API.) In contrast to software frameworks like Neo/NST or TDI, no explicit connection must be established to access a GUI objects input/and output data. Instead, a component allocates a mutex-locked variable inside of a so called DataStore that is created by its top level GUI component, and updates this variable any time a Qt-GUI-Event on this components occurs. Let's have a look on a short example to understand this better: \code #include using namespace icl; int main(int n, char**ppc){ QApplication app(n,ppc); // create a new slider component with range [0,100], initialized to 50 // we name its output "the slider value" GUI g("slider(0,100,50)[@out=the slider value]"); // Yet, g is just a GUI-Definition - no QSlider was created internally and no // no slider data can be accessed // make g visible, This will create all embedded Qt-Widgets internally and // it will allocate data for each component in the GUIs internal DataStore. g.show(); // now a single integer value is allocated, which is assigned to the // slider value, and which is updated immediately when the slider is moved. // The GUI's getValue() template function can now be used to get a reference // (or a pointer) to this slider value int &riSliderValue = g.getValue("the slider value"); // or int *piSliderValue = &g.getValue("the slider value"); return app.exec(); } \endcode Yet, only the most components provide an output pin, however the GUI-definition syntax can be used to define components with N inputs and M outputs.\n As each GUI component has different semantics, the count and the type of it's in- and output pins must be regarded. The following list shows all GUI components and explains their individual in- and output interface:
type handle outputs meaning
vbox BoxHandle 0 -
hbox BoxHandle 0 -
border BorderHandle 0 -
button ButtonHandle 1 type GUIEvent handle for this button (see below!)
buttongroup ButtonGroupHandle 1 type int index of the currently toggled radio button
togglebutton ButtonHandle 1 type bool true=toggled, false=untoggled
label LabelHandle 0 handle for this label (see below!)
slider SliderHandle 1 type int current slider value
fslider FSliderHandle 1 type float current slider value
int IntHandle 1 type int current text input field content
float FloatHandle 1 type float current text input field content
string StringHandle 1 type std::string current text input field content
disp DispHandle 0 handle for the label matrix (see below!)
image ImageHandle 0 handle for the embedded ICLWidget
draw DrawHandle 0 handle for the embedded ICLDrawWidget
draw3D DrawHandle<3D/TD> 0 handle for the embedded ICLDrawWidget3D
combo ComboHandle 1 type std::string current selected item
spinner SpinnerHandle 1 type int current value
fps FPSHandle 0 -
multidraw MultiDrawHandle 0 handle of [string]-accessible ICLDrawWidgets
\section HVV Handles vs. Values In some cases accessing a components value is not enough. E.g. if a "label" component should be used to show another string, of if a slider should be set externally to a specific value. \n To facilitate working with GUI objects, each component is able to allocate a so called "handle"-object of itself in its parent GUI-objects data store. This will be done if a "@handle=.." token is found in the general params list of this component definition (general params are inside angular brackets).\n All handle classes are derived from the GUIHandle template class, which provides functionalities for storing a T-pointer (template parameter) and which offers functions to access this pointer (the *operator). A ButtonHandle for example inherits the GUIHandle, so it wraps a QPushButton* internally. For a more convenient working process, each handle has some special functions which provide and abstract and direct access of the underlying class without knowing it. \code #include #include using namespace icl; int main(int n, char**ppc){ QApplication app(n,ppc); // create a new slider with output value and a handle id string GUI g("slider(0,100,50)[@out=the slider value@handle=the slider handle]"); // show it g.show(); // access the sliders value int &val = g.getValue("the slider value"); // val cannot be used to set a new slider position val = 5; // the slider will not change // accessing the Sliders handle SliderHandle h = g.getValue("the slider handle"); // the handle can be used to affect the underlying QSlider component h = 5; // And the handle can be used to access the QSlider directly // To work with this slider, the \#include statement is mandatory // because the SliderHandle uses the QSlider class forward declared. QSlider *sl = *h; // now the slider itself can be manipulated sl->setValue(7); // enter qts event loop return app.exec(); } \endcode The following subsection shows some examples for different GUIHandles. \subsection LH LabelHandles Labels can be used to show strings, integers and floats \code #include #include int main(int n, char **ppc){ QApplication app(n,ppc); /// create a container GUI gui("vbox"); // add some labels gui << "label(Text 1)[@handle=L1@size=6x1@label=Label 1]"; gui << "label(Text 2)[@handle=L2@size=6x1@label=Label 2]"; gui << "label(Text 3)[@handle=L3@size=6x1@label=Label 3]"; // show the gui gui.show(); // access the sliders value gui.getValue("L1") = "A New Text"; gui.getValue("L2") = 5; gui.getValue("L3") = 3.1415; return app.exec(); } \endcode \image html Image05_LabelDemo.jpg \subsection DISPC DispHandles The disp component implements a 2D-Array of label components (e.g. to visualize a matrix). It makes use of the LabelHandle class to provide an interface of type DispHandle which wraps a matrix of LabelHandles using the ICLUtils/ SimpleMatrix template class. \n Matrix elements - of type "LabelHandle&" - can be addressed using the [][]-operator (see SimpleMatrix). \code #include using namespace icl; int main(int n, char**ppc){ QApplication app(n,ppc); // create top-level container GUI gui; // add a new 4x4 display component // with a bordered label, a min. size of 14 x 6 cells and // handle id mydisp gui << "disp(4,4)[@label=My Display Component@minsize=14x6@handle=mydisp]"; // show this gui (remember: QWidgets are created here, // and the interface data is allocated gui.show(); // Extract the displays handle DispHandle &m = gui.getValue("mydisp"); // assign the first row with string m[0][0] = ""; m[1][0] = "column 1"; m[2][0] = "column 2"; m[3][0] = "column 3"; // assign the first column with strings m[0][1] = "row 1"; m[0][2] = "row 2"; m[0][3] = "row 3"; // assign the rest with integers for(int x=1;x<4;x++){ for(int y=1;y<4;y++){ m[x][y] = 10*x+y; } } // enter Qt's event loop return app.exec(); } \endcode \image html Image03_DispExample.jpg \subsection BHA Button Handles The next interface type, that should be introduced here in detail, is the ButtonHandle type, which is used for the "button" type interface. The button itself produces no data; it can only be accessed by using its handle. In contrast to other components, simple buttons are producing an event instead of some data. When accessing the button from the working thread, you don't need the information if the button is pressed at this time, but you may want to know if it was pressed since the last test. You might say that a simple boolean variable would be sufficient to handle this information, but the following stays doubtful: "Who resets this boolean variable, and when?". To avoid this problem the ButtonHandle data type, can be triggered (if the button is pressed) an it can be checked using its wasTriggered() function, which returns if the event was triggered and resets the internal boolean variable to false. The following example code illustrates this: \code #include #include using namespace icl; /// Use a static gui pointer (accessible in main an in the Thread class GUI *gui = 0; // create a Thread class, which implements the working loop. For this example // this loop will test each second if the button was pressed or not class DemoThread : public Thread{ virtual void run(){ while(true){ if(gui->getValue("b").wasTriggered()){ printf("button was triggered! \n"); }else{ printf("button was not triggered! \n"); } sleep(1.0); } } }; int main(int n, char**ppc){ // create a QApplication object QApplication app(n,ppc); // create the top level container gui = new GUI; // add a button to this container (*gui) << "button(Click me!)[@handle=b]"; // show it gui->show(); // create the working loop thread DemoThread t; // start it t.start(); // enter Qt's event loop return app.exec(); } \endcode \image html Image06_ButtonDemo.jpg \subsubsection EAB Events and Buttons In some applications it might be necessary to associate an event to a button click, which is called immediately if the button is clicked. This is quite useful e.g. to interrupt the current working thread. However this feature is more complex, then the claim of the ICL GUI API can stand, this feature is a "must-have" and it is wrapped into the GUI API to avoid that it is implemented many times elsewhere.\n First we have to decide how we formalize an "event". Here an event is a callback-function or a function object which can be triggered (the function is called). To differentiate between these Callback-Objects and functions, two type-definition were made inside of the ButtonHandle class: \code /// Special Utility class for handling Button clicks in the ICL GUI API \ingroup HANDLES class ButtonHandle : public GUIHandle{ public: /// type definition for a callback function typedef void (*callback)(void); /// Interface for callback objects (functors) struct Callback{ /// Destructor virtual ~Callback(){} /// call back function (pure virtual) virtual void operator()() = 0; }; ... \endcode Each button handle provides two functions named "registerCallback(..)" to add callbacks to a button, which are called exactly when the button is pressed. The following example extends the example above by a simple exit button: \code #include #include using namespace icl; /// Use a static gui pointer (accessible in main an in the Thread class GUI *gui = 0; // create a Thread class, which implements the working loop. For this example // this loop will test each second if the button was pressed or not class DemoThread : public Thread{ ... see above! }; // a simple callback function (type "void (*callback)(void)" void exit_callback(void){ printf("exit callback was called \n"); exit(0); } int main(int n, char**ppc){ // create a QApplication object QApplication app(n,ppc); // create the top level container gui = new GUI; // add a button to this container (*gui) << "button(Click me!)[@handle=b]"; (*gui) << "button(Exit!)[@handle=exit]"; // show it gui->show(); // register the call back function gui->getValue("exit").registerCallback(exit_callback); // create the working loop thread DemoThread t; // start it t.start(); // enter Qt's event loop return app.exec(); } \endcode \subsection EMB Embedding external QWidgets In some cases it might be necessary to embed QWidgets, which are not supported by the GUI-API. For this, the two box-components ("hbox" and "vbox") do also provide a special BoxHandle which wraps the underlying QWidget to provide access to it and its current layout.See the following example for more details: \code #include #include using namespace icl; int main(int n, char**ppc){ QApplication app(n,ppc); // create the top level gui component GUI gui("vbox"); // add some buttons with funny texts gui << "button(click me)[@out=click0]"<< "button(no !click me)[@out=click1]"<< "button(no no no! me!)[@out=click2]"; // add the container widget (comma separated list of output identifiers) gui << "vbox[@label=Progress@handle=conti]"; // create the gui (this allocates input and output data) gui.show(); // create a new QProgressBar using the containers widget as parent QProgressBar *pb = new QProgressBar(0); pb->setValue(50); // add it to the widgets layout gui.getValue("conti").add(pb); // enter Qt's event loop return app.exec(); } \endcode \image html Image04_ExternalWidget.jpg \subsection GUIInGUI Embedding other GUIs GUI objects can be embedded into other GUI objects by using the optional constructor parameter "parent" of the constructor. This can be very useful when creating re-usable GUI classes by using the ICL GUI API. To demonstrate this, we adapt the example above: \code #include using namespace icl; int main(int n, char**ppc){ QApplication app(n,ppc); // create the top level gui component GUI gui_1("hbox"); // add some buttons vertically alligned gui_1 << ( GUI("vbox") << "button(click me)[@out=click0]" << "button(no !click me)[@out=click1]" << "button(no no no! me!)[@out=click2]" ); // add a container widget gui_1 << "vbox[@label=GUI 2@handle=box-handle]"; // create the gui (this allocates input and output data) gui_1.show(); /// create to "to be embeded gui (with given parent) GUI gui_2("vbox",*gui_1.getValue("box-handle")); /// add something to this gui gui_2 << "label(GUI 2!!)"; /// finally create the underlying Qt-stuff gui_2.show(); // enter Qt's event loop return app.exec(); } \endcode \image html Image07_GUIInGUI.jpg \subsection LOCK Locking Some interface types involve the danger to be corrupted when accessed by the working thread as well as by Qts GUI-Thread. This is true for all complex data types like strings and so on. To avoid errors rising on multi-threaded data access, call the GUIs lockData() and unlockData() function. \subsection PERF Performance Although the implementation of the GUIs data access function is highly optimized, it costs some processor cycles to pick a value of a specific component. Internally all values are stored in a large hash-map, which allows the getValue template function to find a specific entry quickly (even if the GUI consist of 100 components which is not realistic). If a value is found, the RTTI (c++ Run-Time Type identification) interface is used, to decide whether the correct template parameter was used, otherwise an error occurs, and the program is aborted.\n So it is much faster to extract a value from a gui only once (at reference or pointer) and work with this reference later on. */ class GUI{ public: /// cell width (all sizes are given in this unit) static const int CELLW = 20; /// cell height (all sizes are given in this unit) static const int CELLH = 20; /// default constructor GUI(const std::string &definition="vbox", QWidget *parent=0); /// copy constructor GUI(const GUI &gui,QWidget *parent=0); /// Destructor virtual ~GUI(){} /// stream operator to add new widgets virtual GUI &operator<<(const std::string &definition); /// stream operator to add new other GUIs virtual GUI &operator<<(const GUI &g); /// wraps the data-stores allocValue function template inline T &allocValue(const std::string &id, const T&val=T()){ return m_oDataStore.allocValue(id,val); } /// wraps the data-stores allocArray function template inline T *allocArray(const std::string &id,unsigned int n){ return m_oDataStore.allocArray(id,n); } /// wraps the datastores release function template inline void release(const std::string &id){ m_oDataStore.release(id); } /// wraps the datastores getValue function template T &getValue(const std::string &id, bool typeCheck=true){ return m_oDataStore.getValue(id,typeCheck); } /// wraps the datastores getArray function template inline T* getArray(const std::string &id, int *lenDst=0){ return m_oDataStore.getArray(id,lenDst); } /// internally creates everything virtual void show(); /// internally locks the datastore inline void lockData() { m_oDataStore.lock(); } /// internally unlocks the data store inline void unlockData() { m_oDataStore.unlock(); } /// waits for the gui to be created completely void waitForCreation(); /// returns the GUI internal dataStore const DataStore &getDataStore() const { return m_oDataStore; } /// Callback helper class: Default implementation calls a callback function class Callback{ /// typedef to wrapped function (only for default implementation) typedef void (*callback_function)(void); /// internally used default callback function callback_function m_func; protected: /// create a new callback object Callback():m_func(0){} public: /// Default implementations constructor with given callback function Callback(callback_function func):m_func(func){} /// vitual execution function virtual void exec(){ if(m_func) m_func(); } }; typedef SmartPtr CallbackPtr; /// registers a callback function on each component /** @param cb callback to execute @param handleNamesList comma-separated list of handle names ownership is passed to the childrens; deletion is performed by the smart pointers that are used... */ void registerCallback(CallbackPtr cb, const std::string &handleNamesList); /// removes all callbacks from components void removeCallbacks(const std::string &handleNamesList); private: void create(QLayout *parentLayout,QWidget *parentWidget, DataStore *ds); /// own definition string std::string m_sDefinition; std::vector m_vecChilds; GUIWidget *m_poWidget; DataStore m_oDataStore; bool m_bCreated; QWidget *m_poParent; }; } #endif