0

I am trying to implement a JPanel (that is added to a JScrollPane). The idea is that on JPanel a series of rectangles will be drawn. One of these rectangles have a different color. The idea is that when the application runs, I should be able to add more of these rectangles(if wanted) or move among the ones that are currently drawn. I have left/right move buttons that will allow me to move among the rectangles. Now the problems is that when I can move among the buttons, but JScrollPane doesn't move when JPanel is being updated. As can be seen in the picture below, suppose I am in the rectangle with value [a], that has the color orange. Now I can move to the left or the right but the JScrollPane stays the same. How can I make JScrollPane move with the rectangles? The panel screenshot

I have a file (TapeArea) that does all the drawing and a file (TapePanel) that bundles 3 of the TapeArea instances together.

import java.awt.Color;
import javax.swing.Timer;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowListener;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;


import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;

public class TapeArea extends JPanel {

    private final Color BG_COLOR = new Color(200,200,200);
    private final Color DEFAULT_CELL_COLOR = Color.white;
    private final Color CURRENT_CELL_COLOR = Color.orange;
    private final Color CELL_INDEX_COLOR = new Color(0, 130, 0);
    private final int FONT_STYLE = Font.BOLD;
    private final String FONT_NAME = "Monotype";
    private final double INITIAL_OFFSET = -1.5;


    int cellWidth = 40;
    int cellHeight = 40;
    int fontSize = 20;
    boolean tapeInit = false;
    int leftMostCell = 0, rightMostCell = 0;
    double newLeftCellWidth = 0, newRightCellWidth = 0;
    boolean newLeftCell = false, newRightCell = false;
    Color currentTextColor = Color.black;
    double offset = INITIAL_OFFSET;
    int origin = 0;

    Dimension areaSize;
    int filler;
    Tape tape;
    FlowLayout layout;

    int x   = 10;   //Start Drawing from X=10   
    int delay   = 500;  //milliseconds

    public TapeArea(Tape t){
        tape = t;
        layout = new FlowLayout();
        setLayout(layout);
        setPreferredSize(new Dimension(1200,50));


//       ActionListener counter = new ActionListener() {
//              public void actionPerformed(ActionEvent evt) 
//              { 
//                  stepOneTapeCell();
//                    repaint();
//                    x++;
//              }};
//           new Timer(delay, counter).start();
    }




