Assignment 7

Objectives:
To re-implement a GUI front end to the calculator in C++.

Due date:
04/27/01

Description:
  1. Re-implement the GTK GUI calculator program in C++.

  2. As before, the calculator GUI should include buttons and a text area for entering an arithmetic expression and display of the evaluated result (see exmaple images below; click to view a larger version).

  3. This time, use a C++ implemntation for:
    • the stack
    • the calculator ``engine''
    • the graphical user interface (using GTK)

    Notes, suggestions, warnings, and additional requirements:

    • Create a templated Stack class for the stack.

    • Create a Calc class as the calculator object. A few design suggestions:

      1. the Calc object should have only two publicly visible member functions: the constructor (Calc()) and an evaluate() member which returns the result of the given string expression.

        The string expression is passed to the Calc object via the constructor.

      2. use two strstream objects:

        1. istrstream as the input string, from which you can ``read'' calculator expression tokens, e.g.,
          	float		t;
          	istrstream	istr;
          
            istr >> t;
          				
          will read in a float into the variable t from the given istr. This makes parsing the input really easy, without having to bother with the older mystring() or myatof() C functions.

        2. ostrstream as the output string, to which you can ``write'' the expression in postfix notation, e.g.,
          	ostrstream	ostr;
          
            ostr << ' ';
            ostr << token2char((TokenType)(int)stk.pop());
            ostr << ' ';
          				
          will compose the current portion of the postfix string ostr.

      3. the Calc can use two different instances of he Stack object: a float stack for postfix expression processing, e.g., Stack<float> stk;, and an integer stack for infix to postfix string conversion, e.g., Stack<int> stk;.

    • Create a Gui class as the GTK GUI object. The Gui object only has 1 publicly visible member function: the constructor. A few design suggestions:

      1. the Gui object should contain the top-level windows as its private data members, i.e., here's the Gui class interface:
        class Gui {
                public:
                // constructors
                Gui()                           { init(); }
        
                // destructors (default destructor ok)
                // ~Gui()                       { }
        
                // friend callbacks
        friend  void            cal_cb_button(GtkWidget *button, gpointer label);
        
                private:
                void            init(void);
                void            add_button(GtkWidget *packing_box,gpointer data);
                void            process_button(gpointer label);
        
                GtkWidget       *window;
                GtkWidget       *frame;
                GtkWidget       *text;
        
        };
        			

      2. the cal_cb_button() function is a global function which is made to be the signal handler for button presses. However, because it is global, by default it cannot access private members of the Gui class. Since we evenutally need to access the Gui internals when processing button presses, we make the signal handler a kind of redirection routine, whose only task is to access a suitable Gui member function, e.g., here's the code for the button handler:
        void cal_cb_button(GtkWidget *button, gpointer label)
        {
                Gui             *guip;
        
          guip = (Gui *)gtk_object_get_data(GTK_OBJECT(button),"gui");
        
          guip->process_button(label);
        }
        			
        Notice that to make this setup work, we also have to attach the entire Gui object to each button widget. This is done as follows in the void Gui::add_button(...) routine:
          // attach text widget pointer to button
          gtk_object_set_data(GTK_OBJECT(button),"gui",this);
        			
        If you decide that the Gui function member called by the button handler is to be a private Gui function (no real reason to make it public), then the handler function must be made a friend of the Gui class.

    • If you design your program from the bottom-up (starting with the stack, then the calculator, and finally your gui), then your final gui_test.cpp driver program should simply consist of the following function:
      #include        <iostream>
      #include        <strstream>
      #include        <glib.h>
      #include        <string.h>
      #include        <gtk/gtk.h>
      #include        <gtk/gtkwidget.h>
      #include        <gtk/gtkhscale.h>
      #include        <gtk/gtkvscale.h>
      #include        <gdk/gdk.h>
      #include        <gdk/gdkkeysyms.h>
      
      using namespace std;
      
      #include        "stk.h"
      #include        "cal.h"
      #include        "gui.h"
      
      int     main(int argc, char **argv);
      
      int main(int argc, char **argv)
      {
              Gui       *gui;
      
        gtk_init(&argc, &argv);
        gui = new Gui();
        gtk_main();
      
        return(0);
      }
      		
      It is strongly suggested that you develop this program incrementally. That is, when you finish implementing the Stack object, write a short test driver program which just tests this module. For example, here's a short stand-alone program, stk_test.cpp which tests the Stack module:
      #include        <iostream>
      
      using namespace std;
      
      #include        "stk.h"
      
      int main()
      {
              Stack      stk;
      
        cout << "stack should be empty: stack is ";
        stk.empty() ? cout << "empty\n" : cout << "not empty\n";
      
        // create list
        for(int i=0;i<10;i++) stk.push(10*(i+1));
      
        cout << "printing forwards:" << endl << stk;
      
        cout << "getting top node:" << stk.top() << endl;
      
        cout << "removing from front:" << endl;
        while(!stk.empty()) cout << stk.pop() << endl;
      
        cout << "stack should be empty: stack is ";
        stk.empty() ? cout << "empty\n" : cout << "not empty\n";
      }
      		
      Similarly, you should write a short calculator test driver when you're done writing the Calc module. Here's the stand-alone driver program cal_test.cpp:
      #include        <iostream>
      #include        <strstream>
      #include        <stdlib.h>
      #include        <math.h>
      
      using namespace std;
      
      #include        "stk.h"
      #include        "cal.h"
      
      int main()
      {
              const int       MAXSIZE = 80;
              char            input[MAXSIZE];
      
        while(cin.getline(input,MAXSIZE-1) && input[0] != 'q') {
          Calc calc(input);
          cout << calc.evaluate() << endl;
          // calc gets automatically deleted here
        }
      }
      		

    What to hand in:
    ``Professional-quality'' code distribution file, including student identification in a README plain text file:
    Course Id--Section No.
    Name:
    SS No:
    Assignment No.

    Electronic submission of source code: provide a self-contained, gzipped .tar of your project directory, complete with a Makefile and README files. See the tar'ing instructions web page for further info on creating the tar file.
    Your code MUST compile and run on a SUN machine.