I am having a table view inside list view.
I want to make the table dynamic. There are fixed 3 columns, but the rows vary in number.
There is a image in each cell. I want that there should be as many table cell as number of images. The number of table rows vary in each row of the list view.
When I create table the image is add to the last cell.
public class BrandView extends LinearLayout {
TableLayout table_layout;
Context context;
Brand brand;
CheckBox chkType,chkName;
TextView txtTypeName,txtBrandName;
ImageView imgBrandImage;
public BrandView(Context context) {
super(context);
this.context=context;
HookUp();
}
public void setBrandView( Brand brand){
this.brand=brand;
imgBrandImage.setImageResource(brand.getImage());
txtTypeName.setText(brand.getTypeName());
}
public void HookUp(){
LayoutInflater inflater=LayoutInflater.from(context);
View view=inflater.inflate(R.layout.lay_brand_select_view,null);
chkType=(CheckBox) view.findViewById(R.id.chkType);
txtTypeName=(TextView) view.findViewById(R.id.txtTypeName);
table_layout=(TableLayout) view.findViewById(R.id.tableLayout1);
BuildTable(1,1);
this.addView(view);
}
private void BuildTable(int rows, int cols) {
// outer for loop
for (int i = 1; i <= rows; i++) {
TableRow row = new TableRow(context);
row.setLayoutParams(new TableRow.LayoutParams(200,
200));
// inner for loop
for (int j = 1; j <= cols; j++) {
LayoutInflater inflater = LayoutInflater.from(context);
View tv = inflater.inflate(R.layout.lay_table, null);
chkName=(CheckBox) tv.findViewById(R.id.chkName);
txtBrandName=(TextView) tv.findViewById(R.id.txtBrandName);
imgBrandImage=(ImageView) tv.findViewById(R.id.imgBrandImage);
row.addView(tv);
}
table_layout.addView(row, new TableLayout.LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT));
/* if(i==rows)
for(int l=k;l>0;l--)
row.removeViewAt(l);*/
}
}
}
You should add rows to your TableView programatically like you do it in BuildTable() method. The point is you need to call this method every time in ListView's adapter getView() method.
Related
I have a ListView where I send data from a database using SimpleCursorAdapter, but I need to have dynamic row layout, because every table can have different number of columns.
Let's say I create TextViews according to the number of columns:
public int getColumnNumbers() {
int i = 0;
cursor = db.rawQuery("PRAGMA table_info("+DATABASE_TABLE_NAME+")", null);
if (cursor.moveToFirst()) {
do {
i++;
} while (cursor.moveToNext());
}
cursor.close();
return i;
}
........
int rowsNumber = getColumnNumbers();
textViews = new TextView[rowsNumber];
for(i = 0; i < rowsNumber; i++) {
textViews[i] = new TextView(this);
textViews[i].setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
What I'm basically looking for, is a way to get these TextViews passed to CursorAdapter's (or other adapter's) argument int[] to
I'm pretty new to this, so I would appreciate any help or advice.
EDIT:
I'm adding my implementation of bindViewmethod, which I made with help of the links provided here, in case someone would have to face similar issue in the future.
#Override
public void bindView(View view, Context context, Cursor cursor) {
int i, count = myHelper.getColumnNumbers();
String[] columnNames = myHelper.getColumnNamesString();
String text;
LinearLayout layout = (LinearLayout) view.findViewById(R.id.linear_layout_horizontal);
for(i = 0; i < (count-1); i++) {
TextView textView = new TextView(context);
textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 1f));
text = cursor.getString(cursor.getColumnIndexOrThrow(columnNames[i+1]));
textView.setText(text);
layout.addView((TextView)textView);
}
}
EDIT 2:
I found out yesterday that the implementation mentioned above works until you need to scroll. After scrolling, ListView gets deformed.
So again, in case someone would like to do something similar, I'm adding my whole Adapter class.
public class DatabaseCursorAdapter extends CursorAdapter {
private DatabaseHelper myHelper;
private int count;
private String[] columnNames;
public DatabaseCursorAdapter(Context context, Cursor cursor) {
super(context, cursor, 0);
myHelper = new DatabaseHelper(context);
count = myHelper.getColumnNumbers();
columnNames = myHelper.getColumnNamesString();
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = LayoutInflater.from(context).inflate(R.layout.text_select, parent, false);
LinearLayout layout = (LinearLayout) view.findViewById(R.id.linear_layout_horizontal);
int i;
for(i = 0; i < count; i++) {
TextView textView = new TextView(context);
textView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1f));
textView.setTag(Integer.valueOf(i));
layout.addView(textView);
}
return view;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
int i;
String text;
for(i = 0; i < count; i++) {
TextView textView = (TextView) view.findViewWithTag(Integer.valueOf(i));
text = cursor.getString(cursor.getColumnIndexOrThrow(columnNames[i]));
textView.setText(text);
}
}
}
In the and, as it is with most issues, the solution is pretty easy. Technique is almost the same as if you would use a static layout, except instead of using findViewById, you just tag elements of your layout and use findViewWithTag method.
You need to look at good tutorial, a link is
Populating a ListView with a CursorAdapter . The web page has some nice explanations.
Look at TodoCursorAdapter and the bindView method to check which column/data is available from the database. Snippet of code from tutorial:
#Override
public void bindView(View view, Context context, Cursor cursor) {
// Find fields to populate in inflated template
TextView tvBody = (TextView) view.findViewById(R.id.tvBody);
// Extract properties from cursor
String body = cursor.getString(cursor.getColumnIndexOrThrow("body"));
// Populate fields with extracted properties
tvBody.setText(body);
}
I think this is a simple code design.
Code in the webpage to populate data onto Listview:
// Find ListView to populate
ListView lvItems = (ListView) findViewById(R.id.lvItems);
// Setup cursor adapter using cursor from last step
TodoCursorAdapter todoAdapter = new TodoCursorAdapter(this, todoCursor);
// Attach cursor adapter to the ListView
lvItems.setAdapter(todoAdapter);
You can implement an ArrayAdapter instead of CursorAdapter. This makes sense if your app is not continually interfacing with the database. The link is Using an ArrayAdapter with ListView . It is the same website.
In this case, look at getView method instead of the similar bindView.
When a user enters a word, it creates Buttons - one Button per letter of the word:
Illustration:
If the user enters "so" it creates 2 Buttons - 's', 'o'
If the user enters "make" it creates 4 Buttons - 'm', 'a', 'k', 'e'
I was having a hard time deciding how I should design this. Ultimately I decided to do the following: Each word is added to a vertical LinearLayout. And for each word, each letter is added to a horizontal LinearLayout. So it's a LinearLayout within a LinearLayout approach.
Here's the code I created which works:
//creates words dynamically
public void makeNewWord(LinearLayout ll, View v, EditText e){
//the horizontal linear layout
LinearLayout linearLayout2 = new LinearLayout(v.getContext());
linearLayout2.setOrientation(LinearLayout.HORIZONTAL);
//the parameters for the horizontal linear layout
LinearLayout.LayoutParams rlp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
//e is the user input
int size = e.getText().toString().length();
for (int i=0; i<size; i++){
final Button dynamicButtons = new Button(v.getContext());
dynamicButtons.setLayoutParams(rlp);
//add the buttons to the horizontal linear layout
linearLayout2.addView(dynamicButtons, rlp);
}
// ll is the vertical linear layout which I created in xml
// so for each entered word, I am adding horizontal linear layouts to my vertical layout
ll.addView(linearLayout2, 0);
}
But now I realized it's probably more efficient using a ListView, especially since I want to make the list of words to be expandable and collapsible. But Is it possible to create the above illustration using a ListView? How would I go about doing so?
I tried creating an ArrayAdapter as follows: ArrayAdapter<LinearLayout> adapter = new ArrayAdapter<LinearLayout>(this, R.id.listview). So basically it would be a ListView of horizontal LinearLayouts. Or should I make an ArrayAdapter of Buttons instead? What is the correct approach?
I can give some idea you can transform this idea in code
1. onTextChanged() method try to get length of text.
2. If you able to get text length then by subString() method get last entered text
3. Then recreate new button instance
You can use a TableLayout for this.
test.xml
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<TableLayout
android:id="#+id/table_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TableLayout>
</ScrollView>
Activity Code
private TableLayout tableLayout;
private HashMap<String, TableRow> tableRows;
#Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.test);
super.onCreate(savedInstanceState);
tableLayout = (TableLayout) findViewById(R.id.table_layout);
tableRows = new HashMap<String, TableRow>();
}
public void addWord(String word) {
if (!tableRows.containsKey(word)) {
TableRow tableRow = new TableRow(this);
for (int i = 0; i < word.length(); i++) {
String letter = String.valueOf(word.charAt(i));
Button btnLetter = new Button(this);
btnLetter.setText(letter);
tableRow.addView(btnLetter);
}
tableRows.put(word, tableRow);
tableLayout.addView(tableRow);
}
}
public void removeWord(String word) {
TableRow tableRow = tableRows.remove(word);
if (tableRow != null) {
tableLayout.removeView(tableRow);
}
}
public void showWord(String word) {
TableRow tableRow = tableRows.get(word);
if (tableRow != null) {
tableRow.setVisibility(View.VISIBLE);
}
}
public void hideWord(String word) {
TableRow tableRow = tableRows.get(word);
if (tableRow != null) {
tableRow.setVisibility(View.GONE);
}
}
Assuming you want a specific button setup, you can inflate an xml button layout dynamically. See here for details.
I would just use a horizontal list view per word.
I you want to be fancy you create a custom layout manager and the new RecyclerView.
Each character of your word would be then a item in your list view. The layout then could be simply a button.
class ListAdapter extends BaseAdapter {
private final Context fContext;
private String mWord;
public ListAdapter(Context context) {
fContext = context;
}
public void updateWord(String word) {
mWord = word;
notifyDataSetChanged();
}
#Override
public int getCount() {
return mWord == null ? 0 : mWord.length();
}
#Override
public String getItem(int position) {
return String.valueOf(mWord.charAt(position));
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Button button;
if (convertView == null) {
button = new Button(parent.getContext());
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
button.setLayoutParams(params);
} else {
button = (Button) convertView;
}
button.setText(getItem(position));
return button;
}
}
On every text change you can then just update the list.
adapter.updateWord();
Be aware the code is just out of my head and i haven't tested this, but should be enough to give you and idea.
Hello i used a code to generate some buttons in a tablelayout. The buttons are generated by this layout, so none of them has an id yet. So here is my question. How can i give a specific button an id? (For example the button in the first row, second colum should have the id "button2")
public class MainActivity extends ActionBarActivity {
private static final int NUM_ROWS = 3;
private static final int NUM_COLS = 3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
populateButtons();
}
private void populateButtons() {
TableLayout table = (TableLayout) findViewById(R.id.TableLayout);
for (int row =0; row < NUM_ROWS; row++) {
TableRow tableRow = new TableRow(this);
tableRow.setLayoutParams(new TableLayout.LayoutParams(
TableLayout.LayoutParams.MATCH_PARENT,
TableLayout.LayoutParams.MATCH_PARENT,
1.0f));
table.addView(tableRow);
for (int col = 0; col < NUM_COLS; col++) {
Button button = new Button(this);
tableRow.addView(button);
}
}
}
You can use View.setId(), however I guess you don't have to find the Views by id because you already know them.
Code:
SimpleCursorAdapter ada = new SimpleCursorAdapter(this,
R.layout.custom_layout, ssCursor, new String[] {
"String" }, new int[] {
R.id.txt2 });
lvSms.setAdapter(ada);
btnDel.setOnClickListener(new View.OnClickListener() {
private ArrayList<String> msgArr;
public void onClick(View v) {
LinearLayout ly = (LinearLayout) findViewById(R.id.lv);
ListView lvMsg = (ListView) ly.getChildAt(3);
int listCount = lvSms.getCount();
for (int a = 0 ; a < listCount ; a++)
{
LinearLayout ll = (LinearLayout) lvMsg.getChildAt(a);
LinearLayout l2 = (LinearLayout) ll.getChildAt(0);
LinearLayout l3 = (LinearLayout) l2.getChildAt(0);
CheckBox chkBox =(CheckBox) l3.getChildAt(0);
boolean valueOfChkBox = chkBox.isChecked();
if (valueOfChkBox) {
LinearLayout l4 = (LinearLayout) l2.getChildAt(1);
TextView txt1 = (TextView) l4.getChildAt(0);
msgArr = new ArrayList<String>();
msgArr.add(txt1.getText().toString());
Log.i("hello", txt1.getText().toString());
}
} });
I am getting a NullPointerException, as getChildAt returns the visible rows and I have to also check the invisible rows, either they are checked or not. So how could I check it on separate button?
I'm guessing that you want to obtain the text of the TextViews from the rows that are checked in the ListView. You can't use getChildAt() and just parse all the rows, instead you should use the data that you pass in the adapter, the cursor(and figure out what rows are checked). Because you use a custom row for the layout you'll have to somehow maintain the checked/unchecked status of your rows CheckBoxes(with a custom adapter). When is time to get the text, at a click of that Button, then simply find out what rows are currently checked and extract from the cursor directly the text you want.
A simple way to store the status of the CheckBoxes is to have an ArrayList<Boolean> that you'll modify depending on which CheckBox you act(probably not that efficient):
private class CustomAdapter extends SimpleCursorAdapter {
// this will hold the status of the CheckBoxes
private ArrayList<Boolean> checkItems = new ArrayList<Boolean>();
public CustomAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to);
int count = c.getCount();
for (int i = 0; i < count; i++) {
checkItems.add(false);
}
}
//this method returns the current status of the CheckBoxes
//use this to see what CheckBoxes are currently checked
public ArrayList<Boolean> getItemsThatAreChecked() {
return checkItems;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
super.bindView(view, context, cursor);
int position = cursor.getPosition();
CheckBox ckb = (CheckBox) view.findViewById(R.id.checkBox);
ckb.setTag(new Integer(position));
ckb.setChecked(checkItems.get(position));
ckb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
Integer realPosition = (Integer) buttonView.getTag();
checkItems.set(realPosition, isChecked);
}
});
}
}
Then parse the ArrayList you get from getItemsThatAreChecked() and extract the data from the cursor. Here you have a small example http://pastebin.com/tsZ6mzt9 (or here git://gist.github.com/2634525.git with the layouts)
LinearLayout ly = (LinearLayout) findViewById(R.id.lv);
The above line - does it try to take the root layout? If so, you can't use findViewById. Rather use:
getLayoutInflater().inflate(R.layout.mylayout)
getLayoutInflater() is Activity's method
I want to get data from database in my android table view.
Should I use loop? Is static good for this?
This may be useful for you..
try{
JSONArray jArray = new JSONArray(result);
TableLayout tv=(TableLayout) findViewById(R.id.table);
tv.removeAllViewsInLayout();
int flag=1;
// when i=-1, loop will display heading of each column
// then usually data will be display from i=0 to jArray.length()
for(int i=-1;i<jArray.length();i++){
TableRow tr=new TableRow(Yourclassname.this);
tr.setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT));
// this will be executed once
if(flag==1){
TextView b3=new TextView(Yourclassname.this);
b3.setText("column heading 1");
b3.setTextColor(Color.BLUE);
b3.setTextSize(15);
tr.addView(b3);
TextView b4=new TextView(Yourclassname.this);
b4.setPadding(10, 0, 0, 0);
b4.setTextSize(15);
b4.setText("column heading 2");
b4.setTextColor(Color.BLUE);
tr.addView(b4);
TextView b5=new TextView(Yourclassname.this);
b5.setPadding(10, 0, 0, 0);
b5.setText("column heading 3");
b5.setTextColor(Color.BLUE);
b5.setTextSize(15);
tr.addView(b5);
tv.addView(tr);
final View vline = new View(Yourclassname.this);
vline.setLayoutParams(new
TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, 2));
vline.setBackgroundColor(Color.BLUE);
tv.addView(vline); // add line below heading
flag=0;
} else {
JSONObject json_data = jArray.getJSONObject(i);
TextView b=new TextView(Yourclassname.this);
String str=String.valueOf(json_data.getInt("column1"));
b.setText(str);
b.setTextColor(Color.RED);
b.setTextSize(15);
tr.addView(b);
TextView b1=new TextView(Yourclassname.this);
b1.setPadding(10, 0, 0, 0);
b1.setTextSize(15);
String str1=json_data.getString("column2");
b1.setText(str1);
b1.setTextColor(Color.WHITE);
tr.addView(b1);
TextView b2=new TextView(Yourclassname.this);
b2.setPadding(10, 0, 0, 0);
String str2=String.valueOf(json_data.getInt("column3"));
b2.setText(str2);
b2.setTextColor(Color.RED);
b2.setTextSize(15);
tr.addView(b2);
tv.addView(tr);
final View vline1 = new View(Yourclassname.this);
vline1.setLayoutParams(new
TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, 1));
vline1.setBackgroundColor(Color.WHITE);
tv.addView(vline1); // add line below each row
}
}
}catch(JSONException e){
Log.e("log_tag", "Error parsing data " + e.toString());
Toast.makeText(getApplicationContext(), "JsonArray fail", Toast.LENGTH_SHORT).show();
}
I see this post is pretty old, but if someone else is facing also the issue to display custom data in a Table in Android, I would like to offer my TableView as possible solution to do so.
So you do not care about how to fill the data to your table, you can simply create a custom adapter for the data you want to show (like we already know in Android from views like the ListView).
our code will look like this:
List<Flight> myData = new ArrayList<>();
myData.add(new Flight(...));
myData.add(new Flight(...));
myData.add(new Flight(...));
TableView<Flight> table = findViewById(R.id.table);
table.setHeaderAdapter(new SimpleHeaderAdapter("Time", "Airline", "Flight", "Destination"));
table.setDataAdapter(new FlightDataAdapter(myData));
The result could look like this:
rs1 = stmt.executeQuery("SELECT * from message");
StringBuilder sb = new StringBuilder();
while (rs1.next())
{
String script = rs1.getString(1);
String call = rs1.getString(2);
String price = rs1.getString(3);
String stoploss = rs1.getString(4);
String target = rs1.getString(5);
String ltp = rs1.getString(6);
String exit = rs1.getString(7);
sb.append(script).append(";").append(call).append(";").append(price).append(";").append(stoploss).append(";").append(target).append(";").append(ltp).append(";").append(exit).append("_");
}
out.print(sb.toString());
out.flush();
for this you have XML
for this you have a XML like
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_marginTop="20dip">
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/tab"
>
<TableRow>
</TableRow>
</TableLayout>
</LinearLayout>
to show the data in the android you write.
String st = new String(str);
Log.e("Main",st);
String[] rows = st.split("_");
TableLayout tableLayout = (TableLayout)findViewById(R.id.tab);
tableLayout.removeAllViews();
for(int i=0;i<rows.length;i++){
String row = rows[i];
TableRow tableRow = new TableRow(getApplicationContext());
final String[] cols = row.split(";");
Handler handler = null;
for (int j = 0; j < cols.length; j++) {
final String col = cols[j];
final TextView columsView = new TextView(getApplicationContext());
columsView.setText(String.format("%7s", col));
tableRow.addView(columsView);
That depends. If you're sure that you'll have only a few rows then you can inflate add them in loop to the TableLayout. But note that you create view for every row.
With lot of data create ListView and adapter for example based on CursorAdapter:
public class MyCursorAdapter extends CursorAdapter {
private static final String TAG = "MyCursorAdapter";
private final int NAME_COLUMN;
private final int ADDRESS_COLUMN;
private final int STATE_COLUMN;
public MyCursorAdapter(Context context, Cursor c) {
super(context, c);
NAME_COLUMN = c.getColumnIndexOrThrow("name");
ADDRESS_COLUMN = c.getColumnIndexOrThrow("address");
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.custom_row, null);
MyRowViewHolder rowData = new MyRowViewHolder();
rowData.name = (TextView) view.findViewById(R.id.name);
rowData.address = (TextView) view.findViewById(R.id.address);
rowData.name.setText(cursor.getString(NAME_COLUMN));
rowData.address.setText(cursor.getString(ADDRESS_COLUMN));
view.setTag(rowData);
return view;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
MyRowViewHolder rowData = (MyRowViewHolder) view.getTag();
rowData.name.setText(cursor.getString(NAME_COLUMN));
rowData.address.setText(cursor.getString(ADDRESS_COLUMN));
}
public static class MyRowViewHolder {
TextView name;
TextView address;
}
}
This approach doesn't create view for every row. I think that's better but needs more effort. To get table layout style use LinearLayout for rows with layout_weight for columns
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/name"
android:layout_weight="0.25"
android:layout_width="0"
android:layout_height="wrap_content">
</TextView>
<TextView
android:id="#+id/address"
android:layout_weight="0.75"
android:layout_width="0"
android:layout_height="wrap_content">
</TextView>
</LinearLayout>
To the ListView add header and footer if you want.
Static can be used when you have a defined never changing number of rows/cols in your table you want to fill. Otherwise I suggest to use a loop and add a row to the table view for each step in your loop.
We could imagine a custom component for android : the TableView.
Its code would be something like :
public class TableView extends TableLayout {
public TableView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TableView(Context context) {
super(context);
}
public void setAdapter(TableAdapter<?> adapter) {
removeAllViews();
for (int row = 0; row < adapter.getRowCount(); row++) {
TableRow tableRow = new TableRow(getContext());
TableLayout.LayoutParams params = new TableLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
tableRow.setLayoutParams(params);
addView(tableRow);
for (int column = 0; column < adapter.getColumnCount(); column++) {
View cell = adapter.getView(row, column);
tableRow.addView(cell);
TableRow.LayoutParams cellParams = (android.widget.TableRow.LayoutParams) cell.getLayoutParams();
cellParams.weight = adapter.getColumnWeight(column);
cellParams.width = 0;
}
}
}
}
And it would use an adapter like this :
public interface TableAdapter<T> {
int getRowCount();
int getColumnWeight(int column);
int getColumnCount();
T getItem(int row, int column);
View getView(int row, int column);
}