    public void paintComponent(Graphics g){
        int i,drawPos;
        int yAlign;
        int fontScalingFactor;
        int stringWidth, stringHeight;
        int index = 0, startAt, cellsToDraw, currentPosition;
        int tapeLength;
        String symbol;
        FontRenderContext DEFAULT_FONT_RENDER_CONTEXT =
                new FontRenderContext(null, false, false);
        Rectangle2D charBounds;

        Graphics2D g2d = (Graphics2D) g;
        super.paintComponent(g2d);

        if(tape == null)
            return;


        g2d.setColor(BG_COLOR);
        g2d.fillRect(0,0,getWidth(), getHeight());

        tapeLength = tape.getSize();

        if(areaSize != null)
            if(areaSize.width != getSize().width || areaSize.height != getSize().height)
                tapeInit = false;

        areaSize = getSize();
        yAlign = areaSize.height / 2;

        cellWidth = getHeight() - 2;
        if(cellWidth > 40) {
            cellWidth = 40;
        }
        cellHeight = cellWidth;
        fontSize = cellWidth / 2;



        if(newLeftCell) {
            startAt = 1;
            cellsToDraw = tapeLength - 1;
            currentPosition = 0;
        } else if(newRightCell) {
            startAt = 0;
            cellsToDraw = tapeLength - 1;
            currentPosition = tapeLength - 2;
        } else {
            startAt = 0;
            cellsToDraw = tapeLength;
            currentPosition = tape.getCurrentPosition();
        }
        currentPosition = tape.getCurrentPosition();

        //determine the number of cells to left and right on the tape
        if(!tapeInit){
            for(filler = 0; cellWidth * (1 + 2*filler) <= areaSize.width; filler++);
            filler += 2;

            leftMostCell = currentPosition - filler;
            rightMostCell = leftMostCell + 2*filler;

            while(-leftMostCell < rightMostCell - tapeLength - 1 && 0 < rightMostCell - tapeLength - 1) {
                leftMostCell--;
                rightMostCell--;
            }
            while(-leftMostCell > rightMostCell - tapeLength + 1 && leftMostCell < 0) {
                leftMostCell++;
                rightMostCell++;
            }

        }//end of if(!tapeInit)



        if(origin != tape.getOrigin()){
            leftMostCell++;
            rightMostCell++;
        }
        origin = tape.getOrigin();
        tapeInit = true;

        //draw tape cells
        for(drawPos = 0, i = leftMostCell; i <= rightMostCell; i++, drawPos++){

            //what symbol to be drawn on the cell
            if(i >= 0 && i < tapeLength)
                symbol = (String) tape.getSymbolAt(i);
            else
                symbol = tape.getFillSymbol();

            //indicate current tape cell by coloring it
            if(i == currentPosition)
                g2d.setColor(CURRENT_CELL_COLOR);
            else 
                g2d.setColor(DEFAULT_CELL_COLOR);

            g2d.fillRect((int)((newLeftCellWidth + drawPos + offset) * cellWidth), yAlign - cellHeight/2, 
                       cellWidth, cellHeight);

            g2d.setColor(Color.black);
            g2d.drawRoundRect((int)((newLeftCellWidth + drawPos + offset) * cellWidth), yAlign - cellHeight/2, 
                       cellWidth, cellHeight, 10, 10);

            //draw symbols on cells
            charBounds = g.getFont().getStringBounds(symbol, 
                     DEFAULT_FONT_RENDER_CONTEXT);

            stringWidth  = (int)Math.ceil(charBounds.getWidth());
            stringHeight = (int)Math.ceil(charBounds.getHeight());

            //if symbol is multi-character, font may be adjusted so it will fit
            fontScalingFactor = (int)Math.ceil((double)stringWidth/(double)cellWidth);
            g.setFont(new Font(FONT_NAME, FONT_STYLE, fontSize/fontScalingFactor));

            charBounds = g.getFont().getStringBounds(symbol, 
                                 DEFAULT_FONT_RENDER_CONTEXT);

            stringWidth  = (int)Math.ceil(charBounds.getWidth());
            stringHeight = (int)Math.ceil(charBounds.getHeight());

            if(i == tape.getCurrentPosition()) {
                g.setColor(currentTextColor);
            }

            g.drawString(symbol, 
                 (int)((newLeftCellWidth + drawPos + 0.5 + offset) * cellWidth - (stringWidth/2)), 
                     (int)(yAlign + (stringHeight/4)));

            g.setFont(new Font(FONT_NAME, Font.PLAIN, fontSize/(2 * fontScalingFactor)));
            g.setColor(CELL_INDEX_COLOR);

            g.drawString(Integer.toString(i - origin), 
                     (int)((newLeftCellWidth + drawPos + 0.3 + offset) * cellWidth - (stringWidth/2)), 
                     (int)(yAlign + 0.45 * cellHeight));
            index++;

        }//end of for() drawing tape cells



    }

    void setTapeData(String m, Tape p){
        p.removeAllTapeData();
        p.addDataToTape(m);
    }


    void stepOneTapeCell(String direction){
        switch(direction){
        case "L":
            if(tape.getCurrentPosition() > tape.getOrigin()){
                tape.setCurrentPosition(tape.getCurrentPosition() - 1);
                revalidate();
                repaint();
            }
            //tape.shiftLeft();
            break;
        case "R":
            if(tape.getCurrentPosition() < tape.getSize()-1){
                tape.setCurrentPosition(tape.getCurrentPosition() + 1);
                revalidate();
                repaint();
            }
//          tape.setCurrentPosition(tape.getCurrentPosition() + 1);
//          repaint();
//          tape.shiftRight();
//          repaint();
            break;
        }   
    }


}//end of class TapePanel

TapePanel file:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;

public class TapePanel extends JPanel implements MyJPanel{

    private final int ROWS = 3;
    private final int COLS = 1;
    private final int TAPE_ORIGINAL_POSITION = 0;
    private int NUM_TAPES = 3;
    GridLayout layout;
    Tape [] tapes;
    TapeArea [] tapeArea;
    JScrollPane sp1, sp2, sp3;

    Border blackLine, border,margin;
    TitledBorder title;

    public TapePanel(Tape t1, Tape t2, Tape t3){

        tapes = new Tape[NUM_TAPES];
        tapes[0] = t1;
        tapes[1] = t2;
        tapes[2] = t3;
        initiateComps();
        addCompsToLayout();
    }


