I am trying to change the look of JComboBox components by extending BasicComboBoxUI class. The problem is that when I use the extended MyComboBoxUI class, combo boxes stop functioning properly.
This SSCCE is demonstrating my problem. The first combo box displays the selected item of the second combo box, and the first combo box doesn't have arrow button painted and items cannot be selected.
Note: I had no problem changing JButton components in this manner.
Main class:
import javax.swing.JFrame;
import javax.swing.UIManager;
public class Main {
public static void main(String[] args) {
UIManager.put("ComboBoxUI", "MyComboBoxUI");
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
ContentPane contentPane = new ContentPane();
frame.setContentPane(contentPane);
frame.setSize(600, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
ContenPane class:
import java.awt.FlowLayout;
import javax.swing.JComboBox;
import javax.swing.JPanel;
public class ContentPane extends JPanel {
public ContentPane() {
setLayout(new FlowLayout());
JComboBox<String> firstComboBox = new JComboBox<>();
firstComboBox.addItem("firstComboBox - 1. item");
firstComboBox.addItem("firstComboBox - 2. item");
firstComboBox.addItem("firstComboBox - 3. item");
add(firstComboBox);
JComboBox<String> secondComboBox = new JComboBox<>();
secondComboBox.addItem("secondComboBox - 1.item");
secondComboBox.addItem("secondComboBox - 2. item");
secondComboBox.addItem("secondComboBox - 3. item");
add(secondComboBox);
}
}
MyComboBoxUI class:
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicComboBoxUI;
public class MyComboBoxUI extends BasicComboBoxUI {
private static MyComboBoxUI myComboBoxUI = new MyComboBoxUI();
public static ComponentUI createUI(JComponent component) {
return myComboBoxUI;
}
}
I think you want:
return new MyComboBoxUI();
When you have a static variable it means every combobox will share the same instance of the UI.
Related
So I have 3 separate classes, the settings button on the mainmenu class should switch to main menu, but it simply hides the first panel, same thing when i click return on the other menu, i would like to find a simple soluton without using a layout manager because i don't know how to have card layout communicate to the 2 classes, but thats the solution, it'd be nice if someone could give me some pointers on how to implement that:
public class Game extends JFrame {
MainMenu mainMenu;
Settings settings;
public Game(){
setSize(900,900);
setDefaultCloseOperation(3);
mainMenu = new MainMenu();
settings = new Settings();
mainMenu.setSettings(settings);
settings.setMainMenu(mainMenu);
add(settings,BorderLayout.CENTER);
add(mainMenu, BorderLayout.CENTER);
}
public static void main(String[] args) {
Game game = new Game();
game.setVisible(true);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainMenu extends JPanel {
Settings settings;
public void setSettings(Settings settings) {
this.settings = settings;
}
public MainMenu() {
setLayout(new GridLayout(1,3));
JButton Newgame = new JButton("New Game");
JButton Cont = new JButton("Continue");
JButton Sett = new JButton("Settings");
add(Newgame);
add(Cont);
SwitchMenu1 switchMenu1 = new SwitchMenu1();
Sett.addActionListener(switchMenu1);
add(Sett);
}
class SwitchMenu1 implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
if(isVisible()){
settings.setVisible(true);
setVisible(false);
}
}
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Settings extends JPanel {
MainMenu mainMenu;
public void setMainMenu(MainMenu mainMenu) {
this.mainMenu = mainMenu;
}
public Settings(){
JButton Return = new JButton("Return");
SwitchMenu2 switchMenu2 = new SwitchMenu2();
Return.addActionListener(switchMenu2);
add(Return, BorderLayout.SOUTH);
}
class SwitchMenu2 implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
if(isVisible()){
mainMenu.setVisible(true);
setVisible(false);
}
}
}
}
I want to have the other JPanel show up on button click, but it doesn't work, the first one simply disappears. How can i fix this?
Thanks a lot!
This sounds a use case for CardLayout. You have a JPanel, named for example cards, which uses a CardLayout manager. You add all your panels (cards) to that panel, giving them unique names (e.g., "MAIN_MENU", "SETTINGS", etc.). Then, instead of passing every other panel in each of your panels, you only pass the cards panel, which can be used to show the card you wish, e.g., cl.show(cards, "SETTINGS"); on clicking a button, for instance.
Update
As per #c0der's suggestion (see comments section below), the code structure has been updated.
Game.java
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Game extends JFrame {
JPanel cards;
CardLayout cardLayout;
public Game(){
MainMenu mainMenu = new MainMenu();
Settings settings = new Settings();
cardLayout = new CardLayout();
cards = new JPanel(cardLayout);
cards.add(mainMenu, "MAIN_MENU");
cards.add(settings, "SETTINGS");
mainMenu.setSetBtnActionListener(new BtnController("SETTINGS"));
settings.setReturnBtnActionListener(new BtnController("MAIN_MENU"));
add(cards);
setSize(640,480);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
class BtnController implements ActionListener {
String cardName;
public BtnController(String cardName) {
this.cardName = cardName;
}
#Override
public void actionPerformed(ActionEvent e) {
cardLayout.show(cards, cardName);
}
}
public static void main(String[] args) {
new Game();
}
}
MainMenu.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
public class MainMenu extends JPanel {
JButton setBtn;
public MainMenu() {
setLayout(new GridLayout(1, 3));
JButton newGameBtn = new JButton("New Game");
JButton contBtn = new JButton("Continue");
setBtn = new JButton("Settings");
add(newGameBtn);
add(contBtn);
add(setBtn);
}
public void setSetBtnActionListener(ActionListener al) {
setBtn.addActionListener(al);
}
}
Settings.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
public class Settings extends JPanel {
JButton returnBtn;
public Settings() {
returnBtn = new JButton("Return");
setLayout(new BorderLayout());
add(returnBtn, BorderLayout.SOUTH);
}
public void setReturnBtnActionListener(ActionListener al) {
returnBtn.addActionListener(al);
}
}
I want to be able to call the Introduction.Intro() method into my main file code, but it tells me I am unable to call a non-static method intro from a static context. Since I am still fairly new to coding I'm not entirely sure what the problem is. I've added my codes down below. I've tried countless online methods but sadly none have seemed to work.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Start extends JFrame implements ActionListener
{
private JFrame Main;
private JPanel PanelA, PanelB, PanelC;
private JLabel Text, ImageL;
private JButton Button;
private ImageIcon Image;
public Start ()
{
//Button
Button = new JButton("Start");
Button.addActionListener(new ButtonListener());
//Text
Text = new JLabel("Welcome To The Game"); //ADD NAME OF THE GAME
//Image
Image = new ImageIcon(getClass().getResource("download.jfif")); //ADD THE IMAGE FOR WELCOME
ImageL = new JLabel(Image);
//Top Panel (PanelA) - Image
PanelA = new JPanel();
PanelA.setBorder(BorderFactory.createEmptyBorder(0,200,150,200));
PanelA.setLayout(new FlowLayout(FlowLayout.CENTER));
PanelA.add(ImageL);
//Middle Panel (PanelB) - Text
PanelB = new JPanel();
PanelB.setBorder(BorderFactory.createEmptyBorder(50,200,10,200));
PanelB.setLayout(new FlowLayout(FlowLayout.CENTER));
PanelB.add(Text);
//Bottom Panel (PanelC) - Buttons
PanelC = new JPanel();
PanelC.setBorder(BorderFactory.createEmptyBorder(0,200,20,200));
PanelC.setLayout(new FlowLayout(FlowLayout.CENTER));
PanelC.add(Button);
//Main Frame
Main = new JFrame ();
Main.add(PanelA, BorderLayout.NORTH);
Main.add(PanelB, BorderLayout.CENTER);
Main.add(PanelC, BorderLayout.SOUTH);
Main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Main.setTitle("GAME TITLE"); //ADD THIS LATER
Main.pack();
Main.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent ae)
{
}
public class ButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if(e.getSource() == Button)
{
Introduction.Intro1(); //THESE LINE RIGHT HERE
return null; //THESE LINE RIGHT HERE
}
}
}
public static void main(String[] args)
{
new Start();
}
}
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Introduction
{
private JFrame Main;
private JPanel PanelD;
private JLabel Text, ImageL;
private JButton Button;
private ImageIcon Image;
public void Intro()
{
Image = new ImageIcon(getClass().getResource("guy.jfif"));
ImageL = new JLabel(Image);
PanelD = new JPanel();
PanelD.setBorder(BorderFactory.createEmptyBorder(0,100,10,100));
PanelD.setLayout(new FlowLayout(FlowLayout.CENTER));
PanelD.add(ImageL);
PanelD.setVisible(true);
Main.add(PanelD, BorderLayout.NORTH);
}
}
EDIT: So I made another method in the Introduction class where I added this line of code, it managed to fix the error, however, the panel isn't being saved and my JFrame is outputting blank.
public static JFrame Intro1()
{
Introduction M = new Introduction();
return M;
}
If you are looking to initialize the Introduction class in main method of Start class, You can add belo code in main method after Start()
Introduction M = new Introduction();
You main method becomes :
public static void main(String[] args)
{
new Start();
Introduction M = new Introduction();
m.Intro
}
Looking at this set of code, It looks like there is incompatible issue, as you have declare JFrame as return type, while you are returning instance of Introduction.
public static JFrame Intro1()
{
Introduction M = new Introduction();
return M;
}
I'm currently working on a project that requires the state of a JRadioButton to change when the record being viewed is updated.
We've had a few clients complain to us that when the record changes, if the JRadioButton is off-screen, it won't be updated until the screen is shown. This behavior seems to be a result of using the Windows Look and Feel, as it doesn't seem to happen when it is not set.
The code example below demonstrates this.
The default selected JRadioButton is 'Cat', so by selecting the 'Dog' JButton and then changing tab to 'Question', you can see the JRadioButton transition occur.
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
/**
* Example of JRadioButton not updating until it's parent panel becomes visible.
*/
public class RadioButtonExample extends JPanel implements ActionListener {
public static final String CAT = "Cat";
public static final String DOG = "Dog";
private final JRadioButton radCat;
private final JRadioButton radDog;
private final ButtonGroup grpAnimal;
public RadioButtonExample() {
super(new BorderLayout());
JLabel lblQuestion = new JLabel("Are you a cat or dog person?");
radCat = new JRadioButton(CAT);
radCat.setActionCommand(CAT);
radCat.setSelected(true);
radDog = new JRadioButton(DOG);
radDog.setActionCommand(DOG);
grpAnimal = new ButtonGroup();
grpAnimal.add(radCat);
grpAnimal.add(radDog);
JPanel pnlQuestion = new JPanel(new GridLayout(0, 1));
pnlQuestion.add(lblQuestion);
pnlQuestion.add(radCat);
pnlQuestion.add(radDog);
JButton btnSetCat = new JButton(CAT);
btnSetCat.setActionCommand(CAT);
btnSetCat.addActionListener(this);
JButton btnSetDog = new JButton(DOG);
btnSetDog.setActionCommand(DOG);
btnSetDog.addActionListener(this);
JPanel pnlButtons = new JPanel(new GridLayout(0, 1));
pnlButtons.add(new JLabel("Update your choice of animal"));
pnlButtons.add(btnSetCat);
pnlButtons.add(btnSetDog);
JTabbedPane tabPane = new JTabbedPane();
tabPane.addTab("Buttons", pnlButtons);
tabPane.addTab("Question", pnlQuestion);
add(tabPane, BorderLayout.LINE_START);
setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
}
public void actionPerformed(ActionEvent evt) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
grpAnimal.clearSelection();
if (CAT.equals(evt.getActionCommand())) {
grpAnimal.setSelected(radCat.getModel(), true);
}
else if (DOG.equals(evt.getActionCommand())) {
grpAnimal.setSelected(radDog.getModel(), true);
}
}
});
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("RadioButtonExample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
JComponent newContentPane = new RadioButtonExample();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
// Comment out the line below to run using standard L&F
setLookAndFeel();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void setLookAndFeel() {
try {
// Set Windows L&F
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
}
catch (Exception e) {
// handle exception
}
}
}
Is there a way to prevent this behavior or even speed it up to make it less noticeable for our users?
You might be able to disable the animation by specifying the swing.disablevistaanimation Java system property:
java -Dswing.disablevistaanimation="true" your-cool-application.jar
In the com.sun.java.swing.plaf.windows.AnimationController class, there is a VISTA_ANIMATION_DISABLED field that is initialized to the swing.disablevistaanimation property. This field determines whether the paintSkin method calls the skin.paintSkinRaw method if animations are disabled or else starts to get into the animations.
It works on my Windows 8.1 laptop with Java 8 (jdk1.8.0_65), so its effect does not seem to be limited to Windows Vista only.
I didn't really know how else to phrase that but essentially:
-I have a few separate "pieces" that I am trying to add onto a master frame; to keep the code from getting unwieldy I have each "piece" be its own class.
-I'm getting stuck on adding the panells onto the master frame, because the classes themselves aren't panels, rather the method of the class creates the panel, which creates issues that I don't know how to solve.
PIECE (works on its own when I have it make a dialog instead of be a panel):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PieceThing3 extends JPanel //<switched from JDialog
{
//set up variables here
private ActionListener pieceAction = new ActionListener()
{
public void actionPerformed (ActionEvent ae)
{
// Action Listener (this also works)
}
};
private void createPiece()
{
//setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
//setLocationByPlatform(true);
// the above are commented out when I switch from dialog to panel
JPanel contentPane = new JPanel();
//something that uses pieceAction is here
//two buttons, b and s, with action listeners are here
contentPane.add(b);
contentPane.add(s);
add(contentPane);
//pack();
//again, commented out to switch from dialog
setVisible(true);
System.out.println("hi I'm done");
//just to check and make sure it's done
}
public static void main(String[] args)
{
// TODO Auto-generated method stub
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new PieceThing3().createPiece();
}
});
}
}
Sorry that is very vague, but the intricacies are not as important as the general idea - it works perfectly when I have it create its own dialog box, but now I am trying to get it to make a panel within a master code, below:
MASTER:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CollectGUI extends JFrame{
private void createDialog(){
this.setSize(2000,1000);
this.setLocation(0,0);
this.setTitle("TITLE");
PieceThing3 pt = new PieceThing3();
//HERE, if I do pt.main(null); while it is in "dialog mode" (rather than panel) it pops up a dialog box and everything is hunky dory. But I don't know how to get it to add the method as a panel.
this.add(pt.main(null));
//this gives an error
this.setVisible(true);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new CollectGUI().createDialog();
}
}
As I said in the comments, if I just do pt.main(null) when pt is set to make a dialog, it does it, but if I try to add pt.main(null) as a panel it throws an error. Can anybody give me some insight on how to add a method of a class rather than a class? I'm pretty stumped.
THANK YOU!!
You are definitely on the right track working to maintain separation of concerns and implement your gui in a number of distinct components. Try something like this:
Panel1
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Panel1 extends JPanel {
public Panel1() {
this.add(new JLabel("This is panel 1"));
}
}
Panel2
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Panel2 extends JPanel {
public Panel2() {
this.add(new JLabel("This is panel 2"));
}
}
JFrame
import java.awt.BorderLayout;
import javax.swing.JFrame;
import org.yaorma.example.jframe.panel.panel1.Panel1;
import org.yaorma.example.jframe.panel.panel2.Panel2;
public class ExampleJFrame extends JFrame {
public ExampleJFrame() {
super("Example JFrame Application");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(400,400);
this.setLayout(new BorderLayout());
Panel1 pan1 = new Panel1();
Panel2 pan2 = new Panel2();
this.add(pan1, BorderLayout.NORTH);
this.add(pan2, BorderLayout.SOUTH);
this.setVisible(true);
}
}
main:
public class ExampleApplication {
public static void main(String[] args) throws Exception {
new ExampleJFrame();
}
}
EDIT:
Here's a Panel1 with a little more content.
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.yaorma.example.action.sayhello.SayHelloAction;
public class Panel1 extends JPanel {
//
// instance variables
//
private JButton pressMeButton;
//
// constructor
//
public Panel1() {
this.setLayout(new BorderLayout());
this.add(new JLabel("This is panel 1"), BorderLayout.NORTH);
this.initPressMeButton();
}
//
// button
//
private void initPressMeButton() {
this.pressMeButton = new JButton("Press Me");
this.pressMeButton.addActionListener(new PressMeButtonActionListener());
this.add(pressMeButton, BorderLayout.SOUTH);
}
//
// method to get the parent jframe
//
private JFrame getParentJFrame() {
Container con = this;
while(con != null) {
con = con.getParent();
if(con instanceof JFrame) {
return (JFrame)con;
}
}
return null;
}
//
// action listener for Press Me button
//
private class PressMeButtonActionListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
JFrame jFrame = getParentJFrame();
SayHelloAction action = new SayHelloAction(jFrame);
action.execute();
}
}
}
Action called by button:
import javax.swing.JFrame;
import javax.swing.JOptionPane;
public class SayHelloAction {
private JFrame owner;
public SayHelloAction(JFrame owner) {
this.owner = owner;
}
public void execute() {
JOptionPane.showMessageDialog(owner, "Hello World");
}
}
I have a show button to show a JTable on click but the table is not visible.
Note: when I remove the JScrollPane the code works properly but the header of the table is not shown, so any help please to make this code work properly without removing the JScrollPane
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class Training extends JFrame {
public Training() {
getContentPane().setLayout(new FlowLayout());
JTable table = new JTable();
table.setModel(new DefaultTableModel(new Object[][] { { "joe", "joe" },
{ "mickel", "mickel" }, }, new String[] { "LastName",
"FirstName" }));
final JScrollPane pane = new JScrollPane(table);
pane.setVisible(false);
getContentPane().add(pane);
JButton btn = new JButton("show");
add(btn);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
pane.setVisible(true);
}
});
}
public static void main(String[] args) {
Training app = new Training();
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setSize(600, 600);
app.setVisible(true);
}
}
After pane.setVisible(true); add the following:
getContentPane().validate();
getContentPane().repaint();
A few things to note:
Never extends JFrame class unnecessarily, or else you might need to extend another class which is very necessary but in java a single class may not extend more than one other class (no multiple inheritance).
Always create Swing components on Event Dispatch Thread via SwingUtilities.invokeLater(Runnable r) block.
Do not use setSize(..) call JFrame#pack() before setting JFrame visible
No need for getContentPane.add(..) or getContentPane().setLayout(..), simply call add(..) or setLayout(..) on JFrame instance as these calls are fowared to the contentPane.
The problem you have is you do not refresh you frame/container after setting pane visible. I disagree with #Dan. Do not use validate() (getContentPane() is not necesarry either) rather:
revalidate();
repaint();
as revalidate() covers validate(). Also validate is used when new JComponents are added to a visible component, whereas revalidate() is used when JComponent is removed/added from a visible component.
Here is a fixed version of the code with the above implemented:
After button pressed:
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
public class Training {
private JFrame frame;
public Training() {
frame = new JFrame();
frame.setLayout(new FlowLayout());
JTable table = new JTable();
table.setModel(new DefaultTableModel(new Object[][]{{"joe", "joe"},
{"mickel", "mickel"},}, new String[]{"LastName",
"FirstName"}));
final JScrollPane pane = new JScrollPane(table);
pane.setVisible(false);
frame.add(pane);
JButton btn = new JButton("show");
frame.add(btn);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
pane.setVisible(true);
frame.pack();//this is so the frame will resize after adding pane
frame.revalidate();
frame.repaint();
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Training();
}
});
}
}
UPDATE:
Also for a more reusable Layout, why not add all components to a JPanel, and add that JPanel to the JFrame, thus if you ever need to add more stuff its simple.