559 HANDS-ON TUTORIAL:

INTRODUCTION TO FLTK

 

 

 

 

Since FLTK is a graphical user interface toolkit, its main purpose is to create interfaces.  In most cases, this involves creating windows with widgets ( buttons, sliders, dials, browsers, etc. ) on them.  FLTK provides a way to create an event driven program based on these interfaces.  Although FLTK provides many classes for specific widgets, one of its beauties lies in the fact that you can inherit from these classes.  Through inheritance, you can override certain functionalities of each widget, and configure it to suit your own application, without the need to code everything from scratch.  As you go through this tutorial, keep in mind that these are specific examples to help you see and learn FLTK.  The most important piece of information you should come away from this tutorial with is how to build interfaces.  It is not as important that you create the specific files that are mentioned in this tutorial.

 

The first thing we are going to need in order to produce an interface is a Visual C++ workspace.

Using the FLTK Tutorial (http://www.cs.wisc.edu/~cs559-1/Notes/fltktut.html)  from last year, create a new FLTK Work Space in Visual C++ 6.0.

 

BRINGING UP A WINDOW

 

Now that you have created a workspace to build interfaces, let’s create a very basic interface.  The most basic interface consists of just a window by itself.

 

1.      Choose File->New

2.      Click on the Files Tab at the top of the dialog box.  Click on C++ Source file.  Name the file “BasicWindow” on the right hand side of the dialog box and press the ok button.

 

3.      At this point you should see a cursor blinking in the editor window.  If not, click on the FileView tab in the workspace window.  Expand ( if necessary ) the source files folder.  Double-click on BasicWindow.cpp.

 

4.      This is where you need to type all the necessary code for the BasicWindow.cpp file.  As with any C/C++ program, you need a main function to run the program.  Type the following lines of code into this file.

 

#include <FL/Fl.H>

#include <FL/Fl_Window.H>

 

int main()

{

        Fl_Window b(20, 20, 400, 400,"Title for our window");

        b.show();              

        return Fl::run();

}

 

5.      At this point, save the file by either choosing File->Save or hitting Ctrl-S on the keyboard. 

 

6.      Now that we have our file saved, we can compile our program.  To compile the program, choose Build->Build [project name].exe or press F7.

 

7.      Now, to execute the program, choose Build-> Execute [project name].exe or press Ctrl-F5 on the keyboard.

 

You should see two windows appear.  One window is the console window which will have an MS-DOS symbol and the project path at the top.  It will have a blinking cursor in it as well.  The other window that will appear is your FLTK window that you have just created.  The console window is where you will see all of your output from your program that you generate using cout or printf.  The FLTK window is your Fl_Window that was just created.  To close the Fl_Window, either press the Esc key or press the X is the upper right corner of the window.

 

 

HANDLING MOUSE EVENTS

 

Now that you can bring up a basic FLTK window, it is imperative that you be able to handle certain events.  Mouse events are extremely important to handle because it is the most common input device used to interface with GUIs.   

 

1.      Create a new C++ Header file called EventWindow.

 

2.      Type the following lines code into this file, or copy them off of the demo web page. ( www.cs.wisc.edu/~pingelm/demo.html )

Take note that the EventWindow class is inheriting from the Fl_Window class.  The Fl_Window class is the widget that FLTK provides for us.  Since we know that we want to treat mouse events differently, we are going to want to override the default code for the handle() method.  This is what makes FLTK so powerful.  We don’t have to spend our time writing windowing code.  We simply want to add additional code that specifies what to do when a mouse event occurs.

 

#ifndef __EVENTWINDOW_H

#define __EVENTWINDOW_H

#include <FL/Fl_Window.H>

#include <FL/Fl.H>

#include <stdio.h>

 

class EventWindow: public Fl_Window

{

        private:

                int handle_mouse(int event, int button, int x, int y);

        public:

                EventWindow(int width, int height);

                int handle(int e);

};

 

#endif

 

 

3.      Create a new C++ Source file called EventWindow.

4.      Copy the following lines of code into this file or copy them off of the demo web page ( www.cs.wisc.edu/~pingelm/demo.html )

Again, take note that the constructor for the EventWindow calls the constructor for the Fl_Window.  We are not going to change anything about the window except for how it handles its events.

 

#include "EventWindow.h"

char * foo;

 

EventWindow::EventWindow(int width, int height)

:Fl_Window(width, height, "")

{      

        label("Demo Window for CS 559");

 

}

 

 

int EventWindow::handle(int event)

{

        switch (event) {

                       

                        case FL_PUSH:

                        case FL_RELEASE:

                        case FL_DRAG:

                        case FL_MOVE:

                                        return handle_mouse(event,Fl::event_button(), Fl::event_x(),Fl::event_y());          

                        case FL_FOCUS:

                                        label ("Gained focus");

                                        damage(1);

                                        return 1;

                        case FL_UNFOCUS:

                                        label ("Lost focus");

                                        damage(1);

                                        return 1;

                        default:

                                        return Fl_Window::handle(event);

        };

}

 

 

 

int EventWindow::handle_mouse(int event, int button, int x, int y) {

 

        if (foo) delete [] foo;

        foo = new char[100];

    int ret = 0;

    switch ( button ) {

                        case 1: // LMB

                                        ret = 1;

                                        // Based on the action, print the action and

                                        // coordinates where it occurred.

                                        if ( event == FL_PUSH ) {

                                                        sprintf(foo,"LMB PUSH ( %d , %d )",x,y);

                                                        label(foo);

                                                        damage(1);

                                        }

                                        else if (event == FL_DRAG ) {

                                                        sprintf(foo,"LMB Drag ( %d , %d )",x,y);

                                                        label(foo);

                                                        damage(1);

                                        }

                                        else if ( event == FL_RELEASE ) {

                                                        sprintf(foo,"LMB Release ( %d , %d )",x,y);

                                                        label(foo);

                                                        damage(1);

                                        }

 

                                        break;

 

                        case 2: // MMB

                                        ret = 1;

                                        // Based on the action, print the action and

                                        // coordinates where it occurred.

                                        if ( event == FL_PUSH ) {

                                                        sprintf(foo,"MMB Push ( %d , %d )",x,y);

                                                        label(foo);

                                                        damage(1);

                                        }

                                        else if (event == FL_DRAG ) {

                                                        sprintf(foo,"MMB Drag ( %d , %d )",x,y);

                                                        label(foo);

                                                        damage(1);

                                        }

                                        else if ( event == FL_RELEASE ) {

                                                        sprintf(foo,"MMB Release ( %d , %d )",x,y);

                                                        label(foo);

                                                        damage(1);

                                        }

                                        break;

                        case 3: // RMB

                                        ret = 1;

                                        // Based on the action, print the action and

                                        // coordinates where it occurred.

                                        if ( event == FL_PUSH ) {

                                                        sprintf(foo,"RMB Push ( %d , %d )",x,y);

                                                        label(foo);

                                                        damage(1);

                                        }

                                        else if (event == FL_DRAG ) {

                                                        sprintf(foo,"RMB Drag ( %d , %d )",x,y);

                                                        label(foo);

                                                        damage(1);

                                        }

                                        else if ( event == FL_RELEASE ) {

                                                        sprintf(foo,"RMB Release ( %d , %d )",x,y);

                                                        label(foo);

                                                        damage(1);

                                        }

                                        break;

                        }

        return ret;

}

 

5.      Delete BasicWindow.cpp from the project workspace by selecting it in the workspace window and pressing the Delete key.  Note, this does not delete the file from your directory, it only removes it from the project.

 

6.      Create a new C++ source file called EventWindowMain.

 

7.      Copy the following lines of code into this file or get them off of the demo web page ( www.cs.wisc.edu/~pingelm/demo.html ).

 

#include <FL/Fl.H>

#include <FL/Fl_Window.H>

#include "EventWindow.h"

 

int main()

{

        EventWindow b(260, 260);

        b.show();

        return Fl::run();

}

 

Now, using the previously mentioned methodology, build and run this application. 

Watch the label change as it corresponds to what you’re doing with the mouse.  Try clicking on the window with each mouse button.  What happens as you drag the mouse?  What occurs as you minimize and restore the window?  Take a look at the code.   See if you can follow what is occurring on the screen and how it is being accomplished in the code. 

 

HANDLING KEYBOARD EVENTS

 

 

Another useful way to obtain information from the user is via the keyboard.  FLTK provides a pretty simple way to handle keyboard events.  We’ve already seen how the handle function handles mouse events, now let’s take a look at how the handle function may incorporate keyboard events as well. 

 

  1. In the EventWindow.h file, add the following function prototype underneath the prototype for handle_mouse.

 

int handle_key(int event, int key);

 

  1. Add the following case statement into the switch statement in the handle function in EventWindow.cpp.

 

case FL_KEYBOARD:  return handle_key(event,Fl::event_key());

 

  1. Add the following function body at the end of the EventWindow.cpp file.

 

 

int EventWindow::handle_key(int event, int key) {

                switch ( key ) {

                case 'c':  label ("letter c was depressed");

                              damage(1);

                                return 1;

                default:  label ("Nothing to do!");

                                damage(1);

                                printf("nothing to do\n");

                                return 1;

                }

}

 

  1. Recompile the project and run the application again.  Note what happens when you depress a key on the keyboard.  What happens when the ‘c’ key is depressed.

 

 

  1. Try altering some of the lines of code and see what happens.

 

 

ADDING BASIC COMPONENTS TO A WINDOW

 

At this point you’ve seen how to handle events, and how to fire up a quick interface.  The one thing that remains is the ability to add some basic components to your windows.  So, let’s go through how to add a few buttons to a window and register callbacks for those buttons. 

 

  1. Remove all files from your current project space by selecting them with the mouse and pressing the Delete key.

 

  1. Create a new C++ source file called ButtonDemoMain.

 

  1. Copy the following lines of code into this file or get them off of the demo web page ( www.cs.wisc.edu/~pingelm/demo.html ):

 

// FLTK demo program for CS559

// Fall, 2000, Mark Pingel

 

#include <FL/Fl.H>

#include <FL/Fl_Window.H>

#include <FL/Fl_Check_Button.H>

#include <FL/Fl_Light_Button.H>

 

 

// Create pointers to a check button and a light button

Fl_Check_Button* check_b;

Fl_Light_Button* light_b;

 

 

 

// Callback function for the check button.

// Turn the label red and change it to read Checked if the

// check button is checked.

void checkbutton_cb(Fl_Widget* w) {

                Fl_Check_Button* flcb = ((Fl_Check_Button*)w);

                if ( flcb->value() == 1 ) {

                                flcb->labelcolor(FL_RED);

                                flcb->label("Checked");

                               

                }

                else {

                                flcb->labelcolor(FL_CYAN);

                                flcb->label("Check Button");

                }             

                flcb->damage(1);

                flcb->parent()->redraw();

}

 

// Callback function for the light button.

// Turn the label yellow and change it to read Light On if the

// light button is pressed.  A yellow square will also appear in

// the box to the left of the label if selected

void lightbutton_cb(Fl_Widget* w) {

                Fl_Light_Button* fllb = ((Fl_Light_Button*)w);

                if ( fllb->value() == 1 ) {

                                check_b->hide();

                }

                else {

                                check_b->show();

                }             

                fllb->damage(1);

                fllb->parent()->redraw();

}

 

 

int main(int argc, char ** argv) {      

                // Create a window to add our buttons to

                Fl_Window* w = new Fl_Window(190,80);

               

                w->label("559 Demo Window");

                // Begin adding children to this window

                w->begin();

 

                                // Instantiate the check button

                                // x , y , width, height, label

                                check_b = new Fl_Check_Button(25,15,140,20,"Check Button");

                                // Instantiate the light button

                                // x , y , width, height, label

                                light_b = new Fl_Light_Button(25,45,140,20,"Hide Check Button");

 

                                // Make all of the default colors for the labels cyan

                                check_b->labelcolor(FL_CYAN);

                                light_b->labelcolor(FL_CYAN);

 

                                // Register the callbacks for each of the buttons

                                check_b->callback(checkbutton_cb);

                                light_b->callback(lightbutton_cb);

 

                // Stop adding children to this window

                w->end();

                // Display the window

                w->show();

                // Run

                return Fl::run();

               

}

 

Now, using the code that is already there as a model, let’s add another button that controls whether or not the Hide Check Button should be grayed out ( activated ) or not.

 

  1. First off, we need to choose a button type for this button we are going to create.  Fl_Round_Button, Fl_Light_Button, and Fl_Check_Button would all be acceptable choices because they each represent boolean choices.  By that I mean that they are either on or off.

 

  1. Since we’ve already used an Fl_Light_Button and an Fl_Check_Button, we’ll use an Fl_Round_Button to add a little variety.

 

  1. First thing we should always do before adding a new component is consult the documentation for the component at the FLTK documentation site. 
  2. www.fltk.org/doc/toc.html

 

  1. We scroll down until Appendix A – Widget Reference.  There we choose the link to Fl_Round_Button.

 

  1. First thing to look for is what include files need to be included.  This information is usually listed second, right after the class hierarchy.  There, we see for Fl_Round_Button that

 

#include <FL/Fl_Round_Button.H>

 

is listed.  So, at this point we can go back to our code and add this line in the list of includes.

 

  1. Once we have this included, we can procede to creating the button.  By looking at the other button declarations, we can see how to declare a button.  So, we can add the following line of code right after the other button declarations:

 

Fl_Round_Button* round_b;

 

  1. At this point, we have our new button declared, but it doesn’t do anything.  In order for this button to do something, we need to write a callback function.  Callbacks are functions that are called when the value of a widget, a button in this case, changes. In the case of a button, the value of the button changes when it is pressed.  Now, what does this callback function need to include.  A callback function is sent an Fl_Widget pointer of the widget that changed and optionally a pointer to data of some sort.  In this case, no additional data needs to be included, so we’ll just pass in an Fl_Widget pointer to our function.  So, it should look like

 

void round_b_cb(Fl_Widget* w) {

 

}

 

Note: You can name the function anything you like.  I generally try to name the callback function something intuitive.  Otherwise it may become more difficult when you have complex interfaces with over fifty widgets and you’re trying to match up a callback with a widget.

 

  1. At this point we need to determine when the button is “on” and when the button is “off.”  The value() function is provided for all Fl_Buttons to do just this task ( See Fl_Button documentation on the FLTK web site ).  It returns a 1 when the button’s value is “on” and 0 for the “off” case.  So, how do we encode this test.  We can use a simple if test on the widget we passed in.  However, we need to cast this Fl_Widget* to be an Fl_Round_Button* in order for us to use the value() function, because Fl_Widget’s do not have the value function.

 

Therefore, our code should resemble:

 

if ( ((Fl_Round_Button*)w)->value() == 1 ) {

}

else {

}

 

  1. Now that we have that part taken care of, the only other piece of information we need to encode is how to activate and deactivate the Hide Check Button button.  Well, we have access to this button through the light_b pointer.  So, we just need to find a function that can handle what we’re asking it to do.  So, consulting the Fl_Light_Button documentation, we don’t find anything directly.  If we keep following the documentation up the class hierarchy, we eventually find the activate() and deactivate() functions in the Fl_Widget class.  Since an Fl_Light_Button is an Fl_Widget, we don’t need to do any casting, we can simply call those functions as necessary.  Placing that code into our callback function we get:

 

void round_b_cb(Fl_Widget* w) {

if ( ((Fl_Round_Button*)w)->value() == 1 ) {

                                light_b->activate();

}

else {

                light_b->deactivate();

}

w->damage(1);

w->parent()->redraw();

}

 

The damage(1) tells FLTK that the widget needs to be redrawn.  The redraw() function tells FLTK that the round button’s parent ( the window itself ) needs to be redrawn.

 

  1. At this point, we only need to create the button itself and register the callback.

To create the button, we just need to specify a location on the window, a width, a height, and a label.

 

round_b = new Fl_Round_Button(25, 75, 140, 20, “Activate Light B”);

round_b->labelcolor(FL_CYAN);

round_b->callback(round_b_cb);

 

  1. Now, we can just compile and run the program and watch how it works.  Feel free to try altering other aspects of the widgets such as labelcolor, label, labelsize, etc.

 

 

THIS CONCLUDES THE HANDS-ON TUTORIAL

 

I strongly recommend reading through some of the documentation on FLTK’s website and playing around with the extra widgets.  Try adding some other widgets besides buttons. 

 

 

Some things to note:

 

Fl::event_button() returns an integer corresponding to which mouse button was pressed.

1 - Left mouse button

2 - Middle mouse button

3 - Right mouse button

 

Fl::event_key() returns an integer corresponding to which key on the keyboard was pushed.  The following constants define the non-ASCII keys on the keyboard for the FL_KEYBOARD events:

 

     FL_Button - A mouse button; use Fl_Button + n for mouse button n.

     FL_BackSpace - The backspace key.

     FL_Tab - The tab key.

     FL_Enter - The enter key.

     FL_Pause - The pause key.

     FL_Scroll_Lock - The scroll lock key.

     FL_Escape - The escape key.

     FL_Home - The home key.

     FL_Left - The left arrow key.

     FL_Up - The up arrow key.

     FL_Right - The right arrow key.

     FL_Down - The down arrow key.

     FL_Page_Up - The page-up key.

     FL_Page_Down - The page-down key.

     FL_End - The end key.

     FL_Print - The print (or print-screen) key.

     FL_Insert - The insert key.

     FL_Menu - The menu key.

     FL_Num_Lock - The num lock key.

     FL_KP - One of the keypad numbers; use FL_KP + n for number n.

     FL_KP_Enter - The enter key on the keypad.

     FL_F - One of the function keys; use FL_F + n for function key n.

     FL_Shift_L - The lefthand shift key.

     FL_Shift_R - The righthand shift key.

     FL_Control_L - The lefthand control key.

     FL_Control_R - The righthand control key.

     FL_Caps_Lock - The caps lock key.

     FL_Meta_L - The left meta/Windows key.

     FL_Meta_R - The right meta/Windows key.

     FL_Alt_L - The left alt key.

     FL_Alt_R - The right alt key.

     FL_Delete - The delete key.