Project A2 20141 - OOP344
The basic information of A2 is on the BTP300 site linked here. A number of issues have been identified within the assignment spec and so a series of amendments is required in order for the spec to produce a correct assignment. The amendments are listed here. In addition, we are also listing a number of clarifications and suggestions that you may find helpful while implementing this assignment. Those will follow the spec code amendments.
You may add functions to your design of this assignment as long as they stay true to the "spirit" of the assignment. For example, adding a protected function in CFrame that returns whether the frame is currently in fullscreen mode or not. This function can then be used by all functions that care about whether the frame is currently fullscreen (eg width, height).
Please use this updated tester package for testing. It includes an updated a2tester.cpp, cdialog.h, cdialog.cpp. There are 4 testing levels. You can change which level you are testing at by changing the TEST_NO #define at the top of a2test.cpp. It may be any one of 0,1,2,3,4.
Additionally, here are 5 official example windows binaries. These are compiled versions of assignment 2, compiled with the tester setting set to 0,1,2,3, and 4 respectively.
Here are the same binaries compiled on matrix. Download them to your matrix with the wget command. Usage is wget --no-check-certificate address_of_thing_to_download. Note the --no-check-certificate flag. This is because Seneca's HTTPS certificate is out of date and we have to force wget to ignore that fact. Make sure to set the execute bit using chmod.
Your assignment is ready for submission when your assignment produces behaviour that is consistent with the above binaries. However, to get 100%, ensure that your assignment meets all of the criteria in the spec/amended spec.
Test 0: A1 Test
This test is a direct copy of the A1 tester. It is here to ensure that your display/edit functions work at a minimal level. While your A1 code will not affect your A2 mark, it will be more difficult for you to finish A2 if your A1 is in bad shape so this tester is present as a convenience to help you sort through that.
Test 1: CFrame Test
- Frame Notes:
- Frame may overwrite but not exceed the top line of the screen
- In turn, be sure that the move message overwrites the frame itself on the top line if the frame is currently occupying the top line while the message is displayed
- Inner Frame Notes:
- Be sure that inner frames do not overwrite any part of their parent's frame if the parent has a frame
Spec Code Amendments
- Instead of #defining NULL in cfg.h, you should #include <cstdlib> at the top of cfg.h. eg:
#ifndef __CFG_H__ #define __CFG_H__ #include <cstdlib> ... #endif
Functions to be Updated
Please make them public or protected.
Please ensure that if the object is in fullscreen mode width returns the width of the screen and height returns the height of the screen.
Functions to be Removed
The following two functions should be removed from the specification completely. Let me be extra clear: DO NOT PUT THEM IN YOUR CFrame HEADER, DO NOT CODE THEM, DO NOT USE THEM!
Functions to be Updated
- The function CField::edit currently accepts 4 parameters and is not virtual.
It should be updated as follows to be consistent with its child classes:
- pure virtual
- should not accept any parameters
- should return int (it already does)
Functions to be Removed
The following functions are a bit awkward. Remove them. See the Functions to be Added section for a better design.
- void** data()
- const void* pdata() const
- void display(int offset)
- Remove CField::display since it has no place as we use draw to print to screen. If you've written any code in the display function of CField, CLine, CLabel, CButton, then migrate it to the respective draw function. By the end of this fix, CField::display should no longer exist.
Functions to be Added
These functions act as setters/getters to CField's internal data member. Code them so that other classes can obtain or update the data member. Make these public.
- void* data() const
- void data(void*)
- Both constructors should cause memory to be allocated dynamically.
In the BTP300 spec, it mentions that the first constructor "passes the address of the data string directly to the CField constructor without allocating any further memory". Please ignore that remark and allocate memory dynamically anyway and copy the contents of the incoming data string into the memory that you are dynamically allocating.
For the rest of the spec, you can now assume that your CLine object will have dynamically allocated memory.
- Both constructors should accept a bool* and NOT a bool for insert mode. Updated descriptions follow:
Receives 8 values.
- bool* - the insert mode for the line field
Receives 7 values.
- bool* - the insert mode for the line field
Big Picture, Clarifications, Recommendations
This assignment is about creating a basic console GUI system. It is meant to show the student how OOP can be used effectively to design a system. The basic setup for this system are the 6 classes iFrame, CFrame, CField, CLabel, CLine, CButton and the supplemental class CDialog provided already implemented as a part of the spec. Let's look at the role of each of these.
NOTE: In the assignment spec, any value in parentheses ( () ) is the default value of that particular parameter.
In this assignment, you need to create a number of header files. You will most likely run into an issue regularly encountered by software developers which is multiple includes, chained includes, and cyclic includes (includes that end up including themselves).
To resolve and prevent these issues, use the following guidelines:
- Use header include protection. Typically this is done by using preprocessor directives to prevent a header from being included twice. For a header named MyHeader.h, it looks like this:
#ifndef __MYHEADER_H__ #define __MYHEADER_H__ ... // Header content ... #endif
- Try to include headers only from cpp files. While you should encounter few issues if you consistently apply the suggestion above, you can prevent even more potential problems by including headers only from cpp files.
iFrame is an interface class that describes a widget that has a frame. We can easily spot interface classes by looking for the "i" at the beginning of their name. A widget is basically a window component such as a label, a button, a field, or even a full window. Since iFrame is an interface, iFrame's job is to declare the set of functionality that every framed object can publicly perform. We call this type of design design by contract because we can think of any class derived from iFrame to be contractually obligated to fulfill what iFrame has promised.
iFrame's contract consists of a set of pure virtual functions that any concrete deriving class must implement. In this case, iFrame declares that any object instanced from it can draw itself, hide itself, and move.
CFrame inherits from iFrame and implements iFrame's core responsibilities. The "C" in CFrame stands for "concrete" (even though it is technically abstract! More on this later). In this system, CFrame is the parent class of all other deriving classes (CDialog, CField) and the ancestor of the other three (CLabel, CLine, CButton).
CFrame is responsible for common communication with the underlying console including:
- Clearing a portion of the screen (draw)
- Optionally drawing a border/frame while doing this
- Restoring a section of a cleared portion of the screen or in its entirety (hide)
- Storing attributes and providing functionality for retrieving the basic state of the frame including:
- Whether it is in fullscreen mode
- Whether it is bordered
- Relative top left row/top left column
- Absolute top left row/top left column
- Width/height of the frame, including the frame/border
- This should be the width/height of the screen if the frame is in fullscreen
- The integer accepted by this function can be a value equivalent to C_NO_FRAME or any other value. If it is C_NO_FRAME, then no frame should be drawn even if the current widget was constructed bordered.
- Child classes should adjust their drawn content so as not to overlap the border if a border will be drawn.
- If a frame is in fullscreen mode (width < 0 or height < 0) then no border should be drawn.
The normal row and column functions (row, col) return the relative position of the widget to its parent where (0,0) indicates the most top left position on which anything can be drawn.
If a widget has a parent then the relative position is relative to the parent's position. If a widget has no parent or if it's parent is fullscreen then the position is relative to the screen in which case the position (0,0) is the most top left character on the screen.
The idea behind the abs functions is to return the position of the widget on the screen. To do this, these functions should start with the current widget's row or col value.
- If the widget has no parent or the parent is fullscreen, these functions return the same values as row/col.
- Otherwise, these functions should add their parent's absrow/abscol (note the abs version!)
- Finally, if the parent is bordered, 1 should be added to the resultant value.
If the constructor receives a border character string that is NULL or is too short (less than 8 characters), the constructor should default to C_BORDER_CHARS.
Hide's purpose is to hide the current object and restore whatever were the contents on the screen that it overwrote.
Hide accepts a CDirection value that indicates which direction that the current object is moving. Passing in C_STATIONARY indicates that the frame is not moving and that all hidden contents should be restored while any of the other values will restore only one row/column. The behaviours are:
- C_STATIONARY: will restore all of the screen contents hidden by the current object.
- C_MOVED_LEFT: will restore the right-most column.
- C_MOVED_RIGHT: will restore the left-most column.
- C_MOVED_UP: will restore the bottom row.
- C_MOVED_DOWN: will restore the top row.
This is an optimization. To explain this optimization, let's assume that hide has received the value C_MOVED_RIGHT. In this case, we only need to restore the left-most column of the contents since that column will be the only part of the contents that our object was hiding that will be shown since the rest will continue to be hidden underneath our object once it moves right by one column.
setLine is somewhat awkward in implementation. Its purpose is to fill a string that is the width of the frame with a first character, a fill character, and an end character. This however requires a string for output which typically must be dynamically allocated before use and then deallocated after use.
However, the same effect can be achieved through a similar function that instead of filling a string simply prints the same characters directly to console utilizing two output statements and a loop. This approach removes the need for an additional string. Obviously a function like this would not need to accept a char*.
You may use either of these implementations of setLine or use any third implementation that you prefer.
bool fullscreen() const
It may be a good idea to add a functin that returns whether a frame is currently in fullscreen mode or not. This is simple and will make code that cares about whether a frame is currently in fullscreen mode simpler to write. This function may be public or protected (not much use if it is private).
CField inherits from CFrame and implements some of the functionalities common to most widgets eg buttons, fields, and labels. It also propagates a contract of its own. This is to allow container objects to work with more derived objects (CLine, CLabel, CButton) without needing to know their exact class.
CField's contract consists of functions that could be used to query the capabilities of an object derived from CField and to perform the basic actions typically required from a window component. These functions are all pure virtual. The functions are as follows:
- void draw(int)
- int edit() [mentioned above]
- bool editable() const
- void set(const void*)
CField is responsible for implementing the data setter/getter. As described above, it should implement the functions:
- void* data() const
- void data(void*)
These functions should do nothing more than get and set a private void* data member. Child classes should access the data member through the data getter and setter.
Child Class Data Storage
All child classes use some kind of dynamically allocated memory. CLine needs to store a pointer to a dynamically allocated string that it modifies. CLabel and CButton both need to store a pointer to a dynamically allocated string that they display. When implementing these child classes, please use the above data functions to store and retrieve the pointer. If you do not, you will crash the a2 tester during some tests!
A CLabel is an uneditable line of text to be displayed somewhere on the screen. It inherits from CField.
Please note that when drawing a CLabel, be sure not to exceed the parent's available width/height. If the label is to exceed the parent's width/height, be sure to stop drawing at the point at which the excess would occur.
- Eg, if we have an unframed label with the text "Hello I am a label" in a framed parent with a width of 5 and a height of 4 and the coordinates of the label are (x2,y1), it will incorrectly exceed as follows if no checking is done:
/----\ | | | Hello I am a label \----/
- The correct way to display the above would be:
/----\ | | | He| \----/
A CLine is an editable line of text. It inherits from CField.
- CLine should use your display and edit functions that you created in A1.
- CLine should have at least two private int member that it passes to your edit function from A1 when editing is requested:
- Cursor position
- String offset
A CButton is a text field with additional decoration displayed when the button is in its "edit" state. The edit state is exited when the user presses any key.
- When displaying the label value of a button, be sure to leave enough space for the prefix/suffix characters [ ]
- Eg, assume you have a framed button with the label value (ie the string stored in the button) "my Button":
- When draw is called, it would look like:
/-----------\ | my Button | \-----------/
- When edit is called, it would look like:
/-----------\ |[my Button]| \-----------/