-
Notifications
You must be signed in to change notification settings - Fork 1
All gui components will be displayed in a single JFrame called the main GUI or IDE. This IDE will look similar to this one
The GUI Components will be added as tabs inside the IDE. This section is titled "All GUI Components Here" in the image. The GUI Components will also be able to add elements to the menu bar, the button bar and the status bar. To do so the following interface is provided by the main GUI
public interface IDE {
/**
* Add your own menu to the menu bar
* @param menu The menu to add
* @param position The position of the new menu
*/
public void addMenu(JMenu menu, Position position, boolean displayAlways);
/**
* Add your own button to the button bar
* @param button The button to add
* @param position The position of the new button
*/
public void addButton(JButton button, Position position, boolean displayAlways);
/**
* Add your own label to the status bar
* @param label The label to add
* @param position The position of the new label
*/
public void addStatusLabel(JLabel label, Position position, boolean displayAlways);
}
The compiler components have a logical order. 1. Lexer, 2. Parser, 3. Semantic Analyze etc.
The main gui should stick to this logical order when displaying the gui components. This is what Position
is for.
public enum Position {
FIRST,
SOURCE_CODE,
LEXER,
TOKENS,
PARSER,
AST,
SEMANTIC_ANALYZER,
CHECKED_AST,
INTERMEDIATE_CODE_GENERATOR,
TAC,
BACKEND,
TARGET_CODE,
LAST
}
You should always specify the logical position for your components. If several components use the same position they will be ordered alphabetically.
The more interesting part is how to create a gui component for visualisation. You will need to stick to the following minimal interfaces and design pattern to do so.
All GUI Components have to use the Model-Passive View-Controller Pattern. This is a flavor of the Model View Controller where views are completely decoupled from the model. The view will never change its own state but instead always notify the controller.
If the user interacts with the view all events will be dispatched to the controller in parameterless methods. The controller will then process the event and update the view. Though the controller does not know anything about swing.
You are doing it wrong if one of the following statements is true
- Your View knows about your Model
- Your View changes the state of any GUI Component
- Your Controller needs to import anything from javax.swing pacakge
The advantage of this design pattern is the test-ability. Since Model and Controller are decoubled from the Swing View you can test both of them with JUnit tests without caring about automated GUI tests in Swing.
The interfaces for the GUI Components are minimalistic because a lot of the functionality will be dependent on the purpose of the component. Anyway some methods are needed to work with the main gui.
The model is the data source for your view. The kind of data is completely defined by the common interfaces of the components and is therefore already known. The data is always one of Source Code, Token Stream, AST, TAC or TargetCode.
public interface Model {
public Controller getController();
public void setSourceCode(String sourceCode);
public void setTokens(List<Token> tokens);
public void setAST(AST ast);
public void setTAC(List<Quadruple> tac);
public void setTargetCode(Map<String, InputStream> target);
}
The main gui / ide will call the set* methods whenever the corresponding values change. You can ignore any changes if you don't want to listen for them. After your model is updated the main gui will inform your controller about the changes. You do not need to create change observers by yourself.
To make life easier an abstract model is defined as follows
public abstract class AbstractModel {
public void setSourceCode(String sourceCode) {};
public void setTokens(List<Token> tokens) {};
public void setAST(AST ast) {};
public void setTAC(List<Quadruple> tac) {};
public void setTargetCode(Map<String, InputStream> target) {};
}
You will then need to overwrite the methods you are interested in.
CAREFUL: the given data in the set* methods of your model can be null if none is present yet. You need to be able to handle null values correctly.
Your model does not need to persistent the data in most of the cases. Anyway if you do need to persistent your data you can always write to a file or something similar.
The view is your gui code. All gui components need to return a JComponent to be displayed by the main gui. This can be a JPanel for example. Every gui component has a name. This name as the Tab label in the main gui.
public interface View {
public JComponent getComponent();
public String getName();
public Position getPosition();
public Controller getController();
public void initComponents(IDE ide);
}
The controller handles all your business logic. This is where all the magic happens. The controller interface is defined as follows
public interface Controller {
public View getView();
public Model getModel();
public void notifyModelChanged();
public void init(IDE ide);
}
Gui Components are initialized by the main gui / ide if they are found by the service loader framework in the classpath. Initialization of your gui component will always look as the following
// inside the ide gui
Controller c = new YourGuiComponentController();
c.init(this)
Model m = c.getModel();
m.setSourceCode(currentSourceCode);
m.setTokenStream(currentTokenStream);
...
c.notifyModelChanged();
Now lets create a simple example for a GUI component to clarify the above explanation. We want to create a GUI component that display the number of lines in the source code. If the user clicks on a button it will show the number of TAC statements instead.
public class ExampleModel extends AbstractModel {
private Controller controller;
private int sourceCodeLines;
private int tacStatements;
public ExampleModel(Controller c) {
controller = c;
}
public void setSourceCode(String source) {
sourceCodeLines = source.split("\n").length;
}
public void setTAC(List<Quadruple> tac) {
tacStatements = tac.size();
}
public int getSourceNum() { return sourceCodeLines; }
public int getTacNum() { return tacStatements; }
}
The View will look like this:
public class ExampleView extends JPanel implements View {
private Controller controller;
private JToggleButton tglbtnShowOf;
public ExampleView(Controller c) {
super();
controller = c;
}
public void initComponents(IDE ide) {
setLayout(null);
JLabel label = new JLabel("Number of Lines");
label.setBounds(12, 12, 123, 15);
add(label);
number = new JTextField();
number.setText("10");
number.setBounds(143, 10, 114, 19);
add(number);
number.setColumns(10);
tglbtnShowOf = new JToggleButton("Show # of TAC Stmts");
tglbtnShowOf.setBounds(58, 39, 257, 25);
add(tglbtnShowOf);
tglbtnShowOf.addActionListener(new ActionListener() {
public void ActionPerformed(ActionEvent e) {
controller.buttonPressed();
}
});
// if you want to add elements to the main gui, you can do so here
// e.g. add a status label to the status bar
JLabel status = new JLabel("this is an example");
ide.addStatusLabel(status, Position.LAST);
}
public boolean isButtonEnabled() {
return tglbtnShowOf.isSelected();
}
public JComponent getComponent() { return this; }
public String getName() { return "Lines of Code"; }
public Position getPosition() { return Position.LAST; }
public Controller getController() { return controller; }
}
Now let's write the controller for this component. It will look like this
public class ExampleController implements Controller {
private ExampleModel model;
private ExampleView view;
private boolean showSourceNums = true;
public ExampleController() {
model = new ExampleModel(this);
view = new ExampleView(this);
}
public View getView() { return view; }
public Model getModel() { return model; }
public void init(IDE ide) { view.initComponents(ide); }
public void notifyModelChanged() {
if (showSourceNums)
view.setText(model.getSourceNum());
else
view.setText(model.getTacNum());
}
public void buttonPressed() {
showSourceNums = view.isButtonEnabled();
if (showSourceNums)
view.setText(model.getSourceNum());
else
view.setText(model.getTacNum());
}
}