//Implementation of Graphical User Interface class //Mandatory assignment 1 //Course 02152 Concurrent Systems, DTU, Fall 2008 //Hans Henrik Løvengreen Sep 24, 2008 import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.FlowLayout; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.Dimension; import java.awt.Insets; import java.awt.event.*; import javax.swing.*; @SuppressWarnings("serial") class CarField extends JPanel { final static int edge = 30; final static Color defcolor = Color.blue; final static Color symbolcolor = new Color(200,200,200); final static Color blockcolor = new Color(180,180,180); final static Color bgcolor = new Color(250,250,250); // Light grey final static Color slowcolor = new Color(255,200,80); // Amber final static Color opencolor = new Color(0,200,0); // Dark green final static Color closedcolor = Color.red; final static Color barriercol = Color.black; final static Font f = new Font("SansSerif",Font.BOLD,12); final static int maxstaints = 10; private Cars cars; private int users = 0; // No. of current users of field private Color c = defcolor; private char id = ' '; private char symbol = ' '; private int xoffset = 0; // -1,0,1 Horizontal offset private int yoffset = 0; // -1,0,1 Vertical offset private boolean isblocked = false; // Field can be used private boolean hadcrash = false; // Car crash has occurred private boolean keepcrash = false; // For detecting crashes private boolean slowdown = false; // Slow field private boolean isstartpos = false; private int startposno = 0; private boolean startposopen = false; private boolean barriertop = false; private boolean barrieractive = false; private int staintx = 0; private int stainty = 0; private int staintd = 0; private static boolean light (Color c) { return (c.getRed() + 2* c.getGreen() + c.getBlue()) > 600; } public CarField(Pos p, Cars c) { cars = c; setPreferredSize(new Dimension(edge,edge)); setBackground(bgcolor); setOpaque(true); addMouseListener(new MouseAdapter () { public void mousePressed(MouseEvent e) { if (isstartpos) { if ((e.getModifiers() & InputEvent.SHIFT_MASK) > 0) cars.removeCar(startposno); else if ((e.getModifiers() & InputEvent.CTRL_MASK) > 0) cars.restoreCar(startposno); else cars.startFieldClick(startposno); } } }); } public void enter(int xoff, int yoff, Color newc, char ch) { users++; if (users > 1 && keepcrash && !hadcrash) { hadcrash = true; // Define a staint int dia = 7; staintx = (edge-1-dia)/2 +(int)Math.round(Math.random()*4) - 2; stainty = (edge-1-dia)/2 +(int)Math.round(Math.random()*4) - 2; staintd = dia; } c = newc; id = ch; xoffset = xoff; yoffset = yoff; repaint(); } public void exit() { users--; repaint(); } public void clean() { hadcrash = false; repaint(); } public void setSymbol(char c) { symbol = c; } public void setBlocked(){ isblocked = true; setBackground(blockcolor); } public void setStartPos(int no, boolean open) { setSymbol((char) (no + (int) '0')); isstartpos = true; startposno = no; startposopen=open; repaint(); } public void setStartPos(boolean open) { startposopen=open; repaint(); } public void showBarrier(boolean active) { barrieractive = active; repaint(); } public void setBarrierPos(boolean top) { barriertop = top; // Set only once } public void setKeep(boolean keep) { keepcrash = keep; if (!keep && hadcrash) clean(); } public void setSlow(boolean slowdown) { this.slowdown = slowdown; setBackground(slowdown? slowcolor : bgcolor); repaint(); } public void paintComponent(Graphics g) { g.setColor(isblocked ? blockcolor : slowdown ? slowcolor: bgcolor); g.fillRect(0,0,edge,edge); if (symbol !=' ') { g.setColor(symbolcolor); g.setFont(f); FontMetrics fm = getFontMetrics(f); int w = fm.charWidth(id); int h = fm.getHeight(); g.drawString(""+symbol,((edge-w)/2),((edge+h/2)/2)); } if (hadcrash) { g.setColor(Color.red); g.fillOval(staintx,stainty,staintd,staintd); } if (users > 1 || (users > 0 && isblocked)) { g.setColor(Color.red); g.fillRect(0,0,edge,edge); } if (users < 0) { g.setColor(Color.yellow); g.fillRect(0,0,edge,edge); } if (users > 0) { g.setColor(c); int deltax = xoffset*(edge/2); int deltay = yoffset*(edge/2); g.fillOval(3+deltax,3+deltay,edge-7,edge-7); if (id != ' ') { if (light(c)) g.setColor(Color.black); else g.setColor(Color.white); g.setFont(f); FontMetrics fm = getFontMetrics(f); int w = fm.charWidth(id); int h = fm.getHeight(); g.drawString(""+id,((edge-w)/2)+deltax,((edge+h/2)/2)+deltay); } } if (isstartpos) { g.setColor(startposopen ? opencolor : closedcolor); g.drawRect(1,1,edge-2,edge-2); } if (barrieractive) { g.setColor(barriercol); if (barriertop) g.fillRect(0,0,edge,2); else g.fillRect(0,edge-2,edge,2); } } } @SuppressWarnings("serial") class Ground extends JPanel { private final int n = 11; private final int m = 12; private Cars cars; private CarField[][] area; public Ground(Cars c) { cars = c; area = new CarField [n] [m]; setLayout(new GridLayout(n,m)); setBorder(BorderFactory.createLineBorder(new Color(180,180,180))); for (int i = 0; i < n ; i++) for (int j = 0; j < m; j++) { area [i][j] = new CarField(new Pos(i,j),cars); add (area [i][j]); } // Define Hut area for (int i = 3; i <= 8; i++) for (int j = 1; j <= 3; j++) if (i < 5 || i > 6 || j < 2) area[i][j].setBlocked(); // Define Shed area for (int i = 0; i <= 0; i++) for (int j = 0; j <= 1; j++) area[i][j].setBlocked(); // Set start/gate positions for (int i = 0; i < 9; i++) { Pos startpos = cars.getStartPos(i); area[startpos.row][startpos.col].setStartPos(i,false); } // Set barrier fields (both adjacent fields) for (int i = 0; i < 9; i++) { area[5][i+3].setBarrierPos(false); area[6][i+3].setBarrierPos(true); } } public synchronized void mark(Pos p, Color c, int no) { CarField f = area[p.row][p.col]; f.enter(0,0,c,(char) (no + (int) '0')); } public synchronized void mark(Pos p, Pos q, Color c, int no) { CarField fp = area[p.row][p.col]; CarField fq = area[q.row][q.col]; char marker = (char) (no + (int) '0'); fp.enter(q.col-p.col,q.row-p.row,c,marker); fq.enter(p.col-q.col,p.row-q.row,c,marker); } public synchronized void clear(Pos p) { CarField f = area[p.row][p.col]; f.exit(); } public synchronized void clear(Pos p, Pos q) { CarField fp = area[p.row][p.col]; CarField fq = area[q.row][q.col]; fp.exit(); fq.exit(); } void setOpen(int no) { Pos p = cars.getStartPos(no); area[p.row][p.col].setStartPos(true); } void setClosed(int no) { Pos p = cars.getStartPos(no); area[p.row][p.col].setStartPos(false); } void showBarrier(boolean active) { for (int no = 0; no < 9; no++) { Pos p = cars.getBarrierPos(no); area[p.row][p.col].showBarrier(active); area[p.row + (no < 5 ? -1 : +1)][p.col].showBarrier(active); } } void setKeep(boolean keep) { for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) area[i][j].setKeep(keep); } void setSlow(boolean slowdown) { for (int i = 1; i < n; i++) area[i][0].setSlow(slowdown); } } @SuppressWarnings("serial") class ControlPanel extends JPanel { private int test_count = 20; Cars cars; JPanel barrier_panel = new JPanel(); ButtonGroup barrier_group = new ButtonGroup(); JRadioButton barrier_off = new JRadioButton("Off", true); JRadioButton barrier_on = new JRadioButton("On", false); JComboBox barrier_set = new JComboBox(); public boolean getBarrierState() { ButtonModel c = barrier_group.getSelection(); return (c == barrier_on); } JCheckBox keep = new JCheckBox("Keep crash", false); JCheckBox slow = new JCheckBox("Slowdown", false); JPanel button_panel = new JPanel(); JPanel test_panel = new JPanel(); JComboBox test_choice = new JComboBox(); public ControlPanel (Cars c) { cars = c; Insets bmargin = new Insets(2,5,2,5); setLayout( new GridLayout(3,1)); JButton start_all = new JButton("Start all"); start_all.setMargin(bmargin); start_all.addActionListener( new ActionListener () { public void actionPerformed(ActionEvent e) { cars.startAll(); } }); JButton stop_all = new JButton("Stop all"); stop_all.setMargin(bmargin); stop_all.addActionListener( new ActionListener () { public void actionPerformed(ActionEvent e) { cars.stopAll(); } }); keep.addItemListener( new ItemListener () { public void itemStateChanged(ItemEvent e) { cars.setKeep(keep.isSelected()); } }); slow.addItemListener( new ItemListener () { public void itemStateChanged(ItemEvent e) { cars.setSlow(slow.isSelected()); } }); button_panel.add(start_all); button_panel.add(stop_all); button_panel.add(new JLabel(" ")); button_panel.add(keep); button_panel.add(new JLabel("")); button_panel.add(slow); add(button_panel); barrier_panel.add(new JLabel("Barrier:")); barrier_group.add(barrier_off); barrier_group.add(barrier_on); barrier_panel.add(barrier_off); barrier_panel.add(barrier_on); barrier_panel.add(new JLabel(" Threshold:")); barrier_panel.add(barrier_set); barrier_off.addItemListener( new ItemListener () { public void itemStateChanged(ItemEvent e) { if (e.getStateChange()==ItemEvent.SELECTED) { cars.barrierClicked(false); } } }); barrier_on.addItemListener( new ItemListener () { public void itemStateChanged(ItemEvent e) { if (e.getStateChange()==ItemEvent.SELECTED) { cars.barrierClicked(true); } } }); for (int i = 0; i <= 7; i++) barrier_set.addItem(""+(i+2)); barrier_set.setSelectedIndex(7); barrier_set.addActionListener( new ActionListener () { public void actionPerformed(ActionEvent e) { int i = barrier_set.getSelectedIndex(); cars.barrierSet(2+i); } }); add(barrier_panel); for (int i = 0; i < test_count; i++) test_choice.addItem(""+i); JButton run_test = new JButton("Run test no."); run_test.setMargin(bmargin); run_test.addActionListener( new ActionListener () { public void actionPerformed(ActionEvent e) { int i = test_choice.getSelectedIndex(); cars.runTest(i); } }); test_panel.add(run_test); test_panel.add(test_choice); add(test_panel); } } @SuppressWarnings({ "serial" }) public class Cars extends JFrame implements CarDisplayI, CarTestingI { static final int width = 30; // Width of text area static final int minhistory = 50; // Min no. of lines kept private boolean[] gateopen = new boolean[9]; private Pos[] startpos = new Pos[9]; private Pos[] barrierpos = new Pos[9]; private boolean barrieractive = false; private boolean slowdown = false; private Object eventreg = new Object(); // Critical region for events private Ground gnd; private JPanel gp; private ControlPanel cp; private JTextArea txt; private JScrollPane log; private CarControlI ctr; private CarTest test; class LinePrinter implements Runnable { String m; public LinePrinter(String line) { m = line; } public void run() { int lines = txt.getLineCount(); if (lines > 2*minhistory) { try { int cutpos = txt.getLineStartOffset(lines/2); txt.replaceRange("",0,cutpos); } catch (Exception e) {} } txt.append(m+"\n"); } } public Cars() { startpos[0] = new Pos(5,2); for (int no = 1; no < 9; no++) startpos[no] = new Pos(no < 5 ? 4 : 7 , 3+no); for (int no = 0; no < 9; no++) barrierpos[no] = new Pos(no < 5 ? 6 : 5 , 3+no); for (int no = 0; no < 9; no++) { gateopen[no] = false; } gnd = new Ground(this); gp = new JPanel(); ctr = new CarControl(this); cp = new ControlPanel(this); txt = new JTextArea("",8,width); txt.setEditable(false); log = new JScrollPane(txt); log.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); setTitle("Cars"); setBackground(new Color(200,200,200)); gp.setLayout(new FlowLayout(FlowLayout.CENTER)); gp.add(gnd); setLayout(new BorderLayout()); add("North",gp); add("Center",cp); add("South",log); addWindowListener(new WindowAdapter () { public void windowClosing(WindowEvent e) { System.exit(1); } }); pack(); setVisible(true); } public static void main(String [] args) { new Cars(); } // High-level event handling -- called from panels or test thread // Uses own critical region eventreg for serialization // Since may be called from test thread, GUI updates are done via event queue void startFieldClick(int no) { synchronized(eventreg) { if (gateopen[no]) stopCar(no); else startCar(no); } } public void startCar(final int no) { synchronized(eventreg) { if (!gateopen[no]) { ctr.startCar(no); EventQueue.invokeLater(new Runnable() { public void run() { gnd.setOpen(no); }} ); gateopen[no] = true; } } } public void stopCar(final int no) { synchronized(eventreg) { if (gateopen[no]) { ctr.stopCar(no); EventQueue.invokeLater(new Runnable() { public void run() { gnd.setClosed(no); }} ); gateopen[no] = false; } } } public void setSpeed(int no, int speed) { synchronized(eventreg) { ctr.setSpeed(no,speed); } } public void setVariation(int no, int var) { synchronized(eventreg) { ctr.setVariation(no,var); } } public void removeCar(int no) { synchronized(eventreg) { ctr.removeCar(no); } } public void restoreCar(int no) { synchronized(eventreg) { ctr.restoreCar(no); } } public void barrierOn() { synchronized(eventreg) { ctr.barrierOn(); EventQueue.invokeLater(new Runnable() { public void run() { gnd.showBarrier(true); }} ); barrieractive = true; } } public void barrierOff() { synchronized(eventreg) { ctr.barrierOff(); EventQueue.invokeLater(new Runnable() { public void run() { gnd.showBarrier(false); }} ); barrieractive = false; } } public void barrierSet(int k) { synchronized(eventreg) { ctr.barrierSet(k); } } void barrierClicked(boolean on) { if (on) barrierOn(); else barrierOff(); } public void setSlow(final boolean slowdown) { synchronized (eventreg) { this.slowdown = slowdown; EventQueue.invokeLater(new Runnable() { public void run() { gnd.setSlow(slowdown); }} ); } } public boolean isSlow() { synchronized (eventreg) { return slowdown; } } public void startAll() { synchronized(eventreg) { int first = barrieractive ? 0 : 1; // Should not start no. 0 if no barrier for (int no = first; no < 9; no++) startCar(no); } } public void stopAll() { synchronized(eventreg) { for (int no = 0; no < 9; no++) stopCar(no); } } void runTest(int i) { if (test!=null && test.isAlive()) { println("Test already running"); return; } println("Run of test "+i); test = new CarTest(this,i); test.start(); } public void setKeep(final boolean keep) { EventQueue.invokeLater(new Runnable() { public void run() { gnd.setKeep(keep);}} ); } // Implementation of CarDisplayI // Mark and clear requests for car no. 0 are processed directly in order not // to fill the event queue (with risk of transiently inconsistent graphics) // Mark area at position p using color c and number no. public void mark(final Pos p, final Color c, final int no){ if (no != 0) EventQueue.invokeLater(new Runnable() { public void run() { gnd.mark(p,c,no); }} ); else gnd.mark(p,c,no); } // Mark area between adjacent positions p and q public void mark(final Pos p, final Pos q, final Color c, final int no){ if (no != 0) EventQueue.invokeLater(new Runnable() { public void run() { gnd.mark(p,q,c,no); }} ); else gnd.mark(p,q,c,no); } // Clear area at position p public void clear(final Pos p){ if (p.col < 2 || p.col > 3 || p.row < 5 || p.row > 6) EventQueue.invokeLater(new Runnable() { public void run() { gnd.clear(p); }} ); else gnd.clear(p); } // Clear area between adjacent positions p and q. public void clear(final Pos p, final Pos q){ if (p.col < 2 || p.col > 3 || p.row < 5 || p.row > 6) EventQueue.invokeLater(new Runnable() { public void run() { gnd.clear(p,q); }} ); else gnd.clear(p,q); } public Pos getStartPos(int no) { // Identify startposition of Car no. return startpos[no]; } public Pos getBarrierPos(int no) { // Identify pos. at barrier line return barrierpos[no]; } public Pos nextPos(int no, Pos pos) { // Fixed tracks --- not to be modified. int mycol = 3+no; Pos nxt = pos.copy(); if (no==0) { // No. 0 is special, running its own tiny track if (pos.row==5 && pos.col > 2) nxt.col--; if (pos.col==3 && pos.row > 5) nxt.row--; if (pos.row==6 && pos.col < 3) nxt.col++; if (pos.col==2 && pos.row < 6) nxt.row++; } else if (no < 5) { // Car going around anti-clockwise (to the left) int myrow = (no < 3? 2 : 1); if (pos.row==myrow && pos.col > 0) nxt.col--; if (pos.col==0 && pos.row < 9) nxt.row++; if (pos.row==9 && pos.col < mycol) nxt.col++; if (pos.col==mycol && pos.row > myrow) nxt.row--; } else if (no >= 5) { // Car going around clockwise (to the right) if (pos.row==10 && pos.col > 0) nxt.col--; if (pos.col==0 && pos.row > 1) nxt.row--; if (pos.row==1 && pos.col < 2) nxt.col++; if (pos.row==1 && pos.col==2) nxt.row--; if (pos.row==0 && pos.col < mycol) nxt.col++; if (pos.col==mycol && pos.row < 10) nxt.row++; } return nxt; } public synchronized void println(String m) { // Print (error) message on screen Runnable job = new LinePrinter(m); EventQueue.invokeLater(job); } }