legacy project initial commit
This commit is contained in:
65
src/gui/CustomIcon.java
Normal file
65
src/gui/CustomIcon.java
Normal file
@ -0,0 +1,65 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
public class CustomIcon implements Icon {
|
||||
private BufferedImage image;
|
||||
private int width, heigth;
|
||||
|
||||
public CustomIcon(Image image, int width, int height) {
|
||||
this.image = toBufferedImage(image);
|
||||
this.width = width;
|
||||
this.heigth = height;
|
||||
}
|
||||
|
||||
private final Color color = new Color(1f, 0.82f, 0.45f, 0.4f);
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
Graphics2D g2d = (Graphics2D) g.create();
|
||||
int imageWidth = image.getWidth(),
|
||||
imageHeigth = image.getHeight();
|
||||
// setting image position to center
|
||||
int resultX = x + (width-imageWidth)/2,
|
||||
resultY = y + (heigth-imageHeigth)/2;
|
||||
//g2d.drawRect(x, y, width, heigth);
|
||||
g2d.setColor(color);
|
||||
g2d.fillOval(x, y, width, heigth);
|
||||
g2d.drawImage(image, resultX, resultY, null);
|
||||
}
|
||||
|
||||
private BufferedImage toBufferedImage(Image img)
|
||||
{
|
||||
if (img instanceof BufferedImage)
|
||||
{
|
||||
return (BufferedImage) img;
|
||||
}
|
||||
|
||||
// Create a buffered image with transparency
|
||||
BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
// Draw the image on to the buffered image
|
||||
Graphics2D bGr = bimage.createGraphics();
|
||||
bGr.drawImage(img, 0, 0, null);
|
||||
bGr.dispose();
|
||||
|
||||
return bimage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return heigth;
|
||||
}
|
||||
|
||||
}
|
||||
118
src/gui/DrawboxEditor.java
Normal file
118
src/gui/DrawboxEditor.java
Normal file
@ -0,0 +1,118 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import model.Drawbox;
|
||||
import model.Point;
|
||||
|
||||
public class DrawboxEditor extends Editable {
|
||||
|
||||
private List<Point> drawboxPoints;
|
||||
private List<Point> basePoints;
|
||||
Logger logger = Logger.getLogger("gui.DrawboxRectangleEditor");
|
||||
|
||||
Point currentPoint = new Point(0, 0);
|
||||
|
||||
DrawboxEditor(ListGUI listGUI) {
|
||||
super(listGUI);
|
||||
|
||||
logger.setLevel(Level.CONFIG);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void drawing(Graphics2D g) {
|
||||
Drawbox drawbox = entity.getDrawbox();
|
||||
drawboxPoints = drawbox.getDrawboxlistPoints();
|
||||
basePoints = drawbox.getbaseListPoints();
|
||||
logger.finest("drawbox point size: "+ drawboxPoints.size());
|
||||
for(Point p: drawboxPoints)
|
||||
logger.finest("DrawBoxPoint: ["+ p.x + ":"+p.y+"]");
|
||||
for(Point p: basePoints)
|
||||
logger.finest("BasePoint: ["+ p.x + ":"+p.y+"]");
|
||||
|
||||
if(drawboxPoints.size() >= 1 && drawboxPoints.size() < 4) {
|
||||
Point lastPoint = drawboxPoints.get(drawboxPoints.size()-1);
|
||||
g.drawLine((int)lastPoint.x, (int)lastPoint.y, (int)currentPoint.x, (int)currentPoint.y);
|
||||
for(int i = 0; i < drawboxPoints.size()-1;i++) {
|
||||
int x1 = (int)drawboxPoints.get(i).x;
|
||||
int y1 = (int)drawboxPoints.get(i).y;
|
||||
int x2 = (int)drawboxPoints.get(i+1).x;
|
||||
int y2 = (int)drawboxPoints.get(i+1).y;
|
||||
g.drawLine(x1, y1, x2, y2);
|
||||
}
|
||||
} else {
|
||||
for(int i = 0; i < drawboxPoints.size();i++) {
|
||||
int x1 = (int)drawboxPoints.get(i % drawboxPoints.size()).x;
|
||||
int y1 = (int)drawboxPoints.get(i % drawboxPoints.size()).y;
|
||||
int x2 = (int)drawboxPoints.get((i+1) % drawboxPoints.size()).x;
|
||||
int y2 = (int)drawboxPoints.get((i+1) % drawboxPoints.size()).y;
|
||||
g.drawLine(x1, y1, x2, y2);
|
||||
}
|
||||
// ОТРИСОВКА ОСНОВАНИЯ
|
||||
g.setColor(Color.BLUE);
|
||||
for(int i = 0; i < basePoints.size()-1;i++) {
|
||||
int x1 = (int)basePoints.get(i).x;
|
||||
int y1 = (int)basePoints.get(i).y;
|
||||
int x2 = (int)basePoints.get(i+1).x;
|
||||
int y2 = (int)basePoints.get(i+1).y;
|
||||
g.drawLine(x1, y1+3, x2, y2+3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveDataInEntity() { }
|
||||
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if(drawboxPoints.size() < 4) {
|
||||
Point p = new Point(e.getX(), e.getY());
|
||||
drawboxPoints.add(p);
|
||||
if(drawboxPoints.size() == 4) {
|
||||
Point baseStart = drawboxPoints.get(0),
|
||||
baseEnd = drawboxPoints.get(1);
|
||||
for(Point pseudo: drawboxPoints) { // если не будет цикла в цикле - не выйдет сравнить всех со всеми
|
||||
for(Point vertex: drawboxPoints) { // что приведёт к артефактам построения основания
|
||||
if(vertex != baseStart && vertex != baseEnd) {
|
||||
if(vertex.y >= baseStart.y) baseStart = vertex;
|
||||
else if(vertex.y >= baseEnd.y) baseEnd = vertex;
|
||||
}
|
||||
}
|
||||
}
|
||||
basePoints.add(baseStart);
|
||||
basePoints.add(baseEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
currentPoint.x = e.getX();
|
||||
currentPoint.y = e.getY();
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JTabbedPane parent = (JTabbedPane) getParent();
|
||||
if(parent.getSelectedComponent() == this){
|
||||
if(entity != null) {
|
||||
entity.getDrawbox().getDrawboxlistPoints().clear();
|
||||
entity.getDrawbox().getbaseListPoints().clear();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
111
src/gui/Editable.java
Normal file
111
src/gui/Editable.java
Normal file
@ -0,0 +1,111 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import model.Entity;
|
||||
import repository.Project;
|
||||
|
||||
public abstract class Editable extends JPanel implements MouseListener, MouseMotionListener, ListSelectionListener, ActionListener {
|
||||
|
||||
protected ListGUI listGUI;
|
||||
protected Entity entity;
|
||||
protected String name;
|
||||
protected BufferedImage image;
|
||||
JPanel drawPanel;
|
||||
|
||||
private static Logger logger = Logger.getLogger("gui.Editable");
|
||||
|
||||
|
||||
Editable(ListGUI listGUI){
|
||||
this.listGUI = listGUI;
|
||||
|
||||
addMouseListener(this);
|
||||
addMouseMotionListener(this);
|
||||
|
||||
// РАСКОММЕНТИТЬ ЕСЛИ НУЖНО ВЫВОДИТЬ ПОДРОБНЫЕ ЛОГИ
|
||||
logger.setLevel(Level.ALL);
|
||||
}
|
||||
|
||||
|
||||
//Абстрактные методы
|
||||
|
||||
public abstract void drawing(Graphics2D g);
|
||||
public abstract void saveDataInEntity();
|
||||
|
||||
|
||||
|
||||
//методы родительского класса Editable
|
||||
|
||||
//get,set для name
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
//заполнить текущую сущность по имени.
|
||||
public void setEntityByName(String name) throws Exception {
|
||||
entity = Project.getInstance().getEntityByName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
g.drawImage(image, 0, 0, this);
|
||||
if(entity != null) {
|
||||
drawing((Graphics2D)g);
|
||||
}
|
||||
}
|
||||
|
||||
// из mouse motion listener'a
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {}
|
||||
|
||||
// этих ребят нас обязывает создать MouseListener, так что они здесь
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {}
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {}
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {}
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {}
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {}
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
logger.log(Level.FINEST, "entering method {0} of class {1}", new Object[]{"MouseClicked()" , this.getClass().getName()});
|
||||
//logger.entering(this.getClass().getName(), "MouseClicked()");
|
||||
if(e.getSource() instanceof JList) {
|
||||
//TODO: надо бы элегантнее пробросить сюда ListGUI - просто передача его в аргументах немножко громоздкая
|
||||
// как-то обыграть это через события?
|
||||
// ps. попытки обратиться к eventSource проваливаются - ListGUI это панель, уже внутри которой лежит JList
|
||||
name = listGUI.getSelectedName();
|
||||
try {
|
||||
entity = Project.getInstance().getEntityByName(name);
|
||||
} catch (Exception e1) {
|
||||
logger.severe("Entity with name '"+name+"' was not found! Cannot display it on panel!");
|
||||
}
|
||||
image = Project.getInstance().loadImageByName(name);
|
||||
//TODO: if(image == null) вызов FileChooser'a и выбор изображения
|
||||
|
||||
this.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/gui/HitboxCircleEditor.java
Normal file
47
src/gui/HitboxCircleEditor.java
Normal file
@ -0,0 +1,47 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
public class HitboxCircleEditor extends Editable {
|
||||
|
||||
HitboxCircleEditor(ListGUI listGUI) {
|
||||
super(listGUI);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void drawing(Graphics2D g) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveDataInEntity() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
48
src/gui/HitboxPoligonEditor.java
Normal file
48
src/gui/HitboxPoligonEditor.java
Normal file
@ -0,0 +1,48 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
public class HitboxPoligonEditor extends Editable {
|
||||
|
||||
HitboxPoligonEditor(ListGUI listGUI) {
|
||||
super(listGUI);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void drawing(Graphics2D g) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveDataInEntity() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
53
src/gui/HitboxRectangleEditor.java
Normal file
53
src/gui/HitboxRectangleEditor.java
Normal file
@ -0,0 +1,53 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
public class HitboxRectangleEditor extends Editable {
|
||||
|
||||
HitboxRectangleEditor(ListGUI listGUI) {
|
||||
super(listGUI);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawing(Graphics2D g) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveDataInEntity() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
219
src/gui/ListGUI.java
Normal file
219
src/gui/ListGUI.java
Normal file
@ -0,0 +1,219 @@
|
||||
package gui;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import listeners.CreateFrameAddElementListener;
|
||||
import listeners.RemoveListElementEntityListener;
|
||||
import model.Drawbox;
|
||||
import model.Entity;
|
||||
import model.Hitbox;
|
||||
import model.Point;
|
||||
import repository.Project;
|
||||
|
||||
public class ListGUI extends JPanel {
|
||||
private Map<String, Icon> iconMap = new HashMap<>();
|
||||
JButton addListElementEntity;
|
||||
JButton removeListElementEntity;
|
||||
JButton addPicEntity;
|
||||
String pathImage;
|
||||
JList list;
|
||||
JScrollPane scroll;
|
||||
ActionListener removeEntity;
|
||||
ActionListener addEntity;
|
||||
DefaultListModel<String> testModel;
|
||||
|
||||
private static Logger logger = Logger.getLogger("gui.ListGUI");
|
||||
|
||||
public ListGUI() {
|
||||
try {
|
||||
Project.getInstance().load();
|
||||
} catch (SAXException | IOException | ParserConfigurationException e) {
|
||||
// TODO: сделать нормальную обработку исключений - ситуации когда файл не существует, случилась ошибка парсинга и тд должны обрабатываться отдельно
|
||||
// в идеале должны появляться разные окошки, которые обрисуют в чём проблема и предложения что делать
|
||||
JOptionPane.showMessageDialog(this, "Parser exception, cause: "+e);
|
||||
}
|
||||
String[] nameList = createNameList();
|
||||
|
||||
testModel = new DefaultListModel<>();
|
||||
|
||||
list = new JList(testModel);
|
||||
list.setCellRenderer(new ListEntityRenderer());
|
||||
|
||||
addEntity = new CreateFrameAddElementListener(this);
|
||||
removeEntity = new RemoveListElementEntityListener(this);
|
||||
|
||||
scroll = new JScrollPane(list);
|
||||
scroll.setSize(new Dimension(223, 638));
|
||||
scroll.setLocation(5, 5);
|
||||
this.add(scroll);
|
||||
|
||||
list.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
addListElementEntity = createButton(5,645, addEntity,"res/addbutton.png");
|
||||
removeListElementEntity = createButton(117,645,removeEntity,"res/deletebutton.png");
|
||||
|
||||
|
||||
/// ЧТОБЫ ОТКЛЮЧИТЬ ИЗБЫТОЧНЫЙ ВЫВОД В КОНСОЛЬ - НУЖНО СМЕНИТЬ УРОВЕНЬ ЛОГГИРОВАНИЯ В СТРОКЕ НИЖЕ
|
||||
logger.setLevel(Level.FINEST);
|
||||
// в идеале достаточно уровня WARNING, но если нужна более точна информация о модуле - можно снизить уровень
|
||||
|
||||
updateList();
|
||||
}
|
||||
|
||||
public String getSelectedName() {
|
||||
return (String) list.getSelectedValue();
|
||||
}
|
||||
|
||||
public void addListElement(String name,String solid) {
|
||||
// плейсхолдеры для новых хитбоксов и дроубоксов, иначе всё валится с NPE
|
||||
List<Point> hitboxPoints = new LinkedList<Point>();
|
||||
List<Point> drawboxPoints = new LinkedList<Point>();
|
||||
Hitbox hitbox = new Hitbox("Rectangle", hitboxPoints);
|
||||
Drawbox drawbox = new Drawbox(drawboxPoints);
|
||||
|
||||
// а тут уже создание новой сущности
|
||||
Entity e = new Entity(name, hitbox, drawbox);
|
||||
e.setType(solid);
|
||||
logger.finer("Entity \""+name+"\" was created.");
|
||||
Project.getInstance().addEntity(e);
|
||||
updateList();
|
||||
}
|
||||
|
||||
public void updateList() {
|
||||
String[] nameList = createNameList();
|
||||
testModel.removeAllElements();
|
||||
for (String name : nameList)
|
||||
testModel.addElement(name);
|
||||
|
||||
pathImage = Project.getInstance().getXMLPath();
|
||||
createImageMap(nameList);
|
||||
list.updateUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция, формирующая список имён сущностей для JList, чтобы сформировать его итемы
|
||||
* */
|
||||
private String[] createNameList() {
|
||||
//TODO: meh, хочется избавиться от использования списка - нехорошее это дело, лучше работать через интерфейс Project
|
||||
int index = 0,
|
||||
size = Project.getInstance().getListEntity().size();
|
||||
String[] names = new String[size];
|
||||
for(Entity e: Project.getInstance())
|
||||
names[index++] = e.getName();
|
||||
//TODO: не будет ли лучше хранить в JList не только имена, но и ссылки на сущности?
|
||||
//было бы удобно по нажатию на итем JList'а сразу дёргать из него нужную сущность а
|
||||
//из неё её хитбоксы и дроубоксы вместо вызовов по типу "Project.getInstance().getEntityByName(jListItem.getText())"
|
||||
//НО! текущий вариант не требует написания кастомного JList, без которого в него целую сущность не запихать...
|
||||
|
||||
return names;
|
||||
}
|
||||
private final float iconMaxWidth = 40, iconMaxHeight = 40;
|
||||
|
||||
public Icon imageScaling(BufferedImage image) {
|
||||
CustomIcon icon = null;
|
||||
try {
|
||||
int imageWidth = image.getWidth(), imageHeight = image.getHeight();
|
||||
float scaleFactorX = iconMaxWidth / imageWidth, scaleFactorY = iconMaxHeight / imageHeight,
|
||||
scaleFactor = scaleFactorX < scaleFactorY ? scaleFactorX : scaleFactorY;
|
||||
// debug print
|
||||
// System.out.printf("ImageWidth: %d, ImageHeight: %d, scaleFactorX: %f,
|
||||
// scaleFactorY: %f, scaleFactor: %f \n", imageWidth, imageHeight, scaleFactorX,
|
||||
// scaleFactorY, scaleFactor);
|
||||
Image scaledImage = image.getScaledInstance((int) (imageWidth * scaleFactor),
|
||||
(int) (imageHeight * scaleFactor), Image.SCALE_SMOOTH);
|
||||
icon = new CustomIcon(scaledImage, (int) iconMaxWidth, (int) iconMaxHeight);
|
||||
} catch (NullPointerException npe) {
|
||||
logger.log(Level.WARNING, "Cannot scale icon for list item - the reference is null");
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
public class ListEntityRenderer extends DefaultListCellRenderer
|
||||
{
|
||||
Font font = new Font("helvitica", Font.BOLD, 14);
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
|
||||
{
|
||||
int iconTextOffset = 10;
|
||||
|
||||
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
Entity e = null;
|
||||
try {
|
||||
e = Project.getInstance().getEntityByName((String)value);
|
||||
String text = label.getText();
|
||||
text = "<html>"+text+"<br>"+"<small>"+e.getType()+"</small>";
|
||||
label.setText(text);
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
Icon icon = iconMap.get((String) value);
|
||||
if(icon != null) {
|
||||
label.setIcon(icon);
|
||||
label.setIconTextGap((int)iconMaxWidth - icon.getIconWidth() + iconTextOffset);
|
||||
}
|
||||
label.setFont(font);
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
private void createImageMap(String[] nameList) {
|
||||
for (int i = 0; i < nameList.length; i++) {
|
||||
String name = nameList[i];
|
||||
BufferedImage image = Project.getInstance().loadImageByName(name);
|
||||
iconMap.put(name, imageScaling(image));
|
||||
}
|
||||
}
|
||||
|
||||
private JButton createButton(int width,int height,ActionListener listener,String pathImage) {
|
||||
JButton button = new JButton(new ImageIcon(pathImage));
|
||||
button.setSize(110, 46);
|
||||
button.setLocation(width, height);
|
||||
button.addActionListener(listener);
|
||||
button.setContentAreaFilled(false);
|
||||
button.setFocusPainted(false);
|
||||
this.add(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Немного костыль, который позволяет зарегестрировать слушатель мыши на списке компонентов
|
||||
* не создавая излишне тесных связей между компонентами
|
||||
* */
|
||||
public void registerJListListener(ListSelectionListener ml) {
|
||||
list.addListSelectionListener(ml);
|
||||
}
|
||||
|
||||
public void printNameList(String[] nameList) {
|
||||
System.out.println("вызван принт лист: "+nameList.length);
|
||||
for(int i = 0;i<nameList.length;i++) {
|
||||
System.out.println(nameList[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
115
src/gui/MainGUI.java
Normal file
115
src/gui/MainGUI.java
Normal file
@ -0,0 +1,115 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import listeners.OpenXMLFileButtonListener;
|
||||
|
||||
public class MainGUI extends JFrame{
|
||||
ListGUI list;
|
||||
ActionListener OpenXMLFileButtonListener;
|
||||
JButton openXMLJButton;
|
||||
JButton saveXMLJButton;
|
||||
JButton clearLinesJButton;
|
||||
public static JTabbedPane editorPane;
|
||||
public static JTabbedPane hitdrawPane;
|
||||
List <Editable> listEditorPanel = new ArrayList<Editable>();
|
||||
DrawboxEditor drawBoxPanel;
|
||||
HitboxCircleEditor hitboxCirclePanel;
|
||||
HitboxRectangleEditor hitboxRectanglePanel;
|
||||
HitboxPoligonEditor hitboxPoligonPanel;
|
||||
|
||||
|
||||
|
||||
public MainGUI() {
|
||||
|
||||
setTitle("Hitbox/Drawbox Editor");
|
||||
setLayout(null);
|
||||
setSize(1200,780);
|
||||
setLocationRelativeTo(null);
|
||||
setResizable(false);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
|
||||
list = new ListGUI();
|
||||
this.add(list);
|
||||
list.setLayout(null);
|
||||
list.setSize(230, 691);
|
||||
list.setLocation(0, 47);
|
||||
list.setVisible(true);
|
||||
|
||||
drawBoxPanel = new DrawboxEditor(list);
|
||||
hitboxCirclePanel = new HitboxCircleEditor(list);
|
||||
hitboxRectanglePanel = new HitboxRectangleEditor(list);
|
||||
hitboxPoligonPanel = new HitboxPoligonEditor(list);
|
||||
listEditorPanel.add(drawBoxPanel);
|
||||
listEditorPanel.add(hitboxCirclePanel);
|
||||
listEditorPanel.add(hitboxRectanglePanel);
|
||||
listEditorPanel.add(hitboxPoligonPanel);
|
||||
|
||||
list.registerJListListener(drawBoxPanel);
|
||||
list.registerJListListener(hitboxCirclePanel);
|
||||
list.registerJListListener(hitboxPoligonPanel);
|
||||
list.registerJListListener(hitboxRectanglePanel);
|
||||
|
||||
OpenXMLFileButtonListener = new OpenXMLFileButtonListener(list);
|
||||
|
||||
openXMLJButton = createButton("XML",5,5, OpenXMLFileButtonListener,"res/xml.png");
|
||||
saveXMLJButton = createButton("Save",80,5,null,"res/download.png");
|
||||
clearLinesJButton = createButton("Clear lines",155,5,null,"res/destroy.png");
|
||||
|
||||
clearLinesJButton.addActionListener(drawBoxPanel);
|
||||
clearLinesJButton.addActionListener(hitboxCirclePanel);
|
||||
clearLinesJButton.addActionListener(hitboxPoligonPanel);
|
||||
clearLinesJButton.addActionListener(hitboxRectanglePanel);
|
||||
|
||||
repaint();
|
||||
|
||||
this.add(editorPane = createPane(editorPane, 230, 50));
|
||||
|
||||
editorPane.addTab("Hitbox", hitdrawPane = createPane(hitdrawPane,10, 10));
|
||||
editorPane.addTab("Drawbox", drawBoxPanel);
|
||||
hitdrawPane.addTab(null, new ImageIcon("res/square.png"),hitboxRectanglePanel, null);
|
||||
hitdrawPane.addTab(null, new ImageIcon("res/circle.png"),hitboxCirclePanel, null);
|
||||
hitdrawPane.addTab(null, new ImageIcon("res/formless.png"),hitboxPoligonPanel, null);
|
||||
hitdrawPane.setTabPlacement(JTabbedPane.LEFT);
|
||||
|
||||
}
|
||||
@Override
|
||||
public void paint(Graphics g){
|
||||
super.paint(g);
|
||||
g.drawLine(0, 72, 1200, 72);
|
||||
}
|
||||
|
||||
|
||||
public JTabbedPane createPane(JTabbedPane pane, int x, int y) {
|
||||
pane = new JTabbedPane();
|
||||
pane.setSize(new Dimension(950, 688));
|
||||
pane.setLocation(new Point(x, y));
|
||||
pane.setVisible(true);
|
||||
return pane;
|
||||
}
|
||||
|
||||
private JButton createButton(String text,int width,int height,ActionListener listener,String pathImage) {
|
||||
JButton button = new JButton(new ImageIcon(pathImage));
|
||||
button.setSize(68, 34);
|
||||
button.setLocation(width, height);
|
||||
button.addActionListener(listener);
|
||||
button.setContentAreaFilled(false);
|
||||
button.setFocusPainted(false);
|
||||
this.add(button);
|
||||
return button;
|
||||
}
|
||||
}
|
||||
90
src/gui/PropertyFrameAddElement.java
Normal file
90
src/gui/PropertyFrameAddElement.java
Normal file
@ -0,0 +1,90 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import listeners.AddListElementEntityListener;
|
||||
import listeners.CopyImageToFile;
|
||||
|
||||
public class PropertyFrameAddElement extends JFrame {
|
||||
|
||||
JPanel contents;
|
||||
JButton acceptSettingsJButton;
|
||||
JButton addPuthImage;
|
||||
JTextField nameTextField,typeTextField;
|
||||
ActionListener acceptSettingListener;
|
||||
ActionListener copyImageToFile;
|
||||
JLabel nameLabel,typeLabel,imageLabel;
|
||||
public PropertyFrameAddElement(ListGUI listGUI) {
|
||||
|
||||
this.setTitle("Add Entity");
|
||||
this.setLayout(null);
|
||||
this.setSize(400,250);
|
||||
this.setLocationRelativeTo(null);
|
||||
this.setResizable(false);
|
||||
this.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
||||
|
||||
nameLabel = new JLabel("Введите имя сущности:");
|
||||
nameLabel.setSize(150,20);
|
||||
nameLabel.setLocation(20, 20);
|
||||
typeLabel = new JLabel("Введите тип сущности:");
|
||||
typeLabel.setSize(150,20);
|
||||
typeLabel.setLocation(20, 50);
|
||||
imageLabel = new JLabel("Для выбора нажмите:");
|
||||
imageLabel.setSize(150,20);
|
||||
imageLabel.setLocation(20, 108);
|
||||
|
||||
nameTextField = new JTextField();
|
||||
nameTextField.setSize(200,20);
|
||||
nameTextField.setLocation(160, 20);
|
||||
typeTextField = new JTextField();
|
||||
typeTextField.setSize(200,20);
|
||||
typeTextField.setLocation(160, 50);
|
||||
|
||||
|
||||
contents = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
contents.setLayout(null);
|
||||
contents.setSize(600,400);
|
||||
contents.add(nameLabel);
|
||||
contents.add(typeLabel);
|
||||
contents.add(imageLabel);
|
||||
contents.add(nameTextField);
|
||||
contents.add(typeTextField);
|
||||
acceptSettingListener = new AddListElementEntityListener(listGUI,this);
|
||||
copyImageToFile = new CopyImageToFile(this);
|
||||
acceptSettingsJButton = createButton("Accept", 148, 170, acceptSettingListener, null);
|
||||
addPuthImage = createButton("Добавить image", 160, 100, copyImageToFile, null);
|
||||
addPuthImage.setSize(160, 40);
|
||||
contents.add(acceptSettingsJButton);
|
||||
contents.add(addPuthImage);
|
||||
setContentPane(contents);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private JButton createButton(String text,int width,int height,ActionListener listener,String pathImage) {
|
||||
JButton button = new JButton(text);
|
||||
button.setSize(90, 40);
|
||||
button.setLocation(width, height);
|
||||
button.addActionListener(listener);
|
||||
button.setContentAreaFilled(false);
|
||||
button.setFocusPainted(false);
|
||||
this.add(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
public String getInputName() {
|
||||
String name = nameTextField.getText();
|
||||
return name;
|
||||
}
|
||||
public String getInputType() {
|
||||
String type = typeTextField.getText();
|
||||
return type;
|
||||
}
|
||||
}
|
||||
37
src/launch/Launcher.java
Normal file
37
src/launch/Launcher.java
Normal file
@ -0,0 +1,37 @@
|
||||
package launch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogManager;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import gui.MainGUI;
|
||||
|
||||
public class Launcher {
|
||||
|
||||
public static void main(String[] args) throws SAXException, IOException, ParserConfigurationException {
|
||||
// Настройка системы логгирования
|
||||
// Указываем, в каком формате выводится строка в консоль
|
||||
System.setProperty("java.util.logging.SimpleFormatter.format",
|
||||
"[%1$tF %1$tT] [%4$-7s] %5$s %n");
|
||||
|
||||
Logger rootLogger = LogManager.getLogManager().getLogger("");
|
||||
//указываем, какой уровень логгирования учитывается при выводе - всё что меньше заданного уровня откидывается
|
||||
//p.s. уровень вывода и уровень регистрации (handler.level | logging.level) это не одно и то же
|
||||
Handler[] handlers = rootLogger.getHandlers();
|
||||
Level lv = Level.FINE;
|
||||
for(int i = 0; i < handlers.length; i++) {
|
||||
handlers[i].setLevel(lv);
|
||||
}
|
||||
rootLogger.info("Root Handler Logging level is "+lv.getName());
|
||||
|
||||
MainGUI gui = new MainGUI();
|
||||
gui.setVisible(true);
|
||||
}
|
||||
|
||||
}
|
||||
30
src/listeners/AddListElementEntityListener.java
Normal file
30
src/listeners/AddListElementEntityListener.java
Normal file
@ -0,0 +1,30 @@
|
||||
package listeners;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.event.ListDataEvent;
|
||||
import javax.swing.event.ListDataListener;
|
||||
|
||||
import gui.ListGUI;
|
||||
import gui.PropertyFrameAddElement;
|
||||
|
||||
public class AddListElementEntityListener implements ActionListener
|
||||
{
|
||||
ListGUI listGUI;
|
||||
PropertyFrameAddElement propertyFrame;
|
||||
public AddListElementEntityListener(ListGUI listGUI,PropertyFrameAddElement propetry) {
|
||||
this.listGUI = listGUI;
|
||||
propertyFrame = propetry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
listGUI.addListElement(propertyFrame.getInputName(),propertyFrame.getInputType());
|
||||
propertyFrame.dispose();
|
||||
}
|
||||
}
|
||||
59
src/listeners/CopyImageToFile.java
Normal file
59
src/listeners/CopyImageToFile.java
Normal file
@ -0,0 +1,59 @@
|
||||
package listeners;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
|
||||
import gui.PropertyFrameAddElement;
|
||||
import repository.Project;
|
||||
|
||||
public class CopyImageToFile extends JFileChooser implements ActionListener {
|
||||
File source;
|
||||
File dest;
|
||||
PropertyFrameAddElement propertyFrame;
|
||||
public String directory = "res/";
|
||||
public String file = "objecttypes.xml";
|
||||
|
||||
public CopyImageToFile(PropertyFrameAddElement propFrame){
|
||||
propertyFrame = propFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
||||
dest = new File(Project.getInstance().getXMLPath()+propertyFrame.getInputName()+".png");
|
||||
this.setDialogTitle("Choose file");
|
||||
this.setFileSelectionMode(JFileChooser.FILES_ONLY);
|
||||
FileNameExtensionFilter filter = new FileNameExtensionFilter("Image PNG", "png");
|
||||
this.setFileFilter(filter);
|
||||
int result = this.showOpenDialog(CopyImageToFile.this);
|
||||
System.out.println("propertyFrame.getInputName() = "+propertyFrame.getInputName());
|
||||
if (result == JFileChooser.APPROVE_OPTION && propertyFrame.getInputName()!=null)
|
||||
{
|
||||
System.out.println("Выбрали!!!!!!!!!!!!");
|
||||
directory = this.getCurrentDirectory().toString()+'/';
|
||||
file = this.getName(getSelectedFile()).toString();
|
||||
source = new File(directory+file);
|
||||
try {
|
||||
copyFileUsingJava7Files(source,dest);
|
||||
System.out.println("ФАЙЛ ВРОДЕ КАК ДОЛЖЕН БЫЛ СОЗДАТЬСЯ, ИДИ ПРОВЕРЬ");
|
||||
} catch (IOException e1) {
|
||||
JOptionPane.showMessageDialog(null, "Выбранное изображение уже существует!");
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// простой и удобный метод копирования файла в Java 7
|
||||
private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
|
||||
Files.copy(source.toPath(), dest.toPath());
|
||||
}
|
||||
}
|
||||
24
src/listeners/CreateFrameAddElementListener.java
Normal file
24
src/listeners/CreateFrameAddElementListener.java
Normal file
@ -0,0 +1,24 @@
|
||||
package listeners;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import gui.ListGUI;
|
||||
import gui.PropertyFrameAddElement;
|
||||
|
||||
public class CreateFrameAddElementListener implements ActionListener {
|
||||
|
||||
PropertyFrameAddElement frameProperty;
|
||||
ListGUI listGUI;
|
||||
|
||||
public CreateFrameAddElementListener(ListGUI listGUI) {
|
||||
this.listGUI = listGUI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
frameProperty = new PropertyFrameAddElement(listGUI);
|
||||
frameProperty.setVisible(true);
|
||||
}
|
||||
|
||||
}
|
||||
44
src/listeners/OpenXMLFileButtonListener.java
Normal file
44
src/listeners/OpenXMLFileButtonListener.java
Normal file
@ -0,0 +1,44 @@
|
||||
package listeners;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
|
||||
import gui.ListGUI;
|
||||
import repository.Project;
|
||||
|
||||
public class OpenXMLFileButtonListener extends JFileChooser implements ActionListener
|
||||
{
|
||||
//public String directory = "C:\\Users\\sivan\\Desktop\\Диплом\\GUI-Collision&Drawing-Metadata-Editor\\res\\";
|
||||
public String directory = "res/";
|
||||
public String file = "objecttypes.xml";
|
||||
|
||||
ListGUI listGUI;
|
||||
|
||||
public OpenXMLFileButtonListener(ListGUI listGUI) {
|
||||
this.listGUI = listGUI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
this.setDialogTitle("Choose file");
|
||||
this.setFileSelectionMode(JFileChooser.FILES_ONLY);
|
||||
FileNameExtensionFilter filter = new FileNameExtensionFilter("Xml File", "xml");
|
||||
this.setFileFilter(filter);
|
||||
int result = this.showOpenDialog(OpenXMLFileButtonListener.this);
|
||||
if (result == JFileChooser.APPROVE_OPTION )
|
||||
{
|
||||
directory = this.getCurrentDirectory().toString()+'/';
|
||||
file = this.getName(getSelectedFile()).toString();
|
||||
Project.getInstance().load(directory, file);
|
||||
listGUI.updateList();
|
||||
|
||||
//Debug
|
||||
System.out.println(directory + file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
32
src/listeners/RemoveListElementEntityListener.java
Normal file
32
src/listeners/RemoveListElementEntityListener.java
Normal file
@ -0,0 +1,32 @@
|
||||
package listeners;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
|
||||
import gui.ListGUI;
|
||||
import model.Entity;
|
||||
import repository.Project;
|
||||
|
||||
public class RemoveListElementEntityListener implements ActionListener{
|
||||
|
||||
ListGUI listGUI;
|
||||
|
||||
public RemoveListElementEntityListener(ListGUI listGUI) {
|
||||
this.listGUI = listGUI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
String name = listGUI.getSelectedName();
|
||||
if(name == null) return;
|
||||
// System.out.println(name);
|
||||
try {
|
||||
Entity e = Project.getInstance().getEntityByName(name);
|
||||
Project.getInstance().removeEntity(e);
|
||||
listGUI.updateList();
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
71
src/model/Drawbox.java
Normal file
71
src/model/Drawbox.java
Normal file
@ -0,0 +1,71 @@
|
||||
package model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Drawbox {
|
||||
private List<Point> drawboxlistPoints ;//для прямоугольника дравбокса.
|
||||
private List<Point> baseListPoints ;//2 точки для линии основания
|
||||
|
||||
public Drawbox(List<Point> drawboxlistPoints){
|
||||
this.drawboxlistPoints = new ArrayList<Point>(drawboxlistPoints) ;
|
||||
createBasePoint();
|
||||
}
|
||||
|
||||
public Drawbox(String informationDrawbox){
|
||||
|
||||
if(informationDrawbox!= null) {
|
||||
drawboxlistPoints = new ArrayList<Point>();
|
||||
baseListPoints = new ArrayList<Point>();
|
||||
String[] informations = informationDrawbox.split(" ");
|
||||
parseStringToDrawbox(informations);
|
||||
createBasePoint();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseStringToDrawbox(String[] informations) {
|
||||
//составляем точки по которым строится прямоугольник, и запихиваем их в лист с точками
|
||||
for(int i = 0;i<informations.length;i+=2) {
|
||||
float x = Float.parseFloat(informations[i]);
|
||||
float y = Float.parseFloat(informations[i+1]);
|
||||
Point point = new Point(x, y);
|
||||
drawboxlistPoints.add(point);
|
||||
}
|
||||
}
|
||||
private void createBasePoint() {
|
||||
//составляем точки по которым строится основание, и запихиваем их в лист с точками
|
||||
if(baseListPoints==null)baseListPoints = new ArrayList<Point>();
|
||||
if(drawboxlistPoints.size()>2) {
|
||||
for(int i = 0;i<2;i++) {
|
||||
float x = drawboxlistPoints.get(drawboxlistPoints.size()-i-1).x;
|
||||
float y = drawboxlistPoints.get(drawboxlistPoints.size()-i-1).y+3;
|
||||
Point point = new Point(x, y);
|
||||
baseListPoints.add(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Point> getDrawboxlistPoints() {
|
||||
return drawboxlistPoints;
|
||||
}
|
||||
|
||||
public List<Point> getbaseListPoints() {
|
||||
return baseListPoints;
|
||||
}
|
||||
public void Print() {
|
||||
System.out.println("|||Drawbox:");
|
||||
if(baseListPoints!=null&&drawboxlistPoints!=null) {
|
||||
System.out.println("drawboxlistPoints:");
|
||||
for(Point point: drawboxlistPoints) {
|
||||
System.out.print("("+point.x+";"+point.y+") ");
|
||||
}
|
||||
System.out.println();
|
||||
System.out.println("baseListPoints:");
|
||||
for(Point point: baseListPoints) {
|
||||
System.out.print("("+point.x+";"+point.y+") ");
|
||||
}
|
||||
}else {
|
||||
System.out.println("null");
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/model/Entity.java
Normal file
77
src/model/Entity.java
Normal file
@ -0,0 +1,77 @@
|
||||
package model;
|
||||
|
||||
public class Entity {
|
||||
private String thisName;
|
||||
private Drawbox thisDrawbox;
|
||||
private Hitbox thisHitbox;
|
||||
private Formatter formaterHitbox;
|
||||
private Formatter formaterDrawbox;
|
||||
private String type;
|
||||
|
||||
public Entity(String name,String drawbox,String hitbox) {
|
||||
thisName = new String(name);
|
||||
thisDrawbox = new Drawbox(drawbox);
|
||||
thisHitbox = new Hitbox(hitbox);
|
||||
}
|
||||
|
||||
public Entity(String name, Hitbox hitbox, Drawbox drawbox) {
|
||||
this.thisName = name;
|
||||
this.thisHitbox = hitbox;
|
||||
this.thisDrawbox = drawbox;
|
||||
}
|
||||
|
||||
public void setName(String outName) {
|
||||
thisName = outName;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setDrawbox(Drawbox outDrawbox) {
|
||||
thisDrawbox = outDrawbox;
|
||||
};
|
||||
|
||||
public void setHitbox(Hitbox outHitbox) {
|
||||
thisHitbox = outHitbox;
|
||||
};
|
||||
|
||||
public void setFormaterHitbox(Formatter outFormaterHitbox) {
|
||||
formaterHitbox = outFormaterHitbox;
|
||||
};
|
||||
|
||||
public void setFormaterDrawbox(Formatter outFormaterDrawbox) {
|
||||
formaterDrawbox = outFormaterDrawbox;
|
||||
};
|
||||
|
||||
public String getName() {
|
||||
return thisName;
|
||||
};
|
||||
|
||||
public Drawbox getDrawbox() {
|
||||
return thisDrawbox;
|
||||
};
|
||||
|
||||
public Hitbox getHitbox() {
|
||||
return thisHitbox;
|
||||
};
|
||||
|
||||
public Formatter getFormaterHitbox() {
|
||||
return formaterHitbox;
|
||||
};
|
||||
|
||||
public Formatter getFormaterDrawbox() {
|
||||
return formaterDrawbox;
|
||||
};
|
||||
public void PrintEntity() {
|
||||
System.out.println("---------------------");
|
||||
System.out.println("Name: "+thisName);
|
||||
this.thisDrawbox.Print();
|
||||
this.thisHitbox.Print();
|
||||
System.out.println("---------------------");
|
||||
}
|
||||
}
|
||||
5
src/model/Formatter.java
Normal file
5
src/model/Formatter.java
Normal file
@ -0,0 +1,5 @@
|
||||
package model;
|
||||
|
||||
public interface Formatter {
|
||||
|
||||
}
|
||||
5
src/model/FormatterCircle.java
Normal file
5
src/model/FormatterCircle.java
Normal file
@ -0,0 +1,5 @@
|
||||
package model;
|
||||
|
||||
public class FormatterCircle implements Formatter {
|
||||
|
||||
}
|
||||
5
src/model/FormatterRectangle.java
Normal file
5
src/model/FormatterRectangle.java
Normal file
@ -0,0 +1,5 @@
|
||||
package model;
|
||||
|
||||
public class FormatterRectangle implements Formatter {
|
||||
|
||||
}
|
||||
83
src/model/Hitbox.java
Normal file
83
src/model/Hitbox.java
Normal file
@ -0,0 +1,83 @@
|
||||
package model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Hitbox {
|
||||
private String shape = null;// shape - Форма хитбокса. Понадобится при написании сохранения. Один из параметров в xml-file.
|
||||
private List<Point> listPoints = null;//для прямоугольника - 4 точки. Для круга - надо посмотреть.
|
||||
|
||||
public Hitbox(String shape,List<Point> listPoints){
|
||||
this.shape = new String(shape);
|
||||
this.listPoints = new ArrayList<Point>(listPoints) ;
|
||||
}
|
||||
|
||||
public Hitbox(String informationHitbox){
|
||||
if(informationHitbox!= null) {
|
||||
listPoints = new ArrayList<Point>();
|
||||
String[] informations = informationHitbox.split(" ");
|
||||
//в 0-м индексе всегда идет название фигуры.Так сделан наш xml.
|
||||
shape = new String(informations[0]);
|
||||
//заполняем лист точками. Пока что делаю тупо и топорно. Хардкод. Потом можно переделать.
|
||||
if(shape.equals("Rectangle")) {
|
||||
parseStringToRectangleHitbox(informations);
|
||||
}else if(shape.equals("Circle")) {
|
||||
parseStringToCircleHitbox(informations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseStringToRectangleHitbox(String[] informations) {
|
||||
//составляем точки по которым строится прямоугольник, и запихиваем их в лист с точками
|
||||
for(int i = 1;i<informations.length;i+=2) {
|
||||
float x = Float.parseFloat(informations[i]);
|
||||
float y = Float.parseFloat(informations[i+1]);
|
||||
Point point = new Point(x, y);
|
||||
listPoints.add(point);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseStringToCircleHitbox(String[] informations) {
|
||||
//ЭТО ХАРДКОД.ДА.Но в данном случае, имхо, разумный(в каком то смысле).
|
||||
//первая точка где строится круг
|
||||
float x1 = Float.parseFloat(informations[1]);
|
||||
float y1 = Float.parseFloat(informations[2]);
|
||||
Point point1 = new Point(x1, y1);
|
||||
//вторая точка x2 - диаметр, а y2 = 0, потому что нам на него пофиг.
|
||||
//Просто же не создавать для круга новый класс хитбокса? Нет. Так что так.
|
||||
float x2 = Float.parseFloat(informations[3]);
|
||||
Point point2 = new Point(x2, 0);
|
||||
listPoints.add(point2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void addPoint(Point point) {
|
||||
listPoints.add(point);
|
||||
}
|
||||
|
||||
public void clearPoints() {
|
||||
listPoints.clear();
|
||||
}
|
||||
public String getShape() {
|
||||
return shape;
|
||||
}
|
||||
public List<Point> getListPoints() {
|
||||
return listPoints;
|
||||
}
|
||||
public void Print() {
|
||||
System.out.println();
|
||||
System.out.println("|||Hitbox:");
|
||||
if(shape!=null&&listPoints!=null) {
|
||||
System.out.println("shape: " + shape);
|
||||
for(Point point: listPoints) {
|
||||
System.out.print("("+point.x+";"+point.y+") ");
|
||||
}
|
||||
System.out.println();
|
||||
}else {
|
||||
System.out.println("null");
|
||||
}
|
||||
}
|
||||
//дописать функцию возвращения listPonts и shape(форма), если будет нужно.
|
||||
// так же при написании функции возвращения нужных координат, надо их сделать целочисленными.
|
||||
}
|
||||
11
src/model/Point.java
Normal file
11
src/model/Point.java
Normal file
@ -0,0 +1,11 @@
|
||||
package model;
|
||||
|
||||
public class Point {
|
||||
public float x;
|
||||
public float y;
|
||||
|
||||
public Point(float mainX,float mainY){
|
||||
x = mainX;
|
||||
y = mainY;
|
||||
}
|
||||
}
|
||||
BIN
src/repository/ListEntity.class
Normal file
BIN
src/repository/ListEntity.class
Normal file
Binary file not shown.
328
src/repository/Project.java
Normal file
328
src/repository/Project.java
Normal file
@ -0,0 +1,328 @@
|
||||
package repository;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import model.Entity;
|
||||
|
||||
|
||||
/**
|
||||
* Класс данных, который оперирует их сохранением, загрузкой, и хранением в памяти.
|
||||
* Он нужен для отделения Модели от Представления (см. <a href="https://ru.wikipedia.org/wiki/Model-View-Controller">MVC</a>), чтобы их можно было менять независимо друг от друга.
|
||||
* Например, при добавлении новой кнопки данные можно будет получить через существующий интерфейс класса не меняя код.
|
||||
*
|
||||
* ВАЖНО: данный класс хранит так же актуальную копию XML-представления файла
|
||||
* */
|
||||
public class Project implements Iterable<Entity> {
|
||||
|
||||
/**
|
||||
* Путь к XML по-умолчанию.
|
||||
* Просто заглушка, обычно заменяется актуальным путём в ходе изменения программы. <br>
|
||||
* Cм. {@link #setXMLPath(String)} и {@link #getXMLPath()}
|
||||
* */
|
||||
public static final String DEFAULT_XML_PATH = "res/";
|
||||
public static final String DEFAULT_XML_FILENAME = "objecttypes.xml";
|
||||
|
||||
static Project thisProject;
|
||||
private List <Entity> listEntity = new ArrayList<Entity>();
|
||||
private String path = DEFAULT_XML_PATH;
|
||||
private String fileName = DEFAULT_XML_FILENAME;
|
||||
// Получение фабрики, чтобы после получить билдер документов.
|
||||
private DocumentBuilderFactory factory;
|
||||
// Получили из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
|
||||
private DocumentBuilder builder;
|
||||
// Запарсили XML, создав структуру Document. Теперь у нас есть доступ ко всем элементам, каким нам нужно.
|
||||
private Document document;
|
||||
|
||||
private static Logger logger = Logger.getLogger("repository.Project");
|
||||
|
||||
//!!!РЕАЛИЗАЦИЯ СИНГЛИТОНА!НАЧАЛО!!!
|
||||
private Project(){};
|
||||
|
||||
public static Project getInstance() {
|
||||
if(thisProject == null) {
|
||||
thisProject = new Project();
|
||||
}
|
||||
return thisProject;
|
||||
};
|
||||
//!!!РЕАЛИЗАЦИЯ СИНГЛИТОНА!ОКОНЧАНИЕ!!!
|
||||
|
||||
/**
|
||||
* Устанавливает значение пути к XML. Все операции загрузки и сохранения будут работать с этой директорией.<br>
|
||||
* По-умолчанию - {@link #DEFAULT_XML_PATH}
|
||||
* @param newPath - абсолютный или относительный адрес папки в виде строки.
|
||||
* <br><i>например: "C:/User/map/" или "/home/username/map/" или "res/map" (относительный путь рассчитывается от корня проекта)</i>
|
||||
* */
|
||||
public void setXMLPath(String newPath) {
|
||||
path = newPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает актуальный путь к директории, в которой лежит XML-файл с типами объектов, а так же папки с ресурсами. <br>
|
||||
* ps. считается, что ресурсы находятся в той же папке что XML-файл
|
||||
* */
|
||||
public String getXMLPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает имя файла, из которого загружаются сущности. Ещё нужен путь (см. {@link #getXMLPath()})
|
||||
* */
|
||||
public void setXMLFileName(String name) {
|
||||
this.fileName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает имя файла, из которого загружаются ресурсы. Нужен для контроля доступа к нему, а так же избавления от жёстких зависимостей.<br>
|
||||
* <i>Например, если fileName в какой-то момент со String сменится на URL достаточно будет адаптировать геттер а не переписывать
|
||||
* все места где происходят к нему обращения.</i>
|
||||
* */
|
||||
public String getXMLFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Перегрузка {@link #load()}.<br>
|
||||
* Использует {@link #setXMLFileName(String)} и {@link #setXMLPath(String)} для того, чтобы сохранить новый путь к XML-файлу и его имя.
|
||||
* Повторно вызывать их вручную не обязательно.
|
||||
*
|
||||
* @param directory - папка где хранится XML-файл и ресурсы (см. {@link #getXMLPath()})
|
||||
* @param name - имя XML-файла с определениями типов сущностей
|
||||
* */
|
||||
public void load(String directory, String name) {
|
||||
setXMLPath(directory);
|
||||
setXMLFileName(name);
|
||||
try {
|
||||
load();
|
||||
} catch (SAXException | IOException | ParserConfigurationException e) {
|
||||
System.err.println("Failed to load project! Cause: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Загружает типы сущностей из файла с заданным именем и расположением.<br>
|
||||
* */
|
||||
public void load() throws SAXException, IOException, ParserConfigurationException {
|
||||
listEntity.clear();
|
||||
factory = DocumentBuilderFactory.newInstance();
|
||||
builder = factory.newDocumentBuilder();
|
||||
document = builder.parse(new File(path+fileName));
|
||||
// Получение списка всех элементов objecttype внутри корневого элемента (getDocumentElement возвращает ROOT элемент XML файла).
|
||||
NodeList objecttypeElements = document.getDocumentElement().getElementsByTagName("objecttype");
|
||||
for(int i = 0; i < objecttypeElements.getLength(); i++) {
|
||||
//System.out.println("---------------------");
|
||||
Node objecttype = objecttypeElements.item(i);
|
||||
NamedNodeMap attributesObject = objecttype.getAttributes();
|
||||
String entityName = new String(attributesObject.getNamedItem("name").getNodeValue());
|
||||
//System.out.println("Name: "+entityName);
|
||||
parsingElementXMLtoElementList(entityName,objecttype);
|
||||
//System.out.println("---------------------");
|
||||
}
|
||||
}
|
||||
|
||||
// эта колбаса парсит сущности из XML в программные объекты внутри модели, является частью внутренней кухни так что обычно её не нужно трогать
|
||||
private void parsingElementXMLtoElementList(String entityName,Node objecttype) {
|
||||
String newDrawbox = null;
|
||||
String newHitbox = null;
|
||||
String type = null;
|
||||
Element element = (Element)objecttype;
|
||||
NodeList propertyElements = element.getElementsByTagName("property");
|
||||
if(propertyElements!=null) {
|
||||
for(int i = 0; i < propertyElements.getLength(); i++) {
|
||||
Element property = (Element)propertyElements.item(i);
|
||||
String propertyName = property.getAttribute("name");
|
||||
String defaultProperty = property.getAttribute("default");
|
||||
switch(propertyName) {
|
||||
case "drawbox":
|
||||
newDrawbox = defaultProperty;
|
||||
break;
|
||||
case "hitbox":
|
||||
newHitbox = defaultProperty;
|
||||
break;
|
||||
case "class":
|
||||
type = defaultProperty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Entity e = new Entity(entityName,newDrawbox,newHitbox);
|
||||
e.setType(type);
|
||||
listEntity.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Загружает с диска изображение с заданным именем. Подразумевается, что изображение находится {@link #path там же} где
|
||||
* XML-файл. Возвращает null если изображение не найдено.
|
||||
* */
|
||||
public BufferedImage loadImageByName(String name) {
|
||||
//TODO: сделать кеширование - не дело подгружать одну и ту же картинку по десять раз!
|
||||
|
||||
String path = Project.getInstance().getXMLPath();
|
||||
String extension = "png";
|
||||
// TODO: изображения следует подгружать в отдельном потоке!
|
||||
|
||||
try {
|
||||
File imageFile = new File(path + name + '.' + extension);
|
||||
if (!imageFile.exists())
|
||||
throw new FileNotFoundException();
|
||||
BufferedImage image = ImageIO.read(imageFile);
|
||||
return image;
|
||||
} catch (FileNotFoundException fe) {
|
||||
logger.warning("Image file \""+path+name+'.'+extension+"\" is not found!");
|
||||
} catch (IOException e) {
|
||||
logger.warning("Cannot read file \""+path+name+'.'+extension+"\"!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/*
|
||||
* Добавляется обьект как в список всех сущностей, так и в xml-метаданные дерева(element objecttype)
|
||||
* Это лишь добавление в метаданные, а не парсинг в файл! не путать!
|
||||
* Для парсинга будет сделана отдельная функция!
|
||||
*/
|
||||
public void addEntity(Entity e) {
|
||||
listEntity.add(e);
|
||||
Element objecttype = document.createElement("objecttype");
|
||||
objecttype.setAttribute("name", e.getName());
|
||||
objecttype.setAttribute("color", "000000");//color of entity, needed by Tiled editor
|
||||
document.getElementsByTagName("objecttypes").item(0).appendChild(objecttype);
|
||||
/*
|
||||
* TODO: set "class" property too (code below)
|
||||
Element property = document.createElement("property");
|
||||
objecttype.appendChild(property);
|
||||
document.appendChild(objecttype);
|
||||
property.setAttribute("name", "class");
|
||||
property.setAttribute("type", "string");
|
||||
property.setAttribute("default", e.getType());
|
||||
|
||||
* creating hitbox or drowbox attributes in advance is insufficient
|
||||
* they will be added while saving
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* Удаляется обьект из списка всех сущностей, и из xml-дерева
|
||||
*/
|
||||
public void removeEntity(Entity e) {
|
||||
listEntity.remove(e);
|
||||
NodeList nl = document.getElementsByTagName("objecttype");
|
||||
for(int i = 0; i < nl.getLength(); i++) {
|
||||
Node objecttype = nl.item(i);
|
||||
if(objecttype.getAttributes().getNamedItem("name").getNodeValue().equals(e.getName()))
|
||||
document.getElementsByTagName("objecttypes").item(0).removeChild(objecttype);
|
||||
}
|
||||
}
|
||||
|
||||
public Entity getEntity(int id) {
|
||||
return listEntity.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает объект сущности с заданным именем, или вбрасывает исключение, если такой сущности не существует.
|
||||
* */
|
||||
public Entity getEntityByName(String name) throws Exception {
|
||||
//debug print
|
||||
//System.out.println("----- session started ------");
|
||||
for(Entity e: listEntity) {
|
||||
//System.out.println("we need "+ name +" we got "+e.getName());
|
||||
if(e.getName().equals(name))
|
||||
return e;
|
||||
}
|
||||
|
||||
// можно было бы и просто null возвращать, но обращение за несуществующей сущностью само по себе нехороший прецедент
|
||||
// так что исключение призвано обратить внимание пользователя, если такое действительно случится
|
||||
throw new Exception("No entity with such name!");
|
||||
}
|
||||
|
||||
public void save() {
|
||||
/*
|
||||
* thanks to addEntity() and removeEntity() our Document and listEntity
|
||||
* are interchangeable - they content exactly the same set of objects.
|
||||
* So we'll iterate over Document instead listEntity. It's more convient
|
||||
* in this case.
|
||||
* */
|
||||
NodeList objecttypes = document.getElementsByTagName("objecttype");
|
||||
for(int i = 0; i < objecttypes.getLength(); i++) {
|
||||
Node objecttype = objecttypes.item(i);
|
||||
NodeList propertiesList = objecttype.getChildNodes();
|
||||
for(int j = 0; j < objecttypes.getLength(); j++) {
|
||||
Element property = (Element) propertiesList.item(j);
|
||||
NamedNodeMap attributes = property.getAttributes();
|
||||
String name = attributes.getNamedItem("name").getNodeValue();
|
||||
Entity entity = null;
|
||||
try {
|
||||
entity = getEntityByName(name);
|
||||
|
||||
} catch (Exception e) {
|
||||
//if there is no entity with such name we'll skip further parsing
|
||||
System.err.println("Entity "+name+" is not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parsing complete, writing to file now
|
||||
writeXML();
|
||||
}
|
||||
|
||||
private void writeXML() {
|
||||
try {
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
DOMSource source = new DOMSource(document);
|
||||
StreamResult result = new StreamResult(new FileOutputStream(path));
|
||||
transformer.transform(source, result);
|
||||
} catch (TransformerException | FileNotFoundException e) {
|
||||
System.err.println("Saving project is unsuccsessfull! Erorr is: "+e);
|
||||
}
|
||||
}
|
||||
|
||||
public void PrintEntitys() {
|
||||
// TODO Auto-generated method stub
|
||||
for(Entity ent:listEntity) {
|
||||
ent.PrintEntity();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entity> iterator() {
|
||||
return listEntity.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Лучше бы пользоваться этой функцией поменьше - почти всю работу вполне можно сделать через интерфейс Project<br>
|
||||
* Например, если нужно перебрать все сущности, стоит использовать цикл foreach с использованием инстанса Project, например:<br>
|
||||
* {@code for(Entity e: Project.getInstance()){ *тут клиентский код* }}
|
||||
* */
|
||||
@Deprecated
|
||||
public List<Entity> getListEntity() {
|
||||
return listEntity;
|
||||
}
|
||||
}
|
||||
//в момент окончания рисования, в зависимости в какой мы рисуем вкладке хитбокса,
|
||||
//в зависимости от того в какой панельке(jpanel)и подклассе интерфейса Editable
|
||||
//мы дорисовали фигуру, такой форматер и создается.
|
||||
//хотя не стоит забывать про сейв(не знаю зачем, но стоит подумать, когда сяду снова делать)
|
||||
Reference in New Issue
Block a user