javafx button to read lines from txt to text fields - user-interface

I am creating a file object that is used in the open and close functions.
For now I am pointing to a specific location and using a fixed name. The file is populated with lines of data.
The button is on the pane, I have a function to openContact which is supposed to read the text file line by line and send the result to the text field setText method, and this function is called when you click on the button.
There are no syntax errors in the editor, but the clicking the button is not populating the fields in the GUI.
Other than that I am not sure what question to ask or what to search for.
I am attaching my code as it is.
Any hints or guidance toward the appropriate questions to ask or thought process would be appreciated.
package programmingassignment1;
import java.awt.Image;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.*;
//import javafx.scene.layout.StackPane;
//import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.io.*; //input/output
import java.util.Scanner;
//import java.util.*; //scanner, user input
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.shape.Rectangle;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
public class Address extends Application {
String contactFirst,
contactLast,
spouseFirst,
spouseLast,
street,
city,
state,
zip;
TextField tf_contactFirst = new TextField();
TextField tf_contactLast = new TextField();
TextField tf_spouseFirst = new TextField();
TextField tf_spouseLast = new TextField();
TextField tf_street = new TextField();
TextField tf_city = new TextField();
TextField tf_state = new TextField();
TextField tf_zip = new TextField();
TextArea ta_notes = new TextArea();
ExtensionFilter jpgExtension = new ExtensionFilter("JPG", "*.jpg");
ExtensionFilter pngExtension = new ExtensionFilter("PNG", "*.png");
ExtensionFilter allExtension = new ExtensionFilter("ALL", "*.*");
Rectangle imageBox = new Rectangle(10, 0, 10, 20);
FileChooser fc = new FileChooser();
#Override
public void start(Stage primaryStage){
//modify text area and register actions
ta_notes.setWrapText(true);
ta_notes.setEditable(true);
ta_notes.setPrefColumnCount(12);
ta_notes.setPrefRowCount(3);
//Setting an action for the Clear button
Button bt_cancel = new Button("Cancel");
bt_cancel.setOnAction(e -> {
tf_contactFirst.clear();
tf_contactLast.clear();
tf_spouseFirst.clear();
tf_spouseLast.clear();
tf_street.clear();
tf_city.clear();
tf_state.clear();
tf_zip.clear();
ta_notes.setText(null);
});
//Setting an action for the Open Contact button
Button bt_openContact = new Button("Open Contact");
File file = new File("AddressBook.txt");
bt_openContact.setOnAction(e -> {
new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent e){
try{openContact(file);}
catch(Exception f){f.getMessage();}
}
};
});
//Setting an action for the Save button
Button bt_save = new Button("Save");
bt_save.setOnAction(
new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent e){
try{saveContact(file);}
catch(Exception f){f.getMessage();}
}});
RadioButton rb_male = new RadioButton("Male");
RadioButton rb_female = new RadioButton("Female");
ToggleGroup tgrp = new ToggleGroup();
rb_male.setToggleGroup(tgrp);
rb_female.setToggleGroup(tgrp);
rb_male.setOnAction(e -> {
if(rb_male.isSelected()){int maleContact = 1;}
});
rb_female.setOnAction(e -> {
if(rb_female.isSelected()){int maleContact = 0;}
});
//create combo box and add items as an observable list
String[] x = {"Home Address", "Work Address"};
ComboBox cbo = new ComboBox(FXCollections.observableArrayList(x));
//cbo.setEditable(false);
cbo.setValue("Home Address");
// cbo.setOnAction(e -> {/**____________***/;});
//set imageBox rectangle action
//click in it, choose image, file, its displayed?
//fc is an import or not?
//setOnMouseClicked should work for any node or scene, why not this rect
/*imageBox.setOnMouseClicked((MouseEvent e) -> {
fc.setTitle("Open Image File");
fc.setInitialDirectory(new File("."));
fc.getExtensionFilters().addAll(jpgExtension, pngExtension, allExtension);
fc.setSelectedExtensionFilter(jpgExtension);
File picture = fc.showOpenDialog(primaryStage);
if (picture != null){
rootPane.getChildren().remove(imageBox);
contact.setImageFile(picture.getName());
Image userImage = new Image(picture.getName());
ImageView userView = new ImageView(userImage);
rootPane.getChildren().add(userView);
}
});*/
GridPane rootPane = new GridPane();
rootPane.add(new Label("First Name"), 1, 1);
rootPane.add(tf_contactFirst, 1, 2);
rootPane.add(new Label("Last Name"), 2, 1);
rootPane.add(tf_contactLast, 2, 2);
rootPane.add(new Label("Sex"), 3, 1);
rootPane.add(rb_female, 3, 2);
rootPane.add(rb_male, 3, 3);
rootPane.add(new Label("Spouse's First Name"), 1, 4);
rootPane.add(tf_spouseFirst, 1, 5);
rootPane.add(new Label("Spouse's Last Name"), 2, 4);
rootPane.add(tf_spouseLast, 2, 5);
rootPane.add(cbo, 1, 6);
rootPane.add(new Label("Address Street"), 1, 7);
rootPane.add(tf_street, 1, 8);
rootPane.add(new Label("City"), 1, 9);
rootPane.add(tf_city, 1, 10);
rootPane.add(new Label("State"), 2, 9);
rootPane.add(tf_state, 2, 10);
rootPane.add(new Label("Zip Code"), 3, 9);
rootPane.add(tf_zip, 3, 10);
rootPane.add(imageBox, 4, 1 );
//Label label = new Label();
rootPane.add(new Label("Notes"), 1, 11);
rootPane.add(ta_notes, 1, 12);
rootPane.add(bt_cancel, 2, 13);
rootPane.add(bt_save, 3, 13);
rootPane.add(bt_openContact, 1, 13);
//scene = window (isn't it just easier if someon mentions that?)
Scene scene = new Scene(rootPane, 1000, 500);
primaryStage.setTitle("Address Book");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public void saveContact(File file) throws FileNotFoundException, Exception{ //declaration
//this code might cause a FileNotFoundException
//if it does it creates an exception object of the above type
try{
//PrintWriter output = new PrintWriter (file);
PrintStream output = new PrintStream(file);
output.println(tf_contactFirst.getText());
output.println(tf_contactLast.getText());
output.println(tf_spouseFirst.getText());
output.println(tf_spouseLast.getText());
output.println(tf_street.getText());
output.println(tf_city.getText());
output.println(tf_state.getText());
output.println(tf_zip.getText());
output.close();
}
//what do do with exception
//here the catch clause with create another exception
//that is passed the result of the getMessage() method from the original exception
catch(FileNotFoundException e){
throw new Exception(e.getMessage());
}
}
//read same text file you save too
public void openContact (File file) throws FileNotFoundException, Exception{
try{
Scanner read = new Scanner(file);
while(read.hasNextLine()){
//how is a blank field recognized, how are two or three
//consecutive tokens handled
//how do I save the imageFileName
tf_contactFirst.setText(read.nextLine());
tf_contactLast.setText(read.nextLine());
//tf_contactGender.setText(read.nextLine());
tf_spouseFirst.setText(read.nextLine());
tf_spouseLast.setText(read.nextLine());
//tf_spouse_gender.setText(read.nextLine());
tf_street.setText(read.nextLine());
tf_city.setText(read.nextLine());
tf_state.setText(read.nextLine());
tf_zip.setText(read.nextLine());
//ta_notes.setText(read.nextLine());
}
}
catch(FileNotFoundException e){
throw new Exception(e.getMessage());
}
}
}

There are several issues with your code that are causing an issue.
First of all, the lambda statement in your setOnAction() method for bt_openContact is incorrect. The openContact() method is never actually being called.
You can correct that with either passing a new EventHandler directly:
bt_openContact.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
openContact(file);
} catch (Exception e) {
e.printStackTrace();
}
}
});
Or using a properly-formatted lamda statement:
bt_openContact.setOnAction(event -> {
try {
openContact(file);
} catch (Exception e) {
e.printStackTrace();
}
});
It appears you were trying to do both. :)
Note also the catch block. Your code is simply calling f.getMessage(), which returns a String. But you don't actually do anything with that String so even if there are errors, you wouldn't see them.
Instead, you should call f.printStackTrace() to actually print any exceptions to the console.
Unrelated Note: Please look into the Java Naming Conventions and stick to them.

Zephir's answer is completely correct.
To answer your question as to hints and guidance:
always, if possible think " why is this here? is there a reason for it? do i need it? " - this will hopefully prevent dead code like : " catch(Exception f){f.getMessage();} "
learn how to use debugers.
It looks like you're trying to figure out how a programming language works, and you probably have some prior experience with other programming languages. Whenever you attempt this, it's a good idea to follow learning trails such as the ones available at https://docs.oracle.com/javase/tutorial/
This is especially important as the more experience in different programming languages you have, the more things start to look the same when in fact they're completely different. Spending 3 hours doing simple tutorials such as these will spare you days of frustration trying to figure out what the hell is going on.
For some reason this looks to me like someone trying to write code directly in notepad or some text editing software. Don't. Use an IDE (netbeans, eclipse, etc.). These come with formatting tools and debuggers which would allow you to find simple issues such as these in less time than it took me to write this answer.

Related

How to make a SWT window/shell and all the components on it adjustable?

So I have created windows/shells with buttons in an application but I want everything to resize when expanded and not to stay in one corner. I have used SWT and window builder to achieve this I used Absolute layout and now when I press full screen it is all in one corner how could I make this aesthetically pleasing so all the buttons and labels expand as well?
Please take a look at Standard layouts in SWT. Refer How to position your widgets and Understanding Layouts.
For Example below is a sample code where I have created 2 labels and 2 text in a Grid Layout which will FILL horizontally when you resize. You can change it according to your needs.
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class SampleApplication
{
protected Shell shell;
private Text text;
private Text text_1;
/**
* Launch the application.
* #param args
*/
public static void main(final String[] args)
{
try
{
SampleApplication window = new SampleApplication();
window.open();
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* Open the window.
*/
public void open()
{
Display display = Display.getDefault();
createContents();
shell.open();
shell.layout();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
{
display.sleep();
}
}
}
/**
* Create contents of the window.
*/
protected void createContents()
{
shell = new Shell();
shell.setSize(450, 224);
shell.setText("SWT Application");
shell.setLayout(new GridLayout(2, false));
Label lblNewLabel = new Label(shell, SWT.NONE);
lblNewLabel.setText("Name");
lblNewLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
text = new Text(shell, SWT.BORDER);
text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
Label lblNewLabel_1 = new Label(shell, SWT.NONE);
lblNewLabel_1.setText("ID");
lblNewLabel_1.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
text_1 = new Text(shell, SWT.BORDER);
text_1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
}
}

JavaFX "quickfixes" with tooltips and hyperlinks

does JavaFX provide something like Eclipse Quickfixes? Meaning that you hover over a thing that is broken and got some solutions for it that you can apply immediately.
I know that there are tooltips but they can only contain text, I would need something clickable. Another solution would be something like Dialogs, but I don't want to open another window. I want it to appear on the current stage.
Any suggestions?
Edit: to make it clear, I want to adopt the concept of eclipse quickfixes onto a JavaFX based application, maybe showing a "quickfix" when hovering over a circle instance. I don't want to check any (java/javafx) source code.
Edit2: I've got a hyperlink on a tooltip now:
HBox box = new HBox();
Tooltip tooltip = new Tooltip();
tooltip.setText("Select an option:");
tooltip.setGraphic(new Hyperlink("Option 1"));
Tooltip.install(box, tooltip);
I've got three new problems now:
How to make the tooltip not disappear when leaving the HBox and staying there when entering the mouse into the tooltip?
How to add mulitple graphics / hyperlinks? Is it even possible?
How to first show the text and then, in a new line, display the graphics?
Thanks in advance!
You can add any node to a tooltip using the setGraphic() method. Here is a simple example demonstrating using a tooltip for "quick fix" functionality:
import java.util.Random;
import javafx.application.Application;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TooltipWithQuickfix extends Application {
#Override
public void start(Stage primaryStage) {
TextField textField = new TextField();
textField.pseudoClassStateChanged(PseudoClass.getPseudoClass("invalid"), true);
textField.setTextFormatter(new TextFormatter<Integer>(c -> {
if (c.getText().matches("\\d*")) {
return c ;
}
return null ;
}));
textField.textProperty().isEmpty().addListener((obs, wasEmpty, isNowEmpty) ->
textField.pseudoClassStateChanged(PseudoClass.getPseudoClass("invalid"), isNowEmpty));
Tooltip quickFix = new Tooltip();
Hyperlink setToDefault = new Hyperlink("Set to default");
Hyperlink setToRandom = new Hyperlink("Set to random");
setToDefault.setOnAction(e -> {
textField.setText("42");
quickFix.hide();
});
Random rng = new Random();
setToRandom.setOnAction(e -> {
textField.setText(Integer.toString(rng.nextInt(100)));
quickFix.hide();
});
VBox quickFixContent = new VBox(new Label("Field cannot be empty"), setToDefault, setToRandom);
quickFixContent.setOnMouseExited(e -> quickFix.hide());
quickFix.setGraphic(quickFixContent);
textField.setOnMouseEntered(e -> {
if (textField.getText().isEmpty()) {
quickFix.show(textField, e.getScreenX(), e.getScreenY());
}
});
VBox root = new VBox(textField);
root.getStylesheets().add("style.css");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
with the stylesheet (style.css):
.root {
-fx-alignment: center ;
-fx-padding: 24 10 ;
}
.text-field:invalid {
-fx-control-inner-background: #ff7979 ;
-fx-focus-color: red ;
}

Can't paint an image after choosing it from JFileChooser

Good evening. I have read a lot of topics here on stackoverflow or even internet but I can't find the solution to my problem.
I have an interface like this:
When I click on "Load Image A", I can choose the image that I want. Next I want to paint this image under the JLabel "Image A". But it doesn't want to show up.
Here is the code I wrote:
package projet;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class MonPanelImage extends JPanel{
private static final long serialVersionUID = -8267224342030244581L;
private BufferedImage image;
public MonPanelImage(File adresse)
{
try{
image = ImageIO.read(adresse);
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponents(g);
System.out.println("paint");
if(image != null){
g.drawImage(image, 20, 20, this);
}
}
}
and here is where I call it:
//panel image. This is my second panel which will be for the images
final JPanel second = new JPanel(new BorderLayout());
//panel button. This is the third panel for the buttons
rows = 0;
cols = 3;
hgap = 5;
vgap = 0;
JPanel third = new JPanel(new GridLayout(rows,cols,hgap,vgap));
//buttons
JButton boutonLoad1 = new JButton("Load image A");
boutonLoad1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int retour = fc.showDialog(frame, "Charger l'image");
if(retour == JFileChooser.APPROVE_OPTION){
String pathImage1 = fc.getSelectedFile().getAbsolutePath();
path1 = pathImage1;
File file = fc.getSelectedFile();
MonPanelImage panelImage1 = new MonPanelImage(file);
second.add(panelImage1, BorderLayout.WEST);
second.revalidate();
second.repaint();
}
}
});
At the very end, i add the 3 panels to my frame and set the frame to visible.
But I can't paint an image. Maybe I'm not doing it properly. Can someone help me please?
Thanks
super.paintComponents(g);
First of all it should be super.paintComponent(g), without the "s".
second.add(panelImage1, BorderLayout.WEST);
You are adding your image to a component using a BorderLayout. The BorderLayout will respect the width of your component, which is 0, so there is nothing to paint.
Whenever, you do custom painting you need to override the getPreferredSize() method to return the size of your component so the layout manager can do its job.
However, an easier solution is to just use a JLabel with an Icon. There is no need to do custom painting when you are painting the image at its real size.

How to get position of an item in ListView in JavaFX?

If I create a ListView in JavaFX like this:
ObservableList<String> elements = FXCollections.observableArrayList("John", "Doe");
ListView<String> lView = new ListView<String>(elements);
What I want to do is draw a line starting from the end of a row in the ListView, say from "John"
To do this, I need the location(x,y) of the row "John". Is it possible to get the location?
Update
This is a sample interface that I got using Swing and Piccolo2D. However, using that library is painful. I am wondering if I can do the same in JavaFX
It is possible, but it may not be as straight forward as you hoped. In order to determine the layout coordinates for a particular Cell within a ListView (or TableView/TreeView) you need to have access to that particular Cell object. The best way (and maybe only way in JavaFX 2.2) is to provide the container with a custom Cell and CellFactory that exposes each Cell. How you expose the Cell depends on what your triggers are for drawing the line.
Bases on your illustration, you'll need access to each cell once the ListViews are populated. You can do this with a List<ListCell<String>> field in the CellFactory. I'll mention one caveat here about ListCells. The ListViewSkin will reuse Cells whenever possible. That means that if you are going to try to populate and connect a list that ends up scrolling, then keeping your lines in the right place will be much more difficult. I'd recommend trying to ensure that all your list items fit on screen.
Below is an example with some notes in the comments. Take note that getting the correct coordinates for drawing your Line will probably require calculating the offset of your SceneGraph which I didn't do in this example.
package listviewcellposition;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewCellPosition extends Application {
// CustomCellFactory for creating CustomCells
public class CustomCellFactory implements
Callback<ListView<String>, ListCell<String>> {
List<ListCell<String>> allCells = new ArrayList<>();
#Override
public ListCell<String> call(final ListView<String> p) {
final CustomCell cell = new CustomCell();
allCells.add(cell);
return cell;
}
public List<ListCell<String>> getAllCells() {
return allCells;
}
}
// CustomCell is where the exposure occurs. Here, it's based on the
// Cell being selected in the ListView. You could choose a different
// trigger here but you'll need to explore.
public class CustomCell extends ListCell<String> {
// General display stuff
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(item == null ? "" : item);
setGraphic(null);
}
}
}
#Override
public void start(Stage primaryStage) {
// This pane will contain the lines after they are created.
// I set it into an AnchorPane to avoid having to deal with
// resizing.
Pane linePane = new Pane();
AnchorPane pane = new AnchorPane();
pane.setPrefSize(100, 250);
AnchorPane.setBottomAnchor(linePane, 0.0);
AnchorPane.setLeftAnchor(linePane, 0.0);
AnchorPane.setRightAnchor(linePane, 0.0);
AnchorPane.setTopAnchor(linePane, 0.0);
pane.getChildren().add(linePane);
ListView<String> lView = new ListView<>();
lView.setPrefSize(100, 250);
CustomCellFactory lCellFactory = new CustomCellFactory();
lView.setCellFactory(lCellFactory);
ListView<String> rView = new ListView<>();
rView.setPrefSize(100, 250);
CustomCellFactory rCellFactory = new CustomCellFactory();
rView.setCellFactory(rCellFactory);
lView.getItems().addAll("Bill", "Doctor", "Steve", "Joanne");
rView.getItems().addAll("Seuss", "Rowling", "King", "Shakespeare");
HBox root = new HBox();
root.getChildren().addAll(lView, pane, rView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
connectCells(lCellFactory, "Bill", rCellFactory, "Shakespeare", linePane);
connectCells(lCellFactory, "Doctor", rCellFactory, "Seuss", linePane);
connectCells(lCellFactory, "Steve", rCellFactory, "King", linePane);
connectCells(lCellFactory, "Joanne", rCellFactory, "Rowling", linePane);
}
// Looks up the ListCell<> for each String and creates a Line
// with the coordinates from each Cell. The calculation is very
// contrived because I know that all the components have the same
// x-coordinate. You'll need more complicated calculations if your
// containers are not aligned this way.
private void connectCells(CustomCellFactory lCellFactory, String lVal,
CustomCellFactory rCellFactory, String rVal, Pane linePane) {
List<ListCell<String>> lList = lCellFactory.getAllCells();
ListCell<String> lCell = null;
for (ListCell<String> lc : lList) {
if (lc.getItem() != null && lc.getItem().equals(lVal)) {
lCell = lc;
break;
}
}
List<ListCell<String>> rList = rCellFactory.getAllCells();
ListCell<String> rCell = null;
for (ListCell<String> rc : rList) {
if (rc.getItem() != null && rc.getItem().equals(rVal)) {
rCell = rc;
break;
}
}
if (lCell != null && rCell != null) {
double startY = lCell.getLayoutY() +
(lCell.getBoundsInLocal().getHeight() / 2);
double endY = rCell.getLayoutY() +
(rCell.getBoundsInLocal().getHeight() / 2);
Line line = new Line(0, startY,
linePane.getBoundsInParent().getWidth(), endY);
line.setStrokeWidth(2);
line.setStroke(Color.BLACK);
linePane.getChildren().add(line);
}
}
public static void main(String[] args) {
launch(args);
}
}

How to make a dynamic image at run time?

I'm working on a card game based on the NetBeans platform and I'm struggling to get my head around dynamic images. Why dynamic? Well I want the cards to adjust at run time to changes to the page (i.e. name, text, cost, etc).
My first hack at it was creating a component (JPanel) with labels pre-placed where I loaded the text/image based on the card values. That seems to work fine but then it became troublesome when I thought about some pages having a different look in later editions (meaning not everything would be on the same place).
So I'm trying to get an idea about ways to do this based on some kind of template.
Any idea?
There's a follow-up question at: JList of cards?
Finally I got some time to get back to this and was able to figure out a way using Java 2D tutorial.
The pictures are not near what I will use in my application but serves as proof of concept.
package javaapplication3;
import java.awt.*; import java.awt.font.FontRenderContext; import
java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute;
import java.awt.font.TextLayout; import java.awt.image.BufferedImage;
import java.io.File; import java.io.IOException; import
java.net.MalformedURLException; import java.net.URL; import
java.text.AttributedCharacterIterator; import
java.text.AttributedString; import java.util.ArrayList; import
java.util.HashMap; import java.util.logging.Level; import
java.util.logging.Logger; import javax.imageio.ImageIO;
/** * * #author Javier A. Ortiz Bultrón
*/ public class DefaultImageManager {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
try {
// TODO code application logic here
DefaultImageManager manager = new DefaultImageManager();
URL url = DefaultImageManager.class.getResource("weather-rain.png");
manager.getLayers().add(ImageIO.read(url));
url = DefaultImageManager.class.getResource("weather-sun.png");
manager.getLayers().add(ImageIO.read(url));
manager.addText(new Font("Arial", Font.PLAIN, 10), "Many people believe that Vincent van Gogh painted his best works "
+ "during the two-year period he spent in Provence. Here is where he "
+ "painted The Starry Night--which some consider to be his greatest "
+ "work of all. However, as his artistic brilliance reached new "
+ "heights in Provence, his physical and mental health plummeted. ",
200, 150, new Point(0, 0));
manager.generate();
} catch (MalformedURLException ex) {
Logger.getLogger(DefaultImageManager.class.getName()).log(Level.SEVERE,
null, ex);
} catch (IOException ex) {
Logger.getLogger(DefaultImageManager.class.getName()).log(Level.SEVERE,
null, ex);
}
}
/**
* Layers used to create the final image
*/
private ArrayList layers = new ArrayList();
private ArrayList textLayers = new ArrayList();
/**
* #return the layers
*/
public ArrayList<BufferedImage> getLayers() {
return layers;
}
private Dimension getMaxSize() {
int width = 0, height = 0;
for (BufferedImage img : getLayers()) {
if (img.getWidth() > width) {
width = img.getWidth();
}
if (img.getHeight() > height) {
height = img.getHeight();
}
}
return new Dimension(width, height);
}
public void addText(Font font, String text, int height, int width, Point location) {
BufferedImage textImage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
HashMap<TextAttribute, Object> map =
new HashMap<TextAttribute, Object>();
map.put(TextAttribute.FAMILY, font.getFamily());
map.put(TextAttribute.SIZE, font.getSize());
map.put(TextAttribute.FOREGROUND, Color.BLACK);
AttributedString aString = new AttributedString(text, map);
AttributedCharacterIterator paragraph = aString.getIterator();
// index of the first character in the paragraph.
int paragraphStart = paragraph.getBeginIndex();
// index of the first character after the end of the paragraph.
int paragraphEnd = paragraph.getEndIndex();
Graphics2D graphics = textImage.createGraphics();
FontRenderContext frc = graphics.getFontRenderContext();
// The LineBreakMeasurer used to line-break the paragraph.
LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);
// Set break width to width of Component.
float breakWidth = width;
float drawPosY = 0;
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
// Note: this won't occur for the English text in this sample.
// Note: drawPosX is always where the LEFT of the text is placed.
float drawPosX = layout.isLeftToRight()
? 0 : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(graphics, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
getTextLayers().add(textImage);
}
public void generate() throws IOException {
Dimension size = getMaxSize();
BufferedImage finalImage = new BufferedImage(size.width, size.height,
BufferedImage.TYPE_INT_ARGB);
for (BufferedImage img : getLayers()) {
finalImage.createGraphics().drawImage(img,
0, 0, size.width, size.height,
0, 0, img.getWidth(null),
img.getHeight(null),
null);
}
for(BufferedImage text: getTextLayers()){
finalImage.createGraphics().drawImage(text,
0, 0, text.getWidth(), text.getHeight(),
0, 0, text.getWidth(null),
text.getHeight(null),
null);
}
File outputfile = new File("saved.png");
ImageIO.write(finalImage, "png", outputfile);
}
/**
* #return the textLayers
*/
public ArrayList<BufferedImage> getTextLayers() {
return textLayers;
}
/**
* #param textLayers the textLayers to set
*/
public void setTextLayers(ArrayList<BufferedImage> textLayers) {
this.textLayers = textLayers;
} }
It still needs some refining specially on the placement of the text but it works. I guess I can implement a xml format to store all this information so is easily configurable. In the example below suns are drawn on top of rain, and the text is on top of all that. For my application each layer will build together the page I want.
Here are the images I used:
And the final result:

Resources