    public void initiateComps() {
        // TODO Auto-generated method stub
        layout = new GridLayout(ROWS,COLS);
        setLayout(layout);

        blackLine = BorderFactory.createLineBorder(Color.BLACK);
        title = BorderFactory.createTitledBorder(blackLine,"Tapes");
        title.setTitleJustification(TitledBorder.LEFT);
        border = title.getBorder();
        margin = new EmptyBorder(15,15,15,15);
        title.setBorder(new CompoundBorder(margin,border));
        setBorder(title);

        tapeArea = new TapeArea[NUM_TAPES];
        tapeArea[0] = new TapeArea(tapes[0]);
        tapeArea[1] = new TapeArea(tapes[1]);
        tapeArea[2] = new TapeArea(tapes[2]);

        sp1 = new JScrollPane(tapeArea[0],JScrollPane.VERTICAL_SCROLLBAR_NEVER,JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        sp1.setPreferredSize(new Dimension(200,70));
        sp2 = new JScrollPane(tapeArea[1],JScrollPane.VERTICAL_SCROLLBAR_NEVER,JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        sp2.setPreferredSize(new Dimension(200,70));
        sp3 = new JScrollPane(tapeArea[2],JScrollPane.VERTICAL_SCROLLBAR_NEVER,JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        sp3.setPreferredSize(new Dimension(200,70));

        //setBorder(new EmptyBorder(20,15,15,15));
    }

    @Override
    protected void paintComponent(Graphics g) {
        // TODO Auto-generated method stub
        super.paintComponent(g);

    }
    void repaintTapeArea(){
        for(int i = 0; i < NUM_TAPES;i++){
            tapeArea[i].revalidate();
            tapeArea[i].repaint();
        }

    }

    void resetAllTapes(){

        for(int i = 0; i < NUM_TAPES; i++){
            tapeArea[i].setTapeData("", tapes[i]);
            tapeArea[i].tape.setCurrentPosition(TAPE_ORIGINAL_POSITION);
        }

    }
    void setTapeData(String m, Tape p, int tapeNumber){
        tapeArea[tapeNumber].setTapeData(m, p);
    }

    void stepOneCell(int tapeNumber, String direction){
        tapeArea[tapeNumber].stepOneTapeCell(direction);
    }

    @Override
    public void addCompsToLayout() {
        // TODO Auto-generated method stub
        add(sp1);
        add(sp2);
        add(sp3);
    }


    @Override
    public void addComponent(Component comp, int zone, int row, int col, int width, int height) {
        // TODO Auto-generated method stub

    }

    public static void main(String [] args){

            JFrame f = new JFrame();
            f.add(new TapePanel(new Tape("a b c d"),new Tape("a b c d"),new Tape("a b")));
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setVisible(true);

            f.pack();

    }
}

Tape is just a simple linkedlist.

fafinu
  • 23
  • 1
  • 6
  • 1
    [`JComponent#scrollRectToVisible`](https://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#scrollRectToVisible(java.awt.Rectangle)) - call this on the panel with the rectangles, pass it the rectangle bounds you want to become visible – MadProgrammer Mar 18 '16 at 09:52
  • For [example](http://stackoverflow.com/questions/19778717/moving-a-view-port-over-a-larger-image-jlableljscrollpane/19779491#19779491), [example](http://stackoverflow.com/questions/30429648/temporarily-disable-or-prevent-repainting-jviewport-on-scrolling-with-a-mousedra/30429965#30429965), [example](http://stackoverflow.com/questions/31171502/scroll-jscrollpane-by-dragging-mouse-java-swing/31173371#31173371), [example](http://stackoverflow.com/questions/33907207/how-to-make-jscrollpane-in-borderlayout-containing-jpanel-smoothly-autoscroll/33907401#33907401) – MadProgrammer Mar 18 '16 at 09:57
  • @MadProgrammer thanks. That solved the problem, though, there is another (related) problem. Now it does show and the focus moves, but up to a point. I only get 14 rectangles on the screen that I can move over with. If i add more than that, then they will not be visible. JScrollpane won't move past 14 rectangles to the right. I tweaked with the TapeArea.size, and found out that those rectangles are added but not just shown. After changing the size, I got 9 visible, but if kept going to right (with step btn) they would be come visible. Any idea why? – fafinu Mar 18 '16 at 14:33

1 Answers1

2

I only get 14 rectangles on the screen that I can move over with. If i add more than that, then they will not be visible.

Scrolling only happens when the preferred size of the component added to the scrollpane is greater than the size of the scroll pane.

You need to override the getPreferredSize() method of your custom painting panel to reflect the area covered by each of your rectangles.

So as you move the rectangle to the right or down you may need to adjust the preferred size.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • I tried doing it, but i couldn't implement a generic/dynamic solution. I implemented getPreferredSize() method. But it only works if I manually set dimensions to a large number. But I want it to be dynamic. Cause these rectangles are drawn based on input, sometimes there might be a few of them sometimes a large number of them. (I am trying to implement a turing machine simulator). – fafinu Mar 18 '16 at 20:11
  • @fafinu, Exactly. So every time you add a new rectangle you need to know the rectangles location and size. Then you can determine if the rectangle bounds is greater than the preferred width/height of the panel. If so you update the preferred size. Same when you move a rectangle. The location will change which can impact the bounds. An easy way to do this is to just iterate through all the rectangles on the panel and determine the maximum bounds each time a rectangle is added or moved. – camickr Mar 18 '16 at 20:33