I want to create simple app able to edit images. Main view of app contains JSplitPane with two JScrollPane. Each JScrollPane contains JPanel. The right JPanel has several buttons etc. and the left JPanel is my drawing area.
Here is my problem...
When I first created JPanelDrawingArea I could set preferred size. If the size is bigger than size of JScrollPane the JScrollBars show up (in default it is equal). But when I load image to JPanelDrawingArea scroll bars don't update. Despite the fact I set new preferred size of JPanelDrawingArea (bigger than size of JScrollPane) scroll bars don't update unless I manually change the JSplitPanes divider position.
Here is my JSplitPane custom class:
public class DrawingPaneView extends JSplitPane{
private DrawingWorkMode drawingWorkMode;
private ImageWorkerView imageWorker;
JScrollPane workScrollPane;
JScrollPane pictureScrollPane;
private DrawingPaneController controller;
private Dimension minimumSize = new Dimension(100, 200);
private JPanel imagePanel;
public DrawingPaneView() {
setPreferredSize(new Dimension(ConfigClass.APP_WIDTH,ConfigClass.DRAWING_PANE_HEIGHT));
controller = new DrawingPaneController(this);
//Panel
drawingWorkMode = new DrawingWorkMode();
workScrollPane = new JScrollPane(drawingWorkMode);
//Image
imageWorker = new ImageWorkerView();
pictureScrollPane = new JScrollPane(imageWorker);
workScrollPane.setMinimumSize(minimumSize);
pictureScrollPane.setMinimumSize(minimumSize);
//addJPanels
this.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
this.setRightComponent(workScrollPane);
this.setLeftComponent(pictureScrollPane);
//addLeftPanelWithJButtonOnly
imagePanel = new ImagePanelView();
pictureScrollPane.setRowHeaderView(imagePanel);
this.setDividerLocation(ConfigClass.DRAWING_PANE_WIDTH);
this.setOneTouchExpandable(true);
}
//Change mode
public void changeMode(String mode){
drawingWorkMode.changeMode(mode);
}
}
And there is my custom JPanel which perform drawing:
public class ImageWorkerView extends JPanel {
private BufferedImage img;
private ImageWorkerController controller;
private int defaultBounds = 50;
private double scale=1.0;
int imgW;
int imgH;
public ImageWorkerView() {
//setLayout(new BorderLayout(0, 0));
controller = new ImageWorkerController(this);
}
public void setScale(double scale) {
this.scale = scale;
}
public void setImage(File image) {
try {
img = ImageIO.read(image);
if (img.getType() != BufferedImage.TYPE_INT_RGB) {
BufferedImage img2 =
new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics big = img2.getGraphics();
big.drawImage(img, 0, 0, null);
img = img2;
}
} catch (IOException e) {
System.out.println("Image could not be read");
}
}
private void adjustPreferredSize(Boolean defaultSize){
if(defaultSize){
//Calculate the proper size of drawing area
imgW = ConfigClass.DRAWING_PANE_WIDTH - ImagePanelView.PREFERRED_WIDTH-10;
imgH = ConfigClass.DRAWING_PANE_HEIGHT-50;
setPreferredSize(new Dimension(imgW,imgH));
controller.setWindowHeight(imgH);
}
else{
imgW = (int)(img.getWidth() * scale + (defaultBounds*2));
imgH = (int)(img.getHeight() * scale + (defaultBounds*2));
setPreferredSize(new Dimension(imgW,imgH));
controller.setWindowHeight(imgH);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if(img!=null){
if(scale!=1.0){
AffineTransform at = AffineTransform.getScaleInstance(scale, scale);
AffineTransformOp aop =
new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC);
g2.drawImage(img, aop, defaultBounds, defaultBounds);
}
else
g2.drawImage(img, defaultBounds, defaultBounds, null);
adjustPreferredSize(false);
}
else{
adjustPreferredSize(true);
}
}
}
And how i load image:
public class ImageWorkerController {
ImageWorkerView view;
ImageModel model;
public ImageWorkerController(ImageWorkerView workerView) {
this.view = workerView;
this.model = ApplicationContext.getObject(ImageModel.class);
//Load image
ApplicationContext.getObject(Context.class).addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if(Context.IMAGE_LOADED.equals(evt.getPropertyName())){
view.setImage((File) evt.getNewValue());
view.repaint();
}
}
});
public void setWindowHeight(int h){
model.setDrawingWindowHeight(h);
}
}
As you can see there is adjustPreferredSize() method, when it is first called, and it sets preferredSize bigger than JScrollPane, JScrollBars appear. But when it is called again it does nothing.
What is interesting, when I manually change divider's location JScrollBars show up, on screen below you have an example:
http://s17.postimage.org/e1nkja3zx/liliebead.jpg
So there is some kind of event, which makes JScrollPane to update? I've tried several ways: updateUI(), repaint(), revalidate(). None of them worked.
Any ideas what I am doing wrong?
In short, you need to revalidate() your ImageWorkerView (right where you call repaint()). This will ask the component and its parent for "re-layout" and that in turn will trigger necessary adjustments for the scroll bars.
Thanks for your answer! Your suggestion made me think. What actually I did wrong is call revalidate() immediatelly after repaint() so in fact revalidate() executes before paintComponent method in ImageWorkerView (I found this out during debugging). The proper way to do this is:
ApplicationContext.getObject(Context.class).addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if(Context.IMAGE_LOADED.equals(evt.getPropertyName())){
view.setImage((File) evt.getNewValue());
//view.repaint();
view.paintImmediately(new Rectangle(1, 1));
view.revalidate();
}
}
});
So now paintComponent sets preferred size and then revalidate() adjust scroll bars.
Related
So I'm currently having the problem that a large image will cover up smaller images in my program when i try to graphically display them in Java. I would like to know how to bring certain images to the front of the window so the large "background" image will stay in the background. Also, I do not believe it's a possibility in my program to simply implement the pictures in reverse order.
Here's the code I used:
my image manager class with the method I use to implement the images into the window,
import java.awt.*;
import javax.swing.*;
public class ImageManager extends JFrame {
private ImageIcon image1;
private JLabel label1;
private ImageIcon image2;
private JLabel label2;
private ImageIcon image3;
private JLabel label3;
public ImageManager() {
}
public void addBackground() {
image3 = new ImageIcon(getClass().getResource("background.png"));
label3 = new JLabel(image3);
add(label3);
}
public void addSeaweed() {
image1 = new ImageIcon(getClass().getResource("seaweed.png"));
label1 = new JLabel(image1);
add(label1);
}
public void addUnderwatervolcano() {
image2 = new ImageIcon(getClass().getResource("underwatervolcano.png"));
label2 = new JLabel(image2);
add(label2);
}
}
and here's where I use the methods from ImageManager:
a method to display a picture of seaweed using the grow() method,
public Seaweed() {
setLayout(new FlowLayout());
World.gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
World.gui.setVisible(true);
World.gui.pack();
World.gui.setTitle("seaweed and underwatervolcano");
carbon = 0;
}
public void grow() {
if(World.getOceanCarbon() >= 10) {
addCarbon(10);
World.addOceanCarbon(-10);
World.gui.addSeaweed();
World.gui.pack();
}
}
and heres the method in a different class that uses the grow() method from the Seaweed class and the gui.addBackground() from the ImageManager class,
public static void runWorld() {
gui.addBackground();
UnderwaterVolcano volcano = new UnderwaterVolcano();
Seaweed seaweed = new Seaweed();
volcano.erupt();
seaweed.grow();
gui.setMinimumSize(new Dimension(905, 560));
}
}
i would like to know how i make it so gui.addBackground() does not cover up the picture of seaweed from gui.addSeaweed() (which was invoked in the seaweed.grow() method) while still invoking gui.addBackground() before invoking gui.addSeaweed(). Is there anyway I can manipulate at the method call the order in which images display in a window? I don't have a very good understanding of JFrame so please be very explanatory with your answers, all help appreciated.
Well your current logic adds all the images to the frame. Swing actually paints the last component added first. That is components are painted based on highest ZOrder being painted first. The default ZOrder of the component is simply the component count at the time the component is added to the panel. So yes based on your current logic the background will paint over top of the other images.
A couple of simple solutions:
1) Manage the ZOrder of your components.
After you add the component to frame you can reset the ZOrder so the component is painted last. So the basic code is
add(aComponent);
setComponentZOrder(aComponent, 0);
2) Add the child images to the background image instead of add all images to the frame. So you have a structure like:
- frame
- background image
- seaweed
- volcano
So the basic logic would be something like:
frame.add( background );
background.add( seaweed );
background.add( volcano );
Since in looks like the seaweed/volcano images are at random places on the background you would still need to manage the size/location of each of these images.
Note when adding child components to the background the child components must be fully contained within the background image or the child image will be truncated.
This is the approach I would use since it better describes the structure of your application. That is your frame contains a background and the background contains other child components. Nesting of components is common to get a desired layout of a frame.
You have to use a LayeredPane. Here is a working example of my own. You only have to replace the used images by some of yours.
The Main class:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
public class Main{
static JFrameWin jFrameWindow;
public static void main(String[] args) {
SwingUtilities.invokeLater(runJFrameLater);
}
public static class JFrameWin extends JFrame{
public JFrameWin(){
// init
initJFrame(this);
JPanelSettings jPanelSettings = new JPanelSettings(this.getWidth(), this.getHeight(), this.getX(), this.getY());
JLayeredPane enclosingJLayeredPane = getEnclosingJLayeredPane(jPanelSettings);
jPanelSettings = new JPanelSettings(this.getWidth(), this.getHeight(), this.getX(), this.getY(), new File("billard_1.jpg"));
JPanel backgroundJPanel = getJPanel(jPanelSettings);
jPanelSettings = new JPanelSettings(this.getWidth()-100, this.getHeight()-100, this.getX()+5, this.getY()+20, new File("billard2.jpg"));
JPanel firstLayerJPanel = getJPanel(jPanelSettings);
jPanelSettings = new JPanelSettings(this.getWidth() - 200, this.getHeight() - 200, this.getX() + 60, this.getY() + 60, new File("painter.jpg"));
JPanel secondLayerJPanel = getJPanel(jPanelSettings);
// assemble
enclosingJLayeredPane.add(backgroundJPanel);
enclosingJLayeredPane.add(firstLayerJPanel);
enclosingJLayeredPane.add(secondLayerJPanel);
// adjust layers
enclosingJLayeredPane.setLayer(backgroundJPanel, 0);
enclosingJLayeredPane.setLayer(firstLayerJPanel, 1);
enclosingJLayeredPane.setLayer(secondLayerJPanel, 2);
// add object to JFrame
this.add(enclosingJLayeredPane, BorderLayout.CENTER);
}
}
static Runnable runJFrameLater = new Runnable() {
#Override
public void run() {
jFrameWindow = new JFrameWin();
jFrameWindow.setVisible(true);
}
};
private static void initJFrame(JFrame jFrame) {
jFrame.setTitle("Boxing Test");
jFrame.setSize(600, 600);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private static JLayeredPane getEnclosingJLayeredPane(JPanelSettings jPanelSettings){
JLayeredPane jLayeredPane = new JLayeredPane();
jLayeredPane.setBounds(jPanelSettings.getXPosition(), jPanelSettings.getYPosition(), jPanelSettings.getWidth(), jPanelSettings.getHeight());
return jLayeredPane;
}
private static JPanel getJPanel(JPanelSettings jPanelSettings){
BufferedImage bufferedImage = null;
try {
bufferedImage = ImageIO.read(jPanelSettings.getImagePath());
} catch (IOException ex) {
System.out.println("Error" + ex.toString());
}
// fit image to frame size
Image scaledBufferedImage = bufferedImage.getScaledInstance(jPanelSettings.getWidth(), jPanelSettings.getHeight(), Image.SCALE_DEFAULT);
JLabel jLabel = new JLabel(new ImageIcon(scaledBufferedImage));
JPanel jPanel = new JPanel();
jPanel.setBounds(jPanelSettings.getXPosition(), jPanelSettings.getYPosition(), jPanelSettings.getWidth(), jPanelSettings.getHeight());
jPanel.add(jLabel);
return jPanel;
}
}
A helper-class
import java.io.File;
public class JPanelSettings {
private int height;
private int width;
private int xPosition;
private int yPosition;
private File imagePath;
// Basic constructor
public JPanelSettings(){ }
// size and positioning constructor
public JPanelSettings(int width,int height, int xPosition, int yPosition ){
setWidth(width);
setHeight(height);
setXPosition(xPosition);
setYPosition(yPosition);
}
// Full constructor
public JPanelSettings(int width,int height, int xPosition, int yPosition, File imagePath ){
setWidth(width);
setHeight(height);
setXPosition(xPosition);
setYPosition(yPosition);
setImagePath(imagePath);
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getXPosition() {
return xPosition;
}
public void setXPosition(int xPosition) {
this.xPosition = xPosition;
}
public int getYPosition() {
return yPosition;
}
public void setYPosition(int yPosition) {
this.yPosition = yPosition;
}
public File getImagePath() {
return imagePath;
}
public void setImagePath(File imagePath) {
this.imagePath = imagePath;
}
}
By using Java swing, what available approach are there to create a foreground image (such as an image of a knight) which is movable on a static background image?
Shall we use JLabel with image icons?
This solution also addresses the issues mentioned in: Images In JFrame are overwriting each other not displaying both images over eachother
If we try to add a background and some foreground images, it can be a little tricky if we intend to let those images overlap each other as many layouts provided by Java may prevent components (such as JLabels) from overlapping each other. Positioning the images to the exact location can be an issue too.
I will suggest a different approach when we want to create a screen similar to those we see in games:
Instead of creating multiple JLabel filled with imageIcon, an alternative will be drawing directly on the panel. This is a customized panel with instances of images we are interested to draw.
class DrawingSpace extends JPanel
{
private BufferedImage bg, hero;
private int bgWidth, bgHeight;
private int heroWidth, heroHeight;
private int scWidth, scHeight;
private int mouseX, mouseY;
public DrawingSpace(){
loadImages();
init();
setPreferredSize(new Dimension(scWidth, scHeight));
addMouseMotionListener(new MouseHandler());
}
private void init(){
mouseX = 0;
mouseY = 0;
heroWidth = hero.getWidth();
heroHeight = hero.getHeight();
bgWidth = bg.getWidth();
bgHeight = bg.getHeight();
scWidth = bgWidth;
scHeight = bgHeight;
}
private void loadImages(){
try{
bg = ImageIO.read(getClass().getResource("Images/background.jpg"));
hero = ImageIO.read(getClass().getResource("Images/knight.png"));
}catch(IOException ioe){System.out.println("Unable to open file");}
}
#Override public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(bg, 0, 0, bgWidth, bgHeight, null);
g.drawImage(hero, mouseX-(heroWidth/2), mouseY-(heroHeight/2), heroWidth, heroHeight, null);
}
private class MouseHandler implements MouseMotionListener
{
#Override public void mouseMoved(MouseEvent e){
mouseX = e.getX();
mouseY = e.getY();
repaint();
}
#Override public void mouseDragged(MouseEvent e){}
}
}
A runner class to drive the codes:
class KnightRunner
{
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
JFrame frame = new JFrame("Knight Runner");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DrawingSpace());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
im trying to set a background image on a JPanel that resizes with the panel. I have no problem in showing the picture, but as soon as I use:
background = background.getScaledInstance(300, -1, Image.SCALE_SMOOTH );
nothing is shown. Any ideas on why?
The code:
import javax.swing.*;
import java.awt.*;
public class LoginJPanel extends JPanel
{
private Image background;
public LoginJPanel()
{
super();
background = new ImageIcon("C:\\ASYS\\Stories\\Authentication UI\\AVDsplashscreen_tiny.jpg").getImage();
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
background = background.getScaledInstance(300, -1, Image.SCALE_SMOOTH );
g.drawImage(background, 0, 0, this);
}
public static void main (String[] args)
{
LoginJPanel ip = new LoginJPanel();
JFrame jf = new JFrame ();
jf.setLayout (new BorderLayout ());
jf.add (ip, BorderLayout.CENTER);
jf.setSize (1000, 600);
jf.setLocation (150, 150);
jf.setVisible (true);
jf.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
}
}
In the end what I made is (as suggested by the comments) to move the scaling outside the paint method. I created a public method which is called from the parent components to notify the panel about the new size, and scale the picture acording to that:
public void initSize(int _width, int _height)
{
int h = background.getHeight(null);
int w = background.getWidth(null);
if (w - _width > h - _height)
{
scaleVertically(_width, _height);
}
else
{
scaleHorizontally(_width, _height);
}
}
I guess I should do this on some listener because this is not very elegant, but I didn't know how to do so.
I want to allow users to be able to "draw" with their mouse (click and drag) to create and size a JTextArea. As well, I would like to have the text areas as resizeable.
Something like this:
comes to mind, but as a JTextArea instead of just a square.
Is there something in Java that would allow me to easily do this? I first thought to allow the user to draw a rectangle and just grab the co-ordinates and size to create the JTextArea. I am unsure on how to do the resizing though.
Edit: "Component Resizer / Reszing" was the term I was looking for and I'm adding it here in case someone else is looking for something similar!
You can found a solution here
I have already try it and the result is very well. In the tutorial there is a reference to another implementation here.
The resizing the JTextArea can be done easily enough via calling setBounds(...) on it -- or better on the JScrollPane that holds it, but you will need to use a null or similar (JLayeredPane) layout on the container that holds the JTextArea and will likely need to repaint the container after resizing the JScrollPane. You will also have to revalidate the scrollpane's viewport so it will re-layout the textarea that it holds.
e.g.,
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class ResizeableTextArea extends JPanel {
private static final int PREF_WIDTH = 700;
private static final int PREF_HEIGHT = 500;
private static final int ROWS = 60;
private static final int COLS = 80;
private static final Color RECT_COLOR = new Color(180, 180, 255);
private JTextArea textArea = new JTextArea(ROWS, COLS);
private JScrollPane scrollPane = new JScrollPane(textArea);
private int x, y, width, height;
private boolean drawRect = false;
public ResizeableTextArea() {
setLayout(null);
add(scrollPane);
MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
addMouseListener(myMouseAdapter);
addMouseMotionListener(myMouseAdapter);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (drawRect) {
g.setColor(RECT_COLOR);
g.drawRect(x, y, width, height);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_WIDTH, PREF_HEIGHT);
}
private class MyMouseAdapter extends MouseAdapter {
private int innerX, innerY;
#Override
public void mousePressed(MouseEvent e) {
x = e.getX();
y = e.getY();
innerX = x;
innerY = y;
width = 0;
height = 0;
drawRect = true;
}
#Override
public void mouseDragged(MouseEvent e) {
calcBounds(e);
drawRect = true;
ResizeableTextArea.this.repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
calcBounds(e);
drawRect = false;
scrollPane.setBounds(x, y, width, height);
scrollPane.getViewport().revalidate();
ResizeableTextArea.this.repaint();
}
private void calcBounds(MouseEvent e) {
width = Math.abs(innerX - e.getX());
height = Math.abs(innerY - e.getY());
x = Math.min(innerX, e.getX());
y = Math.min(innerY, e.getY());
}
}
private static void createAndShowUI() {
JFrame frame = new JFrame("ResizeableTextArea");
frame.getContentPane().add(new ResizeableTextArea());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
You should be able to use the Component Resizer.
that not really good idea, sure is possible to put Image or ImageIcon as BackGround, better would be use for that JLabel with Icon, then you can painting selection easily
I am trying to create an image viewer the the idea is that the viewer will pop up with one image a random button and a reset button to let the user to click the button and cycle through a list of different images randomly. I can open the viewer but cant get the viewer to rotate the images. here is the code. I would be grateful for any help
import java.awt.*;
import javax.swing.*;
public class CreateImage extends JFrame {
private JButton jbtRandom = new JButton("Random");
private JButton jbtReset = new JButton ("Reset");
public CreateImage() {
JPanel panel = new JPanel();
panel.add(jbtRandom);
panel.add(jbtReset);
Image image1 = new ImageIcon("kobe.jpg").getImage();
Image image2 = new ImageIcon("joe.jpg").getImage();
Image image3 = new ImageIcon("sidney.jpg").getImage();
Image image4 = new ImageIcon("bugs.gif").getImage();
Image image5 = new ImageIcon("mac.jpg").getImage();
Image image6 = new ImageIcon("snooki.jpg").getImage();
setLayout(new GridLayout(2, 0, 5, 5));
add(new ImageViewer(image1));
/*add(new ImageViewer(image2));// <== extra lines form first viewer attempt
add(new ImageViewer(image3)); //, <== which showed all images at once.
add(new ImageViewer(image4));// <== only need one image and to flip
add(new ImageViewer(image5));// <== to a random image
add(new ImageViewer(image6));// <== */
}
public class ImageViewer extends JPanel {
private java.awt.Image image;
private boolean stretched = true;
private int xCoordinate;
private int yCoordinate;
public ImageViewer() {
}
public ImageViewer(Image image) {
this.image = image;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (image != null)
if (isStretched())
g.drawImage(image, xCoordinate, yCoordinate, getWidth(), getHeight(), this);
else
g.drawImage(image, xCoordinate, yCoordinate, this);
}
public java.awt.Image getImage() {
return image;
}
public void setImage(java.awt.Image image) {
this.image = image;
repaint();
}
public boolean isStretched() {
return stretched;
}
public void setStretched(boolean stretched) {
this.stretched = stretched;
repaint();
}
public int getXCoordinate() {
return xCoordinate;
}
public void setXCoodinate(int xCoordinate) {
this.xCoordinate = xCoordinate;
}
public int getYCoordinate() {
return xCoordinate;
}
public void setYCoodinate(int yCoordinate) {
this.yCoordinate = yCoordinate;
repaint();
}
}
public static void main(String[] args) {
JFrame frame = new CreateImage();
frame.setTitle("Random Image-Click The Button");
frame.add(new JButton("Random"));
frame.add(new JButton("Reset"));
frame.setSize(400, 320);
frame.setLocationRelativeTo(null); //Center Frame
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Here are some steps you need to take to get started:
1) Instead of just creating and throwing away the images. Put them somewhere - like in a List or Map.
2) Add an event handler to your Random button.
3) On clicking that button, choose and show a new image from your List or Map.
Once you've done that post another more specific question if you're still stuck. You're quite far away from getting your end-goal at the moment so for now just focus on responding to a user event (clicking your buttons) to start with.
Refer to this to get started.