Home » Eclipse Projects » NatTable » Row Filtering issue(Problems getting filtering to work)
Row Filtering issue [message #1852299] |
Mon, 09 May 2022 09:00  |
Eclipse User |
|
|
|
Dear Dirk
I have tried some days to add row filtering to my Nat Table columns.
As I didn't succeed, I tried to narrow down the problem by creating the simplest Nat Table function, having only this feature in my table.
The result, however, is the same. The table returns all rows except the last and leaves an empty row. Also, I do not get any filtering at the top of my columns.
I am running version 2.0, what do I do wrong? :-)
Best regards
Peter Vilhelmsen
NatTable:
package testing.nat;
import static org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes.CELL_PAINTER;
import static org.eclipse.nebula.widgets.nattable.grid.GridRegion.FILTER_ROW;
import static org.eclipse.nebula.widgets.nattable.style.DisplayMode.NORMAL;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDoubleDisplayConverter;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.DefaultGlazedListsFilterStrategy;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterIconPainter;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowHeaderComposite;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowPainter;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.layer.stack.DefaultBodyLayerStack;
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.style.Style;
import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
public class TheDialog extends Dialog {
protected TheDialog(Shell parentShell) {
super(parentShell);
}
private NatTable natTable;
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText("Test");
newShell.setMinimumSize(920, 400);
}
@Override
protected Control createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
GridLayout layoutZeroMarginVH = new GridLayout();
layoutZeroMarginVH.marginWidth = 0;
layoutZeroMarginVH.marginHeight = 0;
Composite groupPanel = new Composite(container, SWT.BORDER);
groupPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
groupPanel.setLayout(layoutZeroMarginVH);
// property names of the Person class
List<String> propertyNames = Arrays.asList("Name", "Surname");
ConfigRegistry configRegistry = new ConfigRegistry();
CustomDataProvider dataProvider = new CustomDataProvider();
EventList<Data> eventList = GlazedLists.eventList(dataProvider.getDataArray());
FilterList<Data> filterList = new FilterList<>(eventList);
IColumnPropertyAccessor<Data> columnPropertyAccessor = new ReflectiveColumnPropertyAccessor<>( propertyNames);
DataLayer bodyDataLayer = new DataLayer(dataProvider);
DefaultBodyLayerStack bodyLayer = new DefaultBodyLayerStack(bodyDataLayer);
// create the column header layer stack
IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(propertyNames.toArray(new String[0]));
DataLayer headerDataLayer = new DataLayer(columnHeaderDataProvider);
ILayer columnHeaderLayer = new ColumnHeaderLayer(headerDataLayer, bodyLayer, bodyLayer.getSelectionLayer());
FilterRowHeaderComposite<Data> filterRowHeaderLayer = new FilterRowHeaderComposite<>(
new DefaultGlazedListsFilterStrategy<>(filterList,
columnPropertyAccessor,
configRegistry),
columnHeaderLayer,
columnHeaderDataProvider,
configRegistry);
ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(headerDataLayer);
headerDataLayer.setConfigLabelAccumulator(labelAccumulator);
// Row header layer
DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);
DefaultRowHeaderDataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, bodyLayer, bodyLayer.getSelectionLayer());
// Corner layer
DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
DataLayer cornerDataLayer = new DataLayer(cornerDataProvider);
CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, filterRowHeaderLayer);
GridLayer gridLayer = new GridLayer(bodyLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer);
natTable = new NatTable(groupPanel, gridLayer, false);
natTable.setConfigRegistry(configRegistry);
natTable.addConfiguration(new FilterRowCustomConfiguration() {
@Override
public void configureRegistry(IConfigRegistry configRegistry) {
super.configureRegistry(configRegistry);
// Shade the row to be slightly darker than the blue background.
final Style rowStyle = new Style();
rowStyle.setAttributeValue(
CellStyleAttributes.BACKGROUND_COLOR,
GUIHelper.getColor(197, 212, 231));
configRegistry.registerConfigAttribute(
CellConfigAttributes.CELL_STYLE,
rowStyle,
DisplayMode.NORMAL,
GridRegion.FILTER_ROW);
}
});
natTable.configure();
natTable.setTheme(new ModernNatTableThemeConfiguration());
GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
return container;
}
public static class FilterRowCustomConfiguration extends AbstractRegistryConfiguration {
final DefaultDoubleDisplayConverter doubleDisplayConverter = new DefaultDoubleDisplayConverter();
@Override
public void configureRegistry(IConfigRegistry configRegistry) {
// override the default filter row configuration for painter
configRegistry.registerConfigAttribute(
CELL_PAINTER,
new FilterRowPainter(
new FilterIconPainter(GUIHelper.getImage("filter"))),
NORMAL, FILTER_ROW);
}
}
}
Supporting code, CustomDataProvider:
package testing.nat;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider;
public class CustomDataProvider implements IRowDataProvider<Data> {
public static final int NUMBER_OF_COLS = 2;
private List<Data> dataArray = new ArrayList<Data>();
CustomDataProvider() {
dataArray.add(new Data("Name1", "Surname1"));
dataArray.add(new Data("Name2", "Surname2"));
dataArray.add(new Data("Name3", "Surname3"));
}
@Override
public int getColumnCount() {
return NUMBER_OF_COLS;
}
@Override
public Object getDataValue(int col, int row) {
Data data = dataArray.get(row);
switch(col) {
case 0:
return data.getName();
case 1:
return data.getSurname();
default:
return "UNKNOWN";
}
}
@Override
public int getRowCount() {
if(dataArray == null)
return 0;
return dataArray.size();
}
@Override
public void setDataValue(int arg0, int arg1, Object arg2) {
// TODO Auto-generated method stub
}
@Override
public Data getRowObject(int row) {
if(dataArray.size() > row) {
return dataArray.get(row);
}
return null;
}
@Override
public int indexOfRowObject(Data d1) {
for ( int i1 = 0; i1 < dataArray.size(); ++i1) {
Data d2 = dataArray.get(i1);
if(d2.getName().equalsIgnoreCase(d1.getName()) &&
d2.getSurname().equalsIgnoreCase(d1.getSurname())) {
return i1;
}
}
return 0;
}
public List<Data> getDataArray() {
return dataArray;
}
}
Supporting code, Data:
package testing.nat;
public class Data {
public Data(String name, String surname) {
super();
this.name = name;
this.surname = surname;
}
private String name;
private String surname;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
}
Supporting code, Entry:
package testing.nat;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class Entry {
public static void main (String args[]) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Nat table example");
shell.setSize(600, 400);
shell.open();
TheDialog dlg = new TheDialog(shell);
dlg.open();
display.dispose();
}
}
|
|
| |
Re: Row Filtering issue [message #1852312 is a reply to message #1852301] |
Tue, 10 May 2022 07:27   |
Eclipse User |
|
|
|
Thanks Dirk
I have tried your suggestions, except adding the SLF4J binding. Can you elaborate on this please? How will I implement this binding to my code?
After the updates, the code is now like this:
The Dialog
package testing.nat;
import static org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes.CELL_PAINTER;
import static org.eclipse.nebula.widgets.nattable.grid.GridRegion.FILTER_ROW;
import static org.eclipse.nebula.widgets.nattable.style.DisplayMode.NORMAL;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDoubleDisplayConverter;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.DefaultGlazedListsFilterStrategy;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterIconPainter;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowHeaderComposite;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowPainter;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.style.Style;
import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
public class TheDialog extends Dialog {
protected TheDialog(Shell parentShell) {
super(parentShell);
}
private NatTable natTable;
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText("Test");
newShell.setMinimumSize(920, 400);
}
@Override
protected Control createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
GridLayout layoutZeroMarginVH = new GridLayout();
layoutZeroMarginVH.marginWidth = 0;
layoutZeroMarginVH.marginHeight = 0;
Composite groupPanel = new Composite(container, SWT.BORDER);
groupPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
groupPanel.setLayout(layoutZeroMarginVH);
// property names of the Person class
List<String> propertyNames = Arrays.asList("Name", "Surname");
ConfigRegistry configRegistry = new ConfigRegistry();
CustomDataProvider dataProvider = new CustomDataProvider();
EventList<Data> eventList = GlazedLists.eventList(dataProvider.getDataArray());
FilterList<Data> filterList = new FilterList<>(eventList);
IColumnPropertyAccessor<Data> columnPropertyAccessor = new ReflectiveColumnPropertyAccessor<>( propertyNames);
DataLayer bodyDataLayer = new DataLayer(dataProvider);
SelectionLayer selectionLayer = new SelectionLayer(bodyDataLayer);
GlazedListsEventLayer<Data> bodyLayer = new GlazedListsEventLayer<>(bodyDataLayer, filterList);
// create the column header layer stack
IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(propertyNames.toArray(new String[0]));
DataLayer headerDataLayer = new DataLayer(columnHeaderDataProvider);
ILayer columnHeaderLayer = new ColumnHeaderLayer(headerDataLayer, bodyLayer, selectionLayer);
FilterRowHeaderComposite<Data> filterRowHeaderLayer = new FilterRowHeaderComposite<>(
new DefaultGlazedListsFilterStrategy<>(filterList,
columnPropertyAccessor,
configRegistry),
columnHeaderLayer,
columnHeaderDataProvider,
configRegistry);
ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(headerDataLayer);
headerDataLayer.setConfigLabelAccumulator(labelAccumulator);
// Row header layer
DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);
DefaultRowHeaderDataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, bodyLayer, selectionLayer);
// Corner layer
DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
DataLayer cornerDataLayer = new DataLayer(cornerDataProvider);
CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, filterRowHeaderLayer);
GridLayer gridLayer = new GridLayer(bodyLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer);
natTable = new NatTable(groupPanel, gridLayer, false);
natTable.setConfigRegistry(configRegistry);
natTable.addConfiguration(new FilterRowCustomConfiguration() {
@Override
public void configureRegistry(IConfigRegistry configRegistry) {
super.configureRegistry(configRegistry);
// Shade the row to be slightly darker than the blue background.
final Style rowStyle = new Style();
rowStyle.setAttributeValue(
CellStyleAttributes.BACKGROUND_COLOR,
GUIHelper.getColor(197, 212, 231));
configRegistry.registerConfigAttribute(
CellConfigAttributes.CELL_STYLE,
rowStyle,
DisplayMode.NORMAL,
GridRegion.FILTER_ROW);
}
});
natTable.configure();
natTable.setTheme(new ModernNatTableThemeConfiguration());
GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
return container;
}
public static class FilterRowCustomConfiguration extends AbstractRegistryConfiguration {
final DefaultDoubleDisplayConverter doubleDisplayConverter = new DefaultDoubleDisplayConverter();
@Override
public void configureRegistry(IConfigRegistry configRegistry) {
// override the default filter row configuration for painter
configRegistry.registerConfigAttribute(
CELL_PAINTER,
new FilterRowPainter(
new FilterIconPainter(GUIHelper.getImage("filter"))),
NORMAL, FILTER_ROW);
}
}
}
I have used the GlazedListsEventLayer<Data> as you suggest, also I have made the data Provider extend IDataProvider.
I see that the data provider returns the correct number of rows (3), but row 2 is never fetched (0 and 1 is).
Data Provider
package testing.nat;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
public class CustomDataProvider implements IDataProvider {
public static final int NUMBER_OF_COLS = 2;
private List<Data> dataArray = new ArrayList<Data>();
CustomDataProvider() {
dataArray.add(new Data("Name1", "Surname1"));
dataArray.add(new Data("Name2", "Surname2"));
dataArray.add(new Data("Name3", "Surname3"));
}
@Override
public int getColumnCount() {
return NUMBER_OF_COLS;
}
@Override
public Object getDataValue(int col, int row) {
Data data = dataArray.get(row);
switch(col) {
case 0:
return data.getName();
case 1:
return data.getSurname();
default:
return "UNKNOWN";
}
}
@Override
public int getRowCount() {
if(dataArray == null)
return 0;
return dataArray.size();
}
@Override
public void setDataValue(int arg0, int arg1, Object arg2) {
}
public List<Data> getDataArray() {
return dataArray;
}
}
I really hope to have this working, it is the last bit of functionality before I can show off the NatTable.
Best regards
Peter Vilhelmsen
|
|
|
Re: Row Filtering issue [message #1852314 is a reply to message #1852312] |
Tue, 10 May 2022 08:41   |
Eclipse User |
|
|
|
Again, your CustomDataProvider is the problem. You should drop that thing completely and use the ListDataProvider which is provided by NatTable.
Apart from that there are multiple issues in your code:
1. The GlazedListsEventLayer needs to be placed on top of the DataLayer
2. Your layer stack is inconsistent. You need to be more careful when composing the layer stack.
3. The biggest problem is that you are not using the FilterRowHeaderComposite as column header in your GridLayer creation. That means you are skipping the filter row in the grid rendering, which causes exceptions in the background when trying to access grid coordinates.
The following snippet is the fixed part of your example:
// property names of the Person class
List<String> propertyNames = Arrays.asList("name", "surname");
IColumnPropertyAccessor<Data> columnPropertyAccessor = new ReflectiveColumnPropertyAccessor<>(propertyNames);
ConfigRegistry configRegistry = new ConfigRegistry();
List<Data> dataArray = new ArrayList<>();
dataArray.add(new Data("Name1", "Surname1"));
dataArray.add(new Data("Name2", "Surname2"));
dataArray.add(new Data("Name3", "Surname3"));
EventList<Data> eventList = GlazedLists.eventList(dataArray);
FilterList<Data> filterList = new FilterList<>(eventList);
ListDataProvider<Data> dataProvider = new ListDataProvider<>(filterList, columnPropertyAccessor);
DataLayer bodyDataLayer = new DataLayer(dataProvider);
GlazedListsEventLayer<Data> bodyLayer = new GlazedListsEventLayer<>(bodyDataLayer, filterList);
SelectionLayer selectionLayer = new SelectionLayer(bodyLayer);
// create the column header layer stack
Map<String, String> propertyToLabelMap = new HashMap<>();
propertyToLabelMap.put("name", "Name");
propertyToLabelMap.put("surname", "Surname");
IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(propertyNames.toArray(new String[0]), propertyToLabelMap);
DataLayer headerDataLayer = new DataLayer(columnHeaderDataProvider);
ILayer columnHeaderLayer = new ColumnHeaderLayer(headerDataLayer, selectionLayer, selectionLayer);
FilterRowHeaderComposite<Data> filterRowHeaderLayer = new FilterRowHeaderComposite<>(
new DefaultGlazedListsFilterStrategy<>(filterList,
columnPropertyAccessor,
configRegistry),
columnHeaderLayer,
columnHeaderDataProvider,
configRegistry);
ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(headerDataLayer);
headerDataLayer.setConfigLabelAccumulator(labelAccumulator);
// Row header layer
DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);
DefaultRowHeaderDataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, selectionLayer, selectionLayer);
// Corner layer
DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
DataLayer cornerDataLayer = new DataLayer(cornerDataProvider);
CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, filterRowHeaderLayer);
GridLayer gridLayer = new GridLayer(bodyLayer, filterRowHeaderLayer, rowHeaderLayer, cornerLayer);
|
|
| | |
Goto Forum:
Current Time: Wed May 21 08:27:49 EDT 2025
Powered by FUDForum. Page generated in 0.06535 seconds
|