COMP 304 Assignment 3 (Statecharts) Due date: Monday 5 April before 23:55 In this assignment, you will model the behaviour of the graphical interface of a Spreadsheet Grid View. The starting point is the full application without the model of the behaviour. This starting point is found in the directory DSheet (from archive DSheet.tgz). The behaviour will be modelled in the DCharts formalism, a variant of the Statecharts formalism. The DCharts formalism is described in detail in Thomas Feng's M.Sc. thesis (http://msdl.cs.mcgill.ca/people/tfeng/thesis/thesis.html). The main application is PolyCell.py. The Spreadsheet Grid View GUI code is found in the GUI/ directory. SSGridView.py encodes the static aspects of a Spreadsheet Grid View It provides a number of methods to query and update the state of the user interface. In addition, it will send the events start mouseClick doubleClick mouseMove leftKey rightKey upKey downKey keypress print quit (with suitable parameters where appropriate) to the SSGridViewStateChart often as a reaction to a user action (such as clicking the mouse in the Spreadsheet Grid View). Have a look in SSGridView.py to find a detailed description of the methods and attributes used in the Statechart's actions. SSGridViewStateChart.py contains the SSGridViewStateChart class which encodes the behaviour of the SSGridView (i.e., reaction to the above events as well as autonomous, timed behaviour). SSGridViewStateChart.py is synthesized from the DCharts specification SSGridViewStateChart.des using scc --ext -lpython SSGridViewStateChart.des SSGridViewStateChart.des needs to be written in the DCharts textual description syntax. This syntax is described in the Textual Syntax section of http://msdl.cs.mcgill.ca/people/tfeng/thesis/thesis.html. You will draw (in any drawing tool of your choice) the Statechart modelling the required behaviour described below. One option is to use AToM3-2.2-DCharts for this. The advantage of using AToM3 is that models can be simulated and animated using SVM (as long as no unresolved references appear in conditions and actions). Also, a .des can be generated from the model in AToM3. Examples are found in the DCharts/models directory. Model names are _DCharts_mdl.py. Warning: the AToM3 DCharts editor is not 100% stable. As long as adding of entities happens from the inside to the outside, there should be no problems. Save model frequently. Or encode the .des by hand. Below is a description of the behaviour of the Spreadsheet Grid View. The system start in "Setup" mode. When it receives the "start" event, it goes into "Running" mode. It takes the single parameter from that event (a reference to the SSGridView class whose dynamics is described) and stores it locally by means of the action SSGridView=[PARAMS] When in "Running" mode, the system will go to "Stopped" mode (encoded as a final state [FS]) when the "quit" event occurs. While in "Running" mode, the "print" event will go back to whatever nested state the system was in after the action SSGridView.printView() which pops up a window asking the user to choose a file for Postscript output of the Spreadsheet Grid View. While in "Running" mode, the system concurrently does "Control" and "Timing". In "Timing", a "MM:SS" timer is repeatedly incremented and displayed on the top of the window every 1s. For this, the action SSGridView.increaseTimeByOne() is used. In "Control", there are two main modes: "Viewing" and "Editing". When in "Viewing" mode and the "mouseMove" event occurs, move the overcell to the cell where the event occurred. Also, update the row/column display to reflect the new values. The action used is event=[PARAMS] newCCoord=SSGridView.cellCoords(event.x,event.y) SSGridView.moveOverCellTo(newCCoord.col,newCCoord.row) SSGridView.setColRowDisplay(str(SSGridView.getOverCell())) SSGridView.setOverCellDisplay(SSGridView.getCellFormula(SSGridView.getOverCell())) When in "Viewing" mode and the "mouseClick" event occurs in a cell other than the currently selected cell, move to this newly selected cell. Note that if the "mouseClick" event had occurred in the already selected cell, we would transition to "Editing" mode. The above condition is encoded as SSGridView.getSelectedCell().row!=SSGridView.cellCoords([PARAMS].x,[PARAMS].y).row or SSGridView.getSelectedCell().col!=SSGridView.cellCoords([PARAMS].x,[PARAMS].y).col The action used is event=[PARAMS] oldCCoord = SSGridView.getSelectedCell() newCCoord = SSGridView.cellCoords(event.x,event.y) SSGridView.moveSelectedCellTo(newCCoord.col,newCCoord.row) When in "Viewing" mode and the "downKey" event occurs, move the selected cell down (modulo the grid: wrap around onto the topmost row of one column to the right if in the bottommost row). The action used is oldCCoord = SSGridView.getSelectedCell() newRow=(oldCCoord.row+1)%SSGridView.getHeight() newCol=0 if oldCCoord.row!=SSGridView.getHeight()-1: newCol=oldCCoord.col if oldCCoord.row==SSGridView.getHeight()-1: newCol=(oldCCoord.col+1)%SSGridView.getWidth() SSGridView.moveSelectedCellTo(newCol,newRow) When in "Viewing" mode and the "upKey" event occurs, move the selected cell up (modulo the grid: wrap around onto the bottommost row of one column to the left if in the topmost row). The action used is oldCCoord = SSGridView.getSelectedCell() newRow=(oldCCoord.row-1)%SSGridView.getHeight() newCol=0 if oldCCoord.row!=0: newCol=oldCCoord.col if oldCCoord.row==0: newCol=(oldCCoord.col-1)%SSGridView.getWidth() SSGridView.moveSelectedCellTo(newCol,newRow) When in "Viewing" mode and the "leftKey" event occurs, move the selected cell left (modulo the grid: wrap around onto the rightmost column of one row above if in the leftmost column). The action used is oldCCoord = SSGridView.getSelectedCell() newCol=(oldCCoord.col-1)%SSGridView.getWidth() newRow=0 if oldCCoord.col!=0: newRow=oldCCoord.row if oldCCoord.col==0: newRow=(oldCCoord.row-1)%SSGridView.getHeight() SSGridView.moveSelectedCellTo(newCol,newRow) When in "Viewing" mode and the "rightKey" event occurs, move the selected cell right (modulo the grid: wrap around onto the leftmost column of one row below if in the rightmost column). The action used is oldCCoord = SSGridView.getSelectedCell() newCol=(oldCCoord.col+1)%SSGridView.getWidth() newRow=0 if oldCCoord.col!=SSGridView.getWidth()-1: newRow=oldCCoord.row if oldCCoord.col==SSGridView.getWidth()-1: newRow=(oldCCoord.row+1)%SSGridView.getHeight() SSGridView.moveSelectedCellTo(newCol,newRow) A transition from "Viewing" to "Editing" mode is triggered by the "mouseClick" event. The transition only takes place when the mouse click occurs in the currently selected cell. Variables are created to hold the edited text. The cursor is drawn. The above condition is encoded as SSGridView.getSelectedCell().row==SSGridView.cellCoords([PARAMS].x,[PARAMS].y).row and SSGridView.getSelectedCell().col==SSGridView.cellCoords([PARAMS].x,[PARAMS].y).col The action used is event=[PARAMS] oldCCoord = SSGridView.getSelectedCell() newCCoord = SSGridView.cellCoords(event.x,event.y) curDataLeft=SSGridView.getCellFormula(oldCCoord) curDataRight="" SSGridView.drawText(oldCCoord,curDataLeft) SSGridView.drawCursor(oldCCoord) SSGridView.setOverCellDisplay(curDataLeft) A transition from "Viewing" to "Editing" mode is triggered by the "keypress" event. In addition, the key pressed must be valid (isDigitChar, isPeriod, isEquals, isAlphaChar, isNonAlphaChar). Variables are created to hold the edited text. The cursor is drawn. The above condition is encoded as C: SSGridView.isDigitChar([PARAMS].keysym) or SSGridView.isPeriod([PARAMS].keysym) or SSGridView.isEquals([PARAMS].keysym) or SSGridView.isAlphaChar([PARAMS].keysym) or SSGridView.isNonAlphaChar([PARAMS].keysym) The action used is event=[PARAMS] curDataLeft=event.char curDataRight="" SSGridView.drawText(SSGridView.getSelectedCell(),curDataLeft,curDataRight) SSGridView.deleteCursor() SSGridView.drawCursor(SSGridView.getSelectedCell()) SSGridView.setOverCellDisplay(curDataLeft+curDataRight) A transition from "Viewing" to "Editing" mode is triggered by the "doubleClick" event. Create the variables holding the formula (get it from the subject), draw the text and the cursor. Also, move the selected cell to the cell where the doubleClick occurred. The action used is event=[PARAMS] oldCCoord = SSGridView.getSelectedCell() newCCoord = SSGridView.cellCoords(event.x,event.y) curDataLeft=SSGridView.getCellFormula(newCCoord) curDataRight="" SSGridView.drawText(newCCoord,curDataLeft) SSGridView.drawCursor(newCCoord) SSGridView.setOverCellDisplay(curDataLeft) SSGridView.moveSelectedCellTo(newCCoord.col,newCCoord.row) In "Editing" mode, concurrently, reaction to editing events and flashing (black text on white background/ white text on black background) of the timer is done. To set black text on white background, we use the action SSGridView.setTimeWhite() To set white text on black background, we use the action SSGridView.setTimeBlack() The flashing happens with a frequency of twice per second. When the system enters"Editing" mode, the time display must be set to black text on white background. When the system leaves "Editing" mode, the time display must be reset to black text on white background. When in "Editing" mode and a valid "keypress" event (isDigitChar, isPeriod, isEquals, isAlphaChar, isNonAlphaChar) occurs, update the edited data and redraw the cursor. The above condition is encoded as SSGridView.isDigitChar([PARAMS].keysym) or SSGridView.isPeriod([PARAMS].keysym) or SSGridView.isEquals([PARAMS].keysym) or SSGridView.isAlphaChar([PARAMS].keysym) or SSGridView.isNonAlphaChar([PARAMS].keysym) The action used is event=[PARAMS] curDataLeft+=event.char SSGridView.drawText(SSGridView.getSelectedCell(),curDataLeft,curDataRight) SSGridView.deleteCursor() SSGridView.drawCursor(SSGridView.getSelectedCell()) SSGridView.setOverCellDisplay(curDataLeft+curDataRight) When in "Editing" mode and a BackSpace occurs and there is data to the left of the cursor, delete one char to the left of the cursor and redraw the cursor, redraw the text The above condition is encoded as [PARAMS].keysym=='BackSpace' and len(curDataLeft)>0 The action used is curDataLeft=curDataLeft[0:-1] SSGridView.drawText(SSGridView.getSelectedCell(),curDataLeft,curDataRight) SSGridView.deleteCursor() SSGridView.drawCursor(SSGridView.getSelectedCell()) SSGridView.setOverCellDisplay(curDataLeft+curDataRight) When in "Editing" mode and a Delete occurs and there is data to the right of the cursor, delete one char to the right of the cursor and redraw the text. In this case, the cursor does not need to be redrawn, since it does not move after a delete The above condition is encoded as [PARAMS].keysym=='Delete' and len(curDataRight)>0 The action used is curDataRight=curDataRight[1:] SSGridView.drawText(SSGridView.getSelectedCell(),curDataLeft,curDataRight) SSGridView.setOverCellDisplay(curDataLeft+curDataRight) When in "Editing" mode and a "leftKey" event occurs and the cursor is not completely at the left of the text, move the cursor one char to the left. See elsewhere what happens when the cursor is at the very left of the text. The above condition is encoded as len(curDataLeft)>0 The action used is oldCCoord = SSGridView.getSelectedCell() curDataRight=curDataLeft[-1]+curDataRight curDataLeft=curDataLeft[:-1] SSGridView.drawText(oldCCoord,curDataLeft,curDataRight) SSGridView.deleteCursor() SSGridView.drawCursor(oldCCoord) When in "Editing" mode and a "rightKey" event occurs and the cursor is not completely at the right of the text, move the cursor one char to the right. See elsewhere what happens when the cursor is at the very right of the text. The above condition is encoded as len(curDataRight)>0 The action used is oldCCoord = SSGridView.getSelectedCell() curDataLeft+=curDataRight[0] curDataRight=curDataRight[1:] SSGridView.drawText(oldCCoord,curDataLeft,curDataRight) SSGridView.deleteCursor() SSGridView.drawCursor(oldCCoord) When in "Editing" mode and a "mouseMove" event occurs within the cell currently being edited, display the formula currently being edited (as we cannot request the formula value from the subject, since the formula was not committed yet). The above condition is encoded as SSGridView.getSelectedCell().row==SSGridView.cellCoords([PARAMS].x,[PARAMS].y).row and SSGridView.getSelectedCell().col==SSGridView.cellCoords([PARAMS].x,[PARAMS].y).col The action used is event=[PARAMS] newCCoord=SSGridView.cellCoords(event.x,event.y) SSGridView.moveOverCellTo(newCCoord.col,newCCoord.row) SSGridView.setColRowDisplay(str(SSGridView.getOverCell())) SSGridView.setOverCellDisplay(curDataLeft+curDataRight) When in "Editing" mode and a "mouseMove" event occurs outside the cell currently being edited, get the formula value for this cell from the subject and display it. The above condition is encoded as SSGridView.getSelectedCell().row!=SSGridView.cellCoords([PARAMS].x,[PARAMS].y).row or SSGridView.getSelectedCell().col!=SSGridView.cellCoords([PARAMS].x,[PARAMS].y).col The action used is event=[PARAMS] newCCoord=SSGridView.cellCoords(event.x,event.y) SSGridView.moveOverCellTo(newCCoord.col,newCCoord.row) SSGridView.setColRowDisplay(str(SSGridView.getOverCell())) SSGridView.setOverCellDisplay(SSGridView.getCellFormula(SSGridView.getOverCell())) When in "Editing" mode and a "doubleClick" event occurs outside the cell currently being edited and if the data can be committed, commit the data for the selected cell. Update the display of the selected cell by putting the evaluated value, delete the cursor, and stay in "Editing" mode. Move the selected cell to the cell where the double click occured, reset the variables holding edited data and draw the cursor. We are now editing *another* cell. The above condition is encoded as (SSGridView.getSelectedCell().row!=SSGridView.cellCoords([PARAMS].x,[PARAMS].y).row or SSGridView.getSelectedCell().col!=SSGridView.cellCoords([PARAMS].x,[PARAMS].y).col) and SSGridView.acceptData(SSGridView.getSelectedCell(),curDataLeft+curDataRight) The action used is event=[PARAMS] oldCCoord = SSGridView.getSelectedCell() newCCoord = SSGridView.cellCoords(event.x,event.y) SSGridView.deleteCursor() SSGridView.drawText(oldCCoord,SSGridView.getCellValue(oldCCoord),centered=1) curDataLeft=SSGridView.getCellFormula(newCCoord) curDataRight="" SSGridView.drawText(newCCoord,curDataLeft) SSGridView.drawCursor(newCCoord) SSGridView.setOverCellDisplay(curDataLeft) SSGridView.moveSelectedCellTo(newCCoord.col,newCCoord.row) When in "Editing" mode and a "mouseClick" event occurs in the currently selected cell, nothing happens. The above condition is encoded as SSGridView.getSelectedCell().row==SSGridView.cellCoords([PARAMS].x,[PARAMS].y).row and SSGridView.getSelectedCell().col==SSGridView.cellCoords([PARAMS].x,[PARAMS].y).col When in "Editing" mode and the "mouseClick" event occurs in a cell different from the currently selectedCell: commit the data, move the selectedCell, and enter "Viewing" mode. The transition fails if there is a parse error (committing the data fails). The above condition is encoded as C: (SSGridView.getSelectedCell().row!=SSGridView.cellCoords([PARAMS].x,[PARAMS].y).row or SSGridView.getSelectedCell().col!=SSGridView.cellCoords([PARAMS].x,[PARAMS].y).col) and SSGridView.acceptData(SSGridView.getSelectedCell(),curDataLeft+curDataRight) The action used is event=[PARAMS] oldCCoord = SSGridView.getSelectedCell() newCCoord = SSGridView.cellCoords(event.x,event.y) SSGridView.drawText(oldCCoord,SSGridView.getCellValue(oldCCoord),centered=1) SSGridView.deleteCursor() SSGridView.moveSelectedCellTo(newCCoord.col,newCCoord.row) curDataLeft="" curDataRight="" When in "Editing" mode and the "leftKey" event occurs and the cursor is at the very left (so "leftKey" does no longer mean "move the cursor to the left") commit the data, move the selectedCell to the left (modulo the grid: wrap around onto the rightmost column of one row above if in the leftmost column). The transition fails if there is a parse error (committing the data fails). The above condition is encoded as len(curDataLeft)==0 and SSGridView.acceptData(SSGridView.getSelectedCell(),curDataLeft+curDataRight) The action used is oldCCoord = SSGridView.getSelectedCell() newCol=(oldCCoord.col-1)%SSGridView.getWidth() newRow=0 if oldCCoord.col!=0: newRow=oldCCoord.row if oldCCoord.col==0: newRow=(oldCCoord.row-1)%SSGridView.getHeight() SSGridView.deleteCursor() SSGridView.drawText(oldCCoord,SSGridView.getCellValue(oldCCoord),centered=1) SSGridView.moveSelectedCellTo(newCol,newRow) When in "Editing" mode and the "rightKey" event occurs and the cursor is at the very right (so "rightKey" does no longer mean "move the cursor to the right") commit the data, move the selectedCell to the right (modulo the grid: wrap around onto the leftmost column of one row below if in the rightmost column). The transition fails if there is a parse error (committing the data fails). The above condition is encoded as len(curDataRight)==0 and SSGridView.acceptData(SSGridView.getSelectedCell(),curDataLeft+curDataRight) The action used is oldCCoord = SSGridView.getSelectedCell() newCol=(oldCCoord.col+1)%SSGridView.getWidth() newRow=0 if oldCCoord.col!=SSGridView.getWidth()-1: newRow=oldCCoord.row if oldCCoord.col==SSGridView.getWidth()-1: newRow=(oldCCoord.row+1)%SSGridView.getHeight() SSGridView.deleteCursor() SSGridView.drawText(oldCCoord,SSGridView.getCellValue(oldCCoord),centered=1) SSGridView.moveSelectedCellTo(newCol,newRow) When in "Editing" mode and the "upKey" event occurs commit the data, move the selectedCell up (modulo the grid: wrap around onto the bottommost row of one column to the left if in the topmost row). The transition fails if there is a parse error (committing the data fails). The above condition is encoded as SSGridView.acceptData(SSGridView.getSelectedCell(),curDataLeft+curDataRight) The action used is oldCCoord = SSGridView.getSelectedCell() newRow=(oldCCoord.row-1)%SSGridView.getHeight() newCol=0 if oldCCoord.row!=0: newCol=oldCCoord.col if oldCCoord.row==0: newCol=(oldCCoord.col-1)%SSGridView.getWidth() SSGridView.deleteCursor() SSGridView.drawText(oldCCoord,SSGridView.getCellValue(oldCCoord),centered=1) SSGridView.moveSelectedCellTo(newCol,newRow) When in "Editing" mode and the "downKey" event occurs commit the data, move the selectedCell down (modulo the grid: wrap around onto the topmost row of one column to the right if in the bottommost row). The transition fails if there is a parse error (committing the data fails). The above condition is encoded as SSGridView.acceptData(SSGridView.getSelectedCell(),curDataLeft+curDataRight) The action used is oldCCoord = SSGridView.getSelectedCell() newRow=(oldCCoord.row+1)%SSGridView.getHeight() newCol=0 if oldCCoord.row!=SSGridView.getHeight()-1: newCol=oldCCoord.col if oldCCoord.row==SSGridView.getHeight()-1: newCol=(oldCCoord.col+1)%SSGridView.getWidth() SSGridView.deleteCursor() SSGridView.drawText(oldCCoord,SSGridView.getCellValue(oldCCoord),centered=1) SSGridView.moveSelectedCellTo(newCol,newRow)