How does JButton implement its icons? - java

I'm trying to understand how Swings JButton/AbstractButton implements the painting of its icons (defaultIcon, disabledIcon, pressedIcon, etc.).
I have found the fields/getters/setters for said icons in AbstractButton, but apparently the assorted paint methods are inherited directly from JComponent. This begs the question how the icons are ever painted!? Obviously they are, but I could not find the code that does it.

Paiting of components in Swing is made by the Look-n-Feel. So if you want to find how the icon of a button is painted simply look the method paintIcon of the class BasicButtonUI. But each Look-n-Feel you use can provide different painting.

The icon in JButton is painted by the UI Class. And the UI class is defined by the Look and Feel. It is an implementation of javax.swing.plaf.ButtonUI.
JComponent.paint method get the ui property from the instance and invoke its update method (and is where the icon is painted). The UI of the instance is acquired by the UIManager.
You can se the paint method on Open JDK BasicButonUI

I hope I understand your question: are you trying to understand how does an icon get painted on an JButton? Take a look at the paint method from JComponent (its super super class). I'll add comments to make it easier to explain:
public void paint(Graphics g) {
boolean shouldClearPaintFlags = false;
//Check the size of the component: don't draw anything with a negative width or height!
if ((getWidth() <= 0) || (getHeight() <= 0)) {
return;
}
//Create new Graphics objects (why? to create the images)
Graphics componentGraphics = getComponentGraphics(g);
Graphics co = componentGraphics.create();
try {
RepaintManager repaintManager = RepaintManager.currentManager(this);
//Initialize a RepaintManager (JavaDoc says: "This class manages repaint requests, allowing the number of repaints to be minimized")
//Create a rectangle at the size of a graphics object
Rectangle clipRect = co.getClipBounds();
int clipX;
int clipY;
int clipW;
int clipH;
//If the rectangle is null, then give it default values
if (clipRect == null) {
clipX = clipY = 0;
clipW = getWidth();
clipH = getHeight();
} else { //otherwise, use its coordinates
clipX = clipRect.x;
clipY = clipRect.y;
clipW = clipRect.width;
clipH = clipRect.height;
}
//Ajust the clip widths and heights
if(clipW > getWidth()) {
clipW = getWidth();
}
if(clipH > getHeight()) {
clipH = getHeight();
}
//If your Component is placed on a JComponent (or extended class), then make adjustments
if(getParent() != null && !(getParent() instanceof JComponent)) {
adjustPaintFlags();
shouldClearPaintFlags = true;
}
//Check if the component is printing (private flag IS_PRINTING)
int bw,bh;
boolean printing = getFlag(IS_PRINTING);
//If the component is not printing its contents, and if the repain manager is double buffered, AND if not ancestor (subclass) is buffering AND if the components is buffered....
//JavaDoc says for RepaintManager.isDoubleBufferingEnabled(): "Returns true if this RepaintManager is double buffered. The default value for this property may vary from platform to platform."
if(!printing && repaintManager.isDoubleBufferingEnabled() &&
!getFlag(ANCESTOR_USING_BUFFER) && isDoubleBuffered()) {
//... then start painting the Graphics
repaintManager.beginPaint();
try {
repaintManager.paint(this, this, co, clipX, clipY, clipW, clipH);
} finally {
repaintManager.endPaint();
}
//if there is an exception, a try/finally is required to avoid the RepaintManager being "left in a state in which the screen is not updated" (JavaDoc)
}
else {
// Will ocassionaly happen in 1.2, especially when printing.
if (clipRect == null) {
co.setClip(clipX, clipY, clipW, clipH);
}
//Checks if the rectangle at the specified coordinates if obscured
if (!rectangleIsObscured(clipX,clipY,clipW,clipH)) {
//Then paint the graphics (or print if printing is true)
if (!printing) {
paintComponent(co);
paintBorder(co);
} else {
printComponent(co);
printBorder(co);
}
}
//Also paint the children (eg: a JPanel has a JLabel and a JButton as children if you add them to the panel) (or print if printing is true)
if (!printing) {
paintChildren(co);
} else {
printChildren(co);
}
}
} finally {
//Clean up!!
co.dispose();
if(shouldClearPaintFlags) {
setFlag(ANCESTOR_USING_BUFFER,false);
setFlag(IS_PAINTING_TILE,false);
setFlag(IS_PRINTING,false);
setFlag(IS_PRINTING_ALL,false);
}
}
}
In other words, there is a lot of code involved in painting Graphics objects, but the steps are pretty simple once you've analyzed the code:
Create new Graphics objects from the one passed in parameter;
Initialize a RepaintManager for repaint requests;
Check if the graphics can be painted, and do so if possible;
Finalize by repainting the children;
You're done!
If you want to know more on how the painting process is applied, then according to Oracle's Java documentation:
In Swing, painting begins with the paint method, which then invokes paintComponent, paintBorder, and paintChildren. The system will invoke this automatically when a component is first painted, is resized, or becomes exposed after being hidden by another window.
Programatic repaints are accomplished by invoking a component's repaint method; do not invoke its paintComponent directly. Invoking repaint causes the painting subsystem to take the necessary steps to ensure that your paintComponent method is invoked at an appropriate time.
You can invoke repaint multiple times from within the same event handler, but Swing will take that information and repaint the component in just one operation.
For components with a UI Delegate, you should pass the Graphics paramater with the line super.paintComponent(g) as the first line of code in your paintComponent override. If you do not, then your component will be responsible for manually painting its background. You can experiment with this by commenting out that line and recompiling to see that the background is no longer painted.
I took the code from here.
I hope I was able to help you,
Cheers

Related

setLocation() and setBounds() method for JButton not working in specific cases

I found similar questions on this forum, but they are not exactly my problem. I have a JPanel with absolute layout and on that panel, I have two JButtons. One is called swapButton, which swaps position of two buttons on the same panel and another is openButton, which opens an image do some processing with that image and with some buttons on the same panel and then calls swapButton.doClick().
Code for action performed by openButton:
private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {
FileDialog filedialog = new FileDialog(GameFrame.this,"Open File",FileDialog.LOAD);
filedialog.setVisible(true);
try{
if(filedialog.getFile() != null){
filename = filedialog.getDirectory() + filedialog.getFile();
file = new File(filename);
File deleteFile = new File(defaultPath.toString());
deleteFile.delete();
Files.copy(file.toPath(),defaultPath);
file = new File(defaultPath.toString());
imageSelected = true;
newGame = true;
cropImage();
setImage();
}
}
catch(IOException e){}
if(imageSelected){
setCombination();
swapButton.doClick();
moves = 0;
msgLabel.setText("");
}
}
Code for action performed by swapButton:
private void swapButtonActionPerformed(java.awt.event.ActionEvent evt) {
int n = Integer.valueOf(numText.getText()); //gets value from a text area
swapButton(n);
}
Code for swapButton method:
void swapButton(int i)
{
javax.swing.JButton button1 = buttonList[i], button2 = emptyButton;
int x1 = button1.getX(), y1 = button1.getY();
int x2 = button2.getX(), y2 = button2.getY();
button1.setLocation(x2, y2);
button2.setLocation(x1, y1);
int p1 = pos[i], p2 = pos[8];
pos[i] = p2;
pos[8] = p1;
arr[p1] = 8;
arr[p2] = i;
moves++;
}
I coded the action performed by swapButton in a seperate method for a purpose.
The problem is, when I click on openButton, all actions of that button works perfectly and swapButton.doClick() is also called, but the location of the buttons in my JPanel remains the same instead of calling setLocation() method in swapButton() method. But when I click on swapButton, all action in swapButton() method works fine. I also tried calling swapButton.doClick() from other area of the code and it works fine.
I printed the locations of the buttons after calling setLocation() method in swapButton() method using getLocation() method and it shows new locations for those buttons, but there are no changes in locations of those buttons in my JFrame. I also tried using setBounds() and getBounds() method and got the same result.
Is it some bug? Or something wrong done by me?
First of all, absolute layout means no layout manager or null layout manager. This is absolutely not recommended. For any beginning or intermediate Swing programmers out there, you should essentially never use absolute layout. If I were doing this I might create my own custom implementation of the java.awt.LayoutManager interface, and give that implementation a swap() method. That would keep all the location-swapping stuff encapsulated in my custom layout manager. But that's not what you asked.
It's hard to answer your question without more details, but is there a reason you need to call the swapButton's doClick() method? You would call that if it were important for the user to see the button appear to be pushed on the screen. [doClick()'s default on-screen push length is 68 milliseconds, btw, during which the EDT* will be frozen.] If you're only concerned that swapButton's ActionListener method gets invoked, then it may work better to simply call that method directly. That way, the openButton stuff does not depend on the timing of when swapButton's listeners are registered and such.
If you replace swapButton.doClick() with swapButtonActionPerformed() does it work any better?
Something else to watch for is that you're doing all this location swapping stuff on the event dispatch thread (EDT). From your description there's no indication that you are not running on the EDT, but we can't tell for sure without seeing the rest of your code. Hence the usefulness of an SSCCE.
*footnote: unless you're not calling it from the EDT, which is its own problem

How do I properly paint to a frame/panel?

I've tried many variations of everything at the bottom of this class and so far nothing works. Occasionally an edit will cause the print statement to work, but the window just always opens at a size that isn't even the one I set it up and stays blank. I don't know what's wrong with it. I'm trying to print 1024 rectangles on my window with a pause inbetween each print. The values are right, they're just not getting painted for some reason. Changing the method to paintComponent doesn't seem to do much either. The code is long, so here's a pastebin: http://pastebin.com/ridipz3X. The important stuff is at the end though:
JFrame frm = new TestEnvironment();
frm.setSize(1152, 1152);
frm.setVisible(true);
JPanel panel = new JPanel();
frm.add(panel);
t = 0;
i = 0;
while (t < x - 1) {
panel.repaint();
j++;
t++;
Thread.sleep(10000);
}
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
}
public void paint(Graphics g) {
g.setColor(Color.black);
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(getForeground());
try {
for (int h = 0; h < 1152; h++) {
g.drawRect(h, 0, (int) (((ampArray[h][j]) / maxFreq) * 1152),
1);
g.fillRect(h, 0, (int) (((ampArray[h][j]) / maxFreq) * 1152),
1);
System.out.println(ampArray[h][j]);
}
} finally {
g.dispose();
}
}
}
Thanks
Painting is typically done from within the paintComponent method of a component that extends from JComponent, typically a JPanel, depending on your needs.
You should refrain from overriding paint of top level containers like JFrame for a number of reasons, including, they are not double buffered, you will end up painting under the window decorations, Swing windows contain a number of layered components which make up the viewable content of a window, meaning that you will either paint over or under this content, which just gets messy.
You should never dispose of Graphics context that you did not create yourself.
Swing is a single threaded environment, that is, anything that blocks the thread (such as Thread.sleep, will prevent it from process new repaint requests and events, making it look like you program has stopped.
Swing is also not thread safe. This means that you are required to ensure that all updates and interactions with the UI are done from within the context of the Event Dispatching Thread.
Animation is typically achieved through the use of a javax.swing.Timer or SwingWorker depending on the complexity of the animation. You can use a Thread, but it complicates the issues as you will be required to ensure that all updates to the UI (directly or otherwise) are done from within the context of the EDT manually.
Take a look at:
Performing Custom Painting
Painting in AWT and Swing
Concurrency in Swing

Java Hangman GUI not properly displaying [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 years ago.
Improve this question
I'm having a couple of different problems with my code, most of them are GUI based problems but i do have one actionevent problem. I will post my code in sections first then I will point out what the issue is specifically with each section. *note all of my code will be in order of how is actually in my IDE.
If you wish to copy my code without all of the other stuff here it is on pastebin: http://pastebin.com/HHjRRtGZ
My imports:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*; //Makes it a Japplet
import java.util.Random;
My Program Starts here:
public class Hangman extends JApplet implements ActionListener {
// How many times you can guess the wrong letter till you lose
static final int DEAD = 7;
private int errors; // amount of errors
private String message; // Message displaying either Error or Victory
private String information; // Secondary Message
private String RealWord; // The Real word
private StringBuffer GuessWord;// The Guessed word
private Button StartBtn; // The Restart Button
private Button GoBtn; // The Go Button
private TextField LetterBox; // The LetterBox
My ActionEvent:
public void actionPerformed(ActionEvent e){
if (e.getSource() == StartBtn){
initGame();
}
if (e.getSource() == GoBtn){
processTurn();
LetterBox.setText("");
repaint();
}
}
Problem One:
The problem that I am having with my Action event is that when I hit the StartBtn it does not reinitialize everything. Nothing is cleared. Nothing is done. It doesn't do anything. So that's the issue there.
My init() function (This is where one of my problems lie.
public void init() {
// Create a "Textbox" for the letter guessing
LetterBox = new TextField();
//Create my buttons and labels
StartBtn = new Button("Restart");
GoBtn = new Button("Go");
//Add the elements to the applet
JPanel p = new JPanel();
p.setLayout(new FlowLayout());
p.add(StartBtn);
p.add(new Label("Guess a letter"));
p.add(LetterBox);
p.add(GoBtn);
add(p, BorderLayout.SOUTH);
//Make buttons event listeners
StartBtn.addActionListener(this);
GoBtn.addActionListener(this);
//Startup the Game
initGame();
}
Problem 2 (MAIN PROBLEM):
The problems I have with my Init code that affect my GUI is that the actual word guess area and the hangman area are kind of messed up. It's hard to explain, so i'll show you an image. The Backdrop for almost all of the form is completely transparent. So it just uses a still image of whatever it was on top of as it's back ground.
Problem 3:
There are some other issues with the image as you can see. The code is further down for those parts however. But as you can tell, the messages do not clear and just write over each other, even though I specify them to (which you will see further down).
Problem 4:
Now before you guess any letters, the word is hidden with a "" but when you guess a correct letter for the word it's supposed to replace the "" with the correct letter guessed. However it just put's it on top of it (you will see the code for this below as well).
This is the Game initializer
public void initGame() {
//Set the errors to 0
errors = 0;
//Enter the wordslist, separated by a | here
String str = "write|program|receive|positive|variables|temporary|good|bad|test";
String[] temp;
//delimiter
String delimiter = "\\|";
// given string will be split by the argument delimiter provided.
temp = str.split(delimiter);
//Create the Random Seed Generator
Random RandGenerator = new Random();
//Generate my Random Number
int randomInt = RandGenerator.nextInt(temp.length);
RealWord = new String(temp[randomInt]);
char positions[] = new char[RealWord.length()];
Right here is where it replaces the unguessed characters on screen with a "*".
for (int i = 0; i < RealWord.length(); i++) {
positions[i] = '*';
}
String s = new String(positions);
GuessWord = new StringBuffer(s);
LetterBox.setText("");
//Delete Messages
message = "";
information = "";
repaint();
}
My Painting function
#Override
public void paint(Graphics g) {
int BaseY = 250;
//THE HANGING STAND
if (errors > 0) { //1 Error
g.drawLine(90, BaseY, 200, BaseY); //The ground
g.drawLine(125, BaseY, 125, BaseY-100); //The bar going up
g.drawLine(125, BaseY-100, 175, BaseY-100); //The sidebar
g.drawLine(175, BaseY-100, 175, BaseY-75); //The Rope
}
//THE PERSON
if (errors > 1) {
g.drawOval(170, BaseY-75, 10, 12); // The Head
}
if (errors > 2) {
g.drawLine(175, BaseY-62, 175, BaseY-45); // The Body
}
if (errors > 3) {
g.drawLine(165, BaseY-65, 175, BaseY-55); // Left Arm
}
if (errors > 4) {
g.drawLine(185, BaseY-65, 175, BaseY-55); // Right Arm
}
if (errors > 5) {
g.drawLine(170, BaseY-30, 175, BaseY-45); //Left Leg
}
if (errors > 6) { //7 Errors
g.drawLine(175, BaseY-45, 180, BaseY-30); // Right Left
}
//Show Messages/Errors
g.drawString(message, 40, BaseY+25);
g.drawString(information, 25, BaseY+45);
g.drawString(new String (GuessWord), 140, BaseY-120);
g.drawString(new String("WELCOME TO HANGMAN!"), 75, 40);
}
This is where alot of the magic happens. This is the processTurn Function
private void processTurn() {
String s, t;
char a;
s = LetterBox.getText();
a = s.charAt(0);
if (!Character.isLetter(a)) {
message = "Only enter letters please.";
return;
}
if (s.length() > 1) {
message = "One letter at a time please.";
return;
}
//Check if letter has been used already
t = new String(GuessWord);
if (t.indexOf(s) != -1) {
message = "You have already guessed with that letter!";
return;
}
//If the letter you guessed does not occur in the Real Word
if (RealWord.indexOf(s) == -1) {
message = "";
errors++;
if (errors==DEAD) {
message = "Sorry, you lose";
information = "Click restart to try again!";
//INSERT MOVING HANGMAN HERE.
}
return;
}
This is where the "*" is supposed to be replaced with the correctl guessed letter but it doesn't work correctly!
//Replace stars in the Guessed Word with the found letter
for (int i = 0; i < RealWord.length(); i++) {
if (RealWord.charAt(i) == a) {
GuessWord.setCharAt(i, a);
}
}
t = new String(GuessWord);
//If all of the stars have been filled, then you win!
if (t.indexOf('*') == -1) {
message = "You have won!";
return;
}
//Delete the Message
message = "";
repaint();
}
Main Function
public static void main(String[] args) {
JFrame frame = new JFrame();
JApplet applet = new Hangman();
applet.init();
applet.start();
frame.add(applet);
frame.setSize(300, 400);
frame.setLocationRelativeTo(null); // Center the frame
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Any and all assistance will be greatly appreciated :) Thanks in advance!
This is caused by the basic fact that you have broken the paint chain.
Painting in AWT/Swing is made of a chain of method calls. If you neglect to maintain this chain, you start ending up with paint artifacts, in the form you are seeing.
The Graphics context is a shared resource, that is, every component painted during any given paint cycle will be given the same Graphics resource. Each component is expected to prepare the Graphics context before painting to it...
To fix the problem, in all your paint methods, you should be calling super.paint(g) to ensure that the paint chain is maintained and that the Graphics context is preapred for the individual components painting needs.
Having said that. It is not recommended to override paint of top level containers. Instead, it is recommended that you perform your custom painting using something like a JPanel and override it's paintComponent method instead (ensuring that you call super.paintComponent as well!)
This has a least two basic benefits. The first is, you can know decide where the panel should be displayed, for example, you could add it to an JApplet or JFrame or other container as you see fit. It is double buffered by default. This means you won't see any flicker when the painting is updated.
You should consider breaking your application down into small panels, focusing on each of the components needs separately. This will help reduce the complexity of your application and make it easier to manage the individual needs of each section of your application.
Take a look at Performing Custom Painting and Painting in AWT and Swing for more details
The problem is that you're painting directly on JApplet. You should not paint directly on top level container such as JFrame or JApplet. Instead, use JComponent or JPanel. Override paintComponent() for painting rather than paint() and don't forget to call super.paintComponent(g).
Take a look at Performing Custom Painting tutorial for more information.
Consider refactoring your code by moving all the current logic and painting into a new JPanel which will be used as applet content. Then, add this panel to the applet.
EDIT:
The source of the problem is not calling super.paint() in you painting implementation. Overriding paint() is not necessary and usually not recommended in many Swing applications. JComponent.paint() (a superclass for all Swing components) handles painting the content, borders, and children of a Swing component. And by neglecting a call to super.paint() you are disrupting the painting of all these details.
Take a looks at A Closer Look at the Paint Mechanism for more details about painting cycle.

Force call to paintComponent

I realise this code looks, pointless, I've just got rid of the irrelevent stuff to show the structure
class Drawer extends JComponent {
public Drawer(int[] data) {
System.out.println("drawer");
for(int x = 0; x < data.length; x++){}
//work out what to draw
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("drawerpC"); //check to see if it is called
//draw stuff
}
}
In a separate file, a new instance of Drawer is called regularly. Every time it is called, data is different, and so every time Drawer is called, paintComponent needs to be called.
I have this code in that other file:
Drawer d = new Drawer(data);
myGUI.con.add(d); //myGUI.con is a previously set up container
repaint() is not causing paintComponent to be called (otherwise you'd see the stdout), so how can I force paintComponent to be called for every calling to Drawer?
Store the int[] as a class attribute. Move //work out what to draw into paintComponent(Graphics).
Calling repaint will have no effect until the component is part of the display hierarchy. It can't possibly be part of a hierarchy while the constructor is still executing.
One option is to add a HierarchyListener to the root of your display hierarchy and do the repaint there. However, it's far from clear to me what you're trying to accomplish and this might not be the best approach.

Rendering from a List<> array

I am trying to render to a JPanel from a list array. I've created my own 2D renderer, but when I try and add to the list using the RenderAdd function, it either doesn't add, or the list array doesn't allow the list to be read...
He's the code which starts it.
JFrame frame = new JFrame();
frame.setSize(900, 500);
frame.setVisible(true);
Render render = new Render(new RenderDimension(frame.getX(),frame.getY(),frame.getWidth(),frame.getHeight()), frame);
BufferedImage zombie = new ImageLoader().readImage("zombie");
BufferedImage player = new ImageLoader().readImage("player");
render.RenderAdd(new RenderImage(new RenderDimension(100, 100, player.getWidth(), player.getHeight()), player));
render.RenderAdd(new RenderImage(new RenderDimension(0, 0, zombie.getWidth(), zombie.getHeight()), zombie));
render.start();
render.RenderAdd(new RenderImage(new RenderDimension(200, 100, player.getWidth(), player.getHeight()), player));
'render' is the main Render class in the rendering part. Then, RenderAdd adds a RenderImage which has RenderDimension which is the x and y of the object, and the image width and height. Then also takes a BufferedImage as a parameter.
Although, every time I try running the program, it comes with a blank screen
Now, in the Render class, there is another class which extends a thread. This is the class which takes the frame as a parameter, deletes the contents and starts painting to the getContentPane(). This next code is inside the paintComponent() function in the renderthread class.
Unfortunately, nothing paints, but is does process because I've tried with System.out.print("h") which repeatedly prints itself.
for (RenderImage r : render.getList()){
int x = r.getSize().getX();
int y = r.getSize().getY();
int wi = r.getSize().getWidth();
int hi = r.getSize().getHeight();
if (x + wi >= -1 && x + wi <= d.getWidth()){
if (y + hi >= -1 && y + hi <= d.getHeight()){
g.drawImage(r.getImage(), x, y, null);
}
}
}
frame.getContentPane().add(p);
frame.getContentPane().validate();
frame.getContentPane().repaint();
I think the problem is that the list won't add, so here's that part.
List<RenderImage> render = new ArrayList<RenderImage>();
public List<RenderImage> getList(){
return render;
}
public void RenderAdd(RenderImage renders){
render.add(renders);
}
It's difficult to know with the example code you've given us.
Scenario #1, overriding JComponent#paintComponent
If you are doing this inside your paintComponent method
for (RenderImage r : render.getList()){
int x = r.getSize().getX();
int y = r.getSize().getY();
int wi = r.getSize().getWidth();
int hi = r.getSize().getHeight();
if (x + wi >= -1 && x + wi <= d.getWidth()){
if (y + hi >= -1 && y + hi <= d.getHeight()){
g.drawImage(r.getImage(), x, y, null);
}
}
}
frame.getContentPane().add(p);
frame.getContentPane().validate();
frame.getContentPane().repaint();
Then DON`T.
Calling any method that updates the UI in any way from within a paint method will only result in disaster. This is simply triggering another repaint request to be added to the Event Dispatching Thread, which will call you paintComponent method and you can say good by to your CPU and program responsiveness.
Also, make sure, when updating the UI from a different Thread other then the EDT, make sure you sync the request back to the EDT using SwingUtilities#invokeLater or SwingUtilities.invokeAndWait
Also, make sure you are calling super.paintComponent
Scenario #2, using JComponent#getGraphics
The question that comes to mind is, where does g come from in your example.
If you're using JComponent#getGraphics, then don't. This is simple a snapshot of the graphics between repaints, as soon as the next repaint occurs, it will be erased.
Create a custom component from something like JPanel and override it's paintComponent method and update the component with your RenderImage loop (just leave out the code that changes the UI)
Also, make sure that all repaint requests are made from within the context of EDT

Categories

Resources