Updates and fixed to preset handling
This commit is contained in:
parent
6f465cd0a2
commit
70e7936454
@ -1,4 +1,4 @@
|
||||
Manufacturer,PartNo,id(cm),od(cm),maxlength(cm)
|
||||
Manufacturer,PartNo,InnerDiameter,OuterDiameter,Length
|
||||
Semroc,BT-3,0.8865,0.9525,45.72
|
||||
Semroc,BT-3H,0.8865,0.9525,7.62
|
||||
Semroc,BT-3XW,0.8865,0.9525,3.81
|
||||
|
|
@ -1587,3 +1587,7 @@ CustomFinImport.error.title = Error loading fin profile
|
||||
CustomFinImport.error.badimage = Could not deduce fin shape from image.
|
||||
CustomFinImport.description = The image will be converted internally to black and white image (black for the fin), so make sure you use a solid dark color for the fin, and white or a light color for the background. The fin must be touching the bottom of the image, which is the base of the fin.
|
||||
|
||||
|
||||
PresetModel.lbl.select = Select preset:
|
||||
PresetModel.lbl.database = From database...
|
||||
|
||||
|
@ -8,7 +8,6 @@ import java.util.List;
|
||||
|
||||
import net.sf.openrocket.preset.ComponentPreset;
|
||||
import net.sf.openrocket.preset.TypedKey;
|
||||
import net.sf.openrocket.rocketcomponent.BodyTube;
|
||||
import au.com.bytecode.opencsv.CSVReader;
|
||||
|
||||
public class PresetCSVReader {
|
||||
@ -16,7 +15,7 @@ public class PresetCSVReader {
|
||||
private InputStream is;
|
||||
private ColumnDefinition[] columns;
|
||||
|
||||
public PresetCSVReader( InputStream is ) {
|
||||
public PresetCSVReader(InputStream is) {
|
||||
this.is = is;
|
||||
}
|
||||
|
||||
@ -27,7 +26,7 @@ public class PresetCSVReader {
|
||||
InputStreamReader r = new InputStreamReader(is);
|
||||
|
||||
// Create the CSV reader. Use comma separator and double-quote escaping.
|
||||
CSVReader reader = new CSVReader(r,',','"');
|
||||
CSVReader reader = new CSVReader(r, ',', '"');
|
||||
|
||||
String[] headers = reader.readNext();
|
||||
if (headers == null || headers.length == 0) {
|
||||
@ -35,28 +34,28 @@ public class PresetCSVReader {
|
||||
}
|
||||
|
||||
columns = new ColumnDefinition[headers.length];
|
||||
for( int i = 0; i< headers.length; i++ ) {
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
String h = headers[i];
|
||||
if( "Manufacturer".equals(h) ) {
|
||||
if ("Manufacturer".equals(h)) {
|
||||
columns[i] = new ColumnDefinition.Manufactuer();
|
||||
} else if ( "PartNumber".equals(h) ) {
|
||||
} else if ("PartNo".equals(h)) {
|
||||
columns[i] = new ColumnDefinition.PartNumber();
|
||||
} else if ( "Type".equals(h) ) {
|
||||
} else if ("Type".equals(h)) {
|
||||
columns[i] = new ColumnDefinition.Type();
|
||||
} else {
|
||||
TypedKey key = ComponentPreset.keyMap.get(h);
|
||||
if ( key == null ) {
|
||||
if (key == null) {
|
||||
throw new RuntimeException("Invalid parameter key " + h + " in file");
|
||||
}
|
||||
columns[i] = new ColumnDefinition.Parameter( key );
|
||||
columns[i] = new ColumnDefinition.Parameter(key);
|
||||
}
|
||||
}
|
||||
|
||||
String[] line;
|
||||
while( (line = reader.readNext()) != null ) {
|
||||
while ((line = reader.readNext()) != null) {
|
||||
ComponentPreset preset = new ComponentPreset();
|
||||
for( int i = 0; i< headers.length; i++ ) {
|
||||
if ( i > line.length ) {
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
if (i > line.length) {
|
||||
break;
|
||||
}
|
||||
String value = line[i];
|
||||
|
@ -1,97 +0,0 @@
|
||||
package net.sf.openrocket.gui.adaptors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractListModel;
|
||||
import javax.swing.ComboBoxModel;
|
||||
|
||||
import net.sf.openrocket.preset.ComponentPreset;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
|
||||
public class BodyTubePresetModel extends AbstractListModel implements ComboBoxModel {
|
||||
|
||||
private final RocketComponent component;
|
||||
|
||||
private List<ComponentPreset> presets;
|
||||
|
||||
public BodyTubePresetModel(RocketComponent component) {
|
||||
presets = Application.getDaos().getBodyTubePresetDao().listAll();
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
public static class BodyTubePresetAdapter {
|
||||
// If the ComponentPreset bt is null, then no preset is selected.
|
||||
private ComponentPreset bt;
|
||||
private BodyTubePresetAdapter( ComponentPreset bt ) {
|
||||
this.bt = bt;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
if ( bt != null ) {
|
||||
return bt.getManufacturer() + " " + bt.getPartNo();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((bt == null) ? 0 : bt.hashCode());
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// I don't know why the default equals generated by Eclipse does not work.
|
||||
// instead of relying on bt.equals(other.bt), we have to compare the hashcodes of those objects.
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
BodyTubePresetAdapter other = (BodyTubePresetAdapter) obj;
|
||||
if (bt == null) {
|
||||
if (other.bt != null)
|
||||
return false;
|
||||
} else if (other.bt == null)
|
||||
return false;
|
||||
return bt.hashCode() == other.bt.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return presets.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getElementAt(int index) {
|
||||
if ( index < 0 ) {
|
||||
return null;
|
||||
}
|
||||
return new BodyTubePresetAdapter(presets.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectedItem(Object anItem) {
|
||||
BodyTubePresetAdapter selected = (BodyTubePresetAdapter) anItem;
|
||||
if ( selected == null ) {
|
||||
component.loadPreset(null);
|
||||
} else {
|
||||
component.loadPreset(selected.bt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSelectedItem() {
|
||||
ComponentPreset preset = (ComponentPreset) component.getPresetComponent();
|
||||
if ( preset == null ) {
|
||||
return null;
|
||||
} else {
|
||||
return new BodyTubePresetAdapter(preset);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
93
core/src/net/sf/openrocket/gui/adaptors/PresetModel.java
Normal file
93
core/src/net/sf/openrocket/gui/adaptors/PresetModel.java
Normal file
@ -0,0 +1,93 @@
|
||||
package net.sf.openrocket.gui.adaptors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractListModel;
|
||||
import javax.swing.ComboBoxModel;
|
||||
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.logging.LogHelper;
|
||||
import net.sf.openrocket.preset.ComponentPreset;
|
||||
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
|
||||
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
|
||||
public class PresetModel extends AbstractListModel implements ComboBoxModel, ComponentChangeListener {
|
||||
|
||||
private static final LogHelper log = Application.getLogger();
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
private static final String SELECT_PRESET = trans.get("lbl.select");
|
||||
private static final String SELECT_DATABASE = trans.get("lbl.database");
|
||||
|
||||
|
||||
private final RocketComponent component;
|
||||
private ComponentPreset previousPreset;
|
||||
|
||||
private final List<ComponentPreset> presets;
|
||||
|
||||
public PresetModel(RocketComponent component) {
|
||||
// FIXME: Make generic for any component type
|
||||
// FIXME: This should load only the user's favorites, NOT all presets
|
||||
presets = Application.getDaos().getBodyTubePresetDao().listAll();
|
||||
this.component = component;
|
||||
previousPreset = component.getPresetComponent();
|
||||
component.addComponentChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return presets.size() + 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getElementAt(int index) {
|
||||
if (index == 0) {
|
||||
return SELECT_PRESET;
|
||||
}
|
||||
if (index == getSize() - 1) {
|
||||
return SELECT_DATABASE;
|
||||
}
|
||||
return presets.get(index - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectedItem(Object item) {
|
||||
log.user("User selected preset item '" + item + "' for component " + component);
|
||||
System.err.println("**** Setting item: " + item);
|
||||
|
||||
if (item == null) {
|
||||
// FIXME: What to do?
|
||||
} else if (item.equals(SELECT_PRESET)) {
|
||||
component.clearPreset();
|
||||
} else if (item.equals(SELECT_DATABASE)) {
|
||||
// FIXME: Open database dialog
|
||||
} else {
|
||||
// FIXME: Add undo point here
|
||||
component.loadPreset((ComponentPreset) item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSelectedItem() {
|
||||
ComponentPreset preset = component.getPresetComponent();
|
||||
if (preset == null) {
|
||||
return SELECT_PRESET;
|
||||
} else {
|
||||
return preset;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentChanged(ComponentChangeEvent e) {
|
||||
if (previousPreset != component.getPresetComponent()) {
|
||||
previousPreset = component.getPresetComponent();
|
||||
System.err.println("Firing event");
|
||||
fireContentsChanged(this, 0, getSize());
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Make model invalidatable
|
||||
|
||||
}
|
@ -1,39 +1,29 @@
|
||||
package net.sf.openrocket.gui.configdialog;
|
||||
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.document.OpenRocketDocument;
|
||||
import net.sf.openrocket.gui.SpinnerEditor;
|
||||
import net.sf.openrocket.gui.adaptors.BodyTubePresetModel;
|
||||
import net.sf.openrocket.gui.adaptors.BooleanModel;
|
||||
import net.sf.openrocket.gui.adaptors.DoubleModel;
|
||||
import net.sf.openrocket.gui.adaptors.PresetModel;
|
||||
import net.sf.openrocket.gui.components.BasicSlider;
|
||||
import net.sf.openrocket.gui.components.UnitSelector;
|
||||
import net.sf.openrocket.gui.dialogs.preset.ComponentPresetChooserDialog;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.material.Material;
|
||||
import net.sf.openrocket.preset.ComponentPreset;
|
||||
import net.sf.openrocket.rocketcomponent.BodyTube;
|
||||
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
|
||||
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.unit.UnitGroup;
|
||||
|
||||
public class BodyTubeConfig extends RocketComponentConfig {
|
||||
|
||||
private ComponentChangeListener listener;
|
||||
private MotorConfig motorConfigPane = null;
|
||||
private DoubleModel maxLength;
|
||||
private JComboBox presetComboBox;
|
||||
@ -44,24 +34,16 @@ public class BodyTubeConfig extends RocketComponentConfig {
|
||||
|
||||
JPanel panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::][]", ""));
|
||||
|
||||
//// Body tube template
|
||||
panel.add( new JLabel(trans.get("BodyTubecfg.lbl.Bodytubepreset")) );
|
||||
presetComboBox = new JComboBox(new BodyTubePresetModel(component));
|
||||
panel.add(presetComboBox);
|
||||
{
|
||||
JButton opendialog = new JButton("o");
|
||||
opendialog.addActionListener(
|
||||
new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ComponentPresetChooserDialog dialog = new ComponentPresetChooserDialog(SwingUtilities.getWindowAncestor(BodyTubeConfig.this));
|
||||
dialog.setVisible(true);
|
||||
ComponentPreset preset = dialog.getSelectedComponentPreset();
|
||||
}
|
||||
});
|
||||
panel.add( opendialog, "wrap" );
|
||||
}
|
||||
|
||||
//// Body tube template
|
||||
// FIXME: Move to proper location
|
||||
panel.add(new JLabel());
|
||||
presetComboBox = new JComboBox(new PresetModel(component));
|
||||
presetComboBox.setEditable(false);
|
||||
panel.add(presetComboBox, "wrap para");
|
||||
|
||||
|
||||
|
||||
//// Body tube length
|
||||
panel.add(new JLabel(trans.get("BodyTubecfg.lbl.Bodytubelength")));
|
||||
@ -142,18 +124,6 @@ public class BodyTubeConfig extends RocketComponentConfig {
|
||||
trans.get("BodyTubecfg.tab.Motormountconf"), 1);
|
||||
tabbedPane.setSelectedIndex(0);
|
||||
|
||||
// need to work in the max length for body tubes based on presets...
|
||||
adjustPresetState();
|
||||
|
||||
listener = new ComponentChangeListener() {
|
||||
|
||||
@Override
|
||||
public void componentChanged(ComponentChangeEvent e) {
|
||||
adjustPresetState();
|
||||
}
|
||||
|
||||
};
|
||||
component.addChangeListener(listener);
|
||||
|
||||
}
|
||||
|
||||
@ -164,24 +134,6 @@ public class BodyTubeConfig extends RocketComponentConfig {
|
||||
motorConfigPane.updateFields();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateModels() {
|
||||
super.invalidateModels();
|
||||
component.removeChangeListener(listener);
|
||||
}
|
||||
|
||||
private void adjustPresetState() {
|
||||
BodyTube bt = (BodyTube) component;
|
||||
if ( bt.getPresetComponent() != null ) {
|
||||
ComponentPreset btPreset = bt.getPresetComponent();
|
||||
maxLength.setValue( btPreset.get(ComponentPreset.LENGTH) );
|
||||
} else {
|
||||
// here we should be able to force the preset combo box to display empty.
|
||||
// We set the selected index to -1 (undefined), then force a repaint.
|
||||
presetComboBox.setSelectedIndex(-1);
|
||||
presetComboBox.repaint();
|
||||
maxLength.setValue(2.0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import java.util.Map;
|
||||
|
||||
import net.sf.openrocket.material.Material;
|
||||
import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
|
||||
import net.sf.openrocket.util.BugException;
|
||||
|
||||
|
||||
/**
|
||||
@ -15,26 +16,29 @@ import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
|
||||
*
|
||||
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
|
||||
*/
|
||||
public class ComponentPreset extends TypedPropertyMap {
|
||||
public class ComponentPreset {
|
||||
|
||||
private final Map<TypedKey<?>, Object> properties = new HashMap<TypedKey<?>, Object>();
|
||||
|
||||
|
||||
// TODO - Implement clone.
|
||||
// Implement "freezing" so the object cannot be modified.
|
||||
|
||||
public enum Type {
|
||||
BodyTube,
|
||||
NoseCone
|
||||
BODY_TUBE,
|
||||
NOSE_CONE
|
||||
}
|
||||
|
||||
public final static TypedKey<Double> LENGTH = new TypedKey<Double>("Length", Double.class);
|
||||
public final static TypedKey<Double> INNER_DIAMETER = new TypedKey<Double>("InnerDiameter", Double.class);
|
||||
public final static TypedKey<Double> OUTER_DIAMETER = new TypedKey<Double>("OuterDiameter", Double.class);
|
||||
public final static TypedKey<Material> MATERIAL = new TypedKey<Material>("Material", Material.class);
|
||||
public final static TypedKey<Finish> FINISH = new TypedKey<Finish>("Finish",Finish.class);
|
||||
public final static TypedKey<Finish> FINISH = new TypedKey<Finish>("Finish", Finish.class);
|
||||
public final static TypedKey<Double> THICKNESS = new TypedKey<Double>("Thickness", Double.class);
|
||||
public final static TypedKey<Boolean> FILLED = new TypedKey<Boolean>("Filled",Boolean.class);
|
||||
public final static TypedKey<Boolean> FILLED = new TypedKey<Boolean>("Filled", Boolean.class);
|
||||
public final static TypedKey<Double> MASS = new TypedKey<Double>("Mass", Double.class);
|
||||
|
||||
public final static Map<String,TypedKey<?>> keyMap = new HashMap<String,TypedKey<?>>();
|
||||
public final static Map<String, TypedKey<?>> keyMap = new HashMap<String, TypedKey<?>>();
|
||||
static {
|
||||
keyMap.put(LENGTH.getName(), LENGTH);
|
||||
keyMap.put(INNER_DIAMETER.getName(), INNER_DIAMETER);
|
||||
@ -51,9 +55,6 @@ public class ComponentPreset extends TypedPropertyMap {
|
||||
private String partDescription;
|
||||
private Type type;
|
||||
|
||||
public ComponentPreset() {
|
||||
|
||||
}
|
||||
|
||||
public String getManufacturer() {
|
||||
return manufacturer;
|
||||
@ -88,4 +89,30 @@ public class ComponentPreset extends TypedPropertyMap {
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean has(Object key) {
|
||||
return properties.containsKey(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(TypedKey<T> key) {
|
||||
Object value = properties.get(key);
|
||||
if (value == null) {
|
||||
throw new BugException("Preset of type " + type + " did not contain key " + key + " mfg=" + manufacturer + " partNo=" + partNo);
|
||||
}
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T put(TypedKey<T> key, T value) {
|
||||
return (T) properties.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return partNo;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ public class TypedPropertyMap {
|
||||
private final Map<TypedKey<?>, Object> delegate;
|
||||
|
||||
public TypedPropertyMap() {
|
||||
delegate = new LinkedHashMap<TypedKey<?>,Object>();
|
||||
delegate = new LinkedHashMap<TypedKey<?>, Object>();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
@ -45,7 +45,7 @@ public class TypedPropertyMap {
|
||||
}
|
||||
|
||||
public void putAll(TypedPropertyMap other) {
|
||||
if ( other == null ) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
delegate.putAll(other.delegate);
|
||||
@ -67,14 +67,4 @@ public class TypedPropertyMap {
|
||||
return delegate.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return delegate.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public abstract class BodyComponent extends ExternalComponent {
|
||||
|
||||
@Override
|
||||
protected void loadFromPreset(ComponentPreset preset) {
|
||||
if ( preset.containsKey(ComponentPreset.LENGTH) ) {
|
||||
if ( preset.has(ComponentPreset.LENGTH) ) {
|
||||
this.setLength(preset.get(ComponentPreset.LENGTH));
|
||||
}
|
||||
|
||||
|
@ -137,10 +137,10 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial
|
||||
@Override
|
||||
protected void loadFromPreset(ComponentPreset preset) {
|
||||
this.autoRadius = false;
|
||||
if ( preset.containsKey(ComponentPreset.OUTER_DIAMETER) ) {
|
||||
if ( preset.has(ComponentPreset.OUTER_DIAMETER) ) {
|
||||
double outerDiameter = preset.get(ComponentPreset.OUTER_DIAMETER);
|
||||
this.outerRadius = outerDiameter/2.0;
|
||||
if ( preset.containsKey(ComponentPreset.INNER_DIAMETER) ) {
|
||||
if ( preset.has(ComponentPreset.INNER_DIAMETER) ) {
|
||||
double innerDiameter = preset.get(ComponentPreset.INNER_DIAMETER);
|
||||
this.thickness = (outerDiameter-innerDiameter) / 2.0;
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ public abstract class ExternalComponent extends RocketComponent {
|
||||
|
||||
// Surface finish is left unchanged
|
||||
|
||||
if ( preset.containsKey(ComponentPreset.MATERIAL ) ) {
|
||||
if ( preset.has(ComponentPreset.MATERIAL ) ) {
|
||||
Material mat = preset.get(ComponentPreset.MATERIAL);
|
||||
if ( mat != null ) {
|
||||
setMaterial(mat);
|
||||
|
@ -148,10 +148,10 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
|
||||
|
||||
@Override
|
||||
protected void loadFromPreset(ComponentPreset preset) {
|
||||
if ( preset.containsKey(ComponentPreset.THICKNESS) ) {
|
||||
if ( preset.has(ComponentPreset.THICKNESS) ) {
|
||||
this.setThickness(preset.get(ComponentPreset.THICKNESS));
|
||||
}
|
||||
if ( preset.containsKey(ComponentPreset.FILLED)) {
|
||||
if ( preset.has(ComponentPreset.FILLED)) {
|
||||
this.setFilled(preset.get(ComponentPreset.FILLED));
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user