JComboBox Item Change - swing

My JComboBox model contains item like item1, item2, item1. My problem is when I select third item (item1) in JComboBox and check getSelectedIndex() it always returns 0.
If the item is same in my model how can I get index of each item differently? Like:
item1 returns 0
item 2 returns 1
item1 returns 2

It returns index = 0. Because the method getSelectedIndex() use .equals on objects that are in the JComboBox and compare it with the selected one. In your case because item1 is also at index 0 it finds the condition true and returns 0. If you want to get different index then you have to override the getSelectedIndex() method.
An outline of default getSelectedIndex() method of JComboBox found at Java2s:
public int getSelectedIndex() {
Object sObject = dataModel.getSelectedItem();
int i, c;
Object obj;
for (i = 0, c = dataModel.getSize(); i < c; i++) {
obj = dataModel.getElementAt(i);
if (obj != null && obj.equals(sObject))
return i;
}
return -1;
}
You should have something [may be itemName if item object has a name or anything else] different in 2 entries to get desired result. Override getSelectedIndex() and compare the thing that is meant to be differ in all. If both entries are completely same then whats the point of adding it twice?

If two entries in the JComboBox correspond to the same Object, then even if you click item 3 the actual item that is selected will be the first entry of that object (i.e. the one with the lowest index)
I don't think that this will work for the same objects.

A JList has no problems with identical items.
import javax.swing.event.*;
import javax.swing.*;
class TestList {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final String[] items = {"item1", "item2", "item1"};
final JList list = new JList(items);
final JTextField output = new JTextField(15);
JPanel gui = new JPanel();
gui.add(list);
gui.add(output);
list.addListSelectionListener(new ListSelectionListener(){
public void valueChanged(ListSelectionEvent lse) {
int index = list.getSelectedIndex();
String outputText =
"Index: " +
index +
" Value: " +
items[index];
output.setText(outputText);
}
});
JOptionPane.showMessageDialog(null, gui);
}
});
}
}

Related

Displaying data from SQLite table's columns, one of which holds an array

I managed to retrieve the SQLite table with only the first item of the array and put it in the UI's TextView. Couldn't get all the of the array's items. From each of the rest of the columns, a single value is returned successfully.
The JSON is parsed and passed as a parcelable ArrayList to a Fragment where it's presented in a list. Clicking on a list item directs to another Fragment where all the of item's details are presented.
I've been trying to write a for loop that returns the Strings in the array into the TextView, but the condition i < genresList.size() is always false. I tried using a while loop, but it returns only the first item of the list.
Various ways I've found on the internet didn't work.
Thanks.
Parsing and insertion to SQLite
private void parseJsonAndInsertToSQLIte(SQLiteDatabase db) throws JSONException {
// parsing the json
String jsonString = getJsonFileData();
JSONArray moviesArray = new JSONArray(jsonString);
ContentValues insertValues;
for (int i = 0; i < moviesArray.length(); i++) {
JSONObject jsonObject = moviesArray.getJSONObject(i);
String title = jsonObject.getString("title");
String imageUrl = jsonObject.getString("image");
String rating = jsonObject.getString("rating");
String releaseYear = jsonObject.getString("releaseYear");
JSONArray genresArray = jsonObject.getJSONArray("genre");
List<String> genres = new ArrayList<>();
for (int k = 0; k < genresArray.length(); k++) {
genres.add(genresArray.getString(k));
}
insertValues = new ContentValues();
insertValues.put(Movie.TITLE, title);
insertValues.put(Movie.IMAGE_URL, imageUrl);
insertValues.put(Movie.RATING, rating);
insertValues.put(Movie.RELEASE_YEAR, releaseYear);
for (int k = 0; k < genresArray.length(); k++) {
insertValues.put(Movie.GENRE, genres.get(k));
}
Log.i(TAG, "insertValues: " + genresArray);
long res = db.insert(TABLE_NAME, null, insertValues);
Log.i(TAG, "parsed and inserted to sql - row: " + res);
}
}
The item's details Fragment
public class MovieDetailsFragment extends Fragment{
... variables declarations come here...
#Nullable
#Override
public View onCreateView(#NotNull LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_details_movie, container, false);
Context context = getActivity();
Bundle idBundle = getArguments();
if (idBundle != null) {
movieId = getArguments().getInt("id");
}
getDatabase = new GetDatabase(context);
getDatabase.open();
Cursor cursor = getDatabase.getMovieDetails(movieId);
... more irelevant code comes here...
titleView = rootView.findViewById(R.id.movieTtlId);
ratingView = rootView.findViewById(R.id.ratingId);
releaseYearView = rootView.findViewById(R.id.releaseYearId);
genreView = rootView.findViewById(R.id.genreID);
String titleFromSQLite = cursor.getString(cursor.getColumnIndex(Movie.TITLE));
String ratingFromSQLite = cursor.getString(cursor.getColumnIndex(Movie.RATING));
String releaseYearFromSQLite = cursor.getString(cursor.getColumnIndex(Movie.RELEASE_YEAR));
String genreFromSQLite;
if(cursor.moveToFirst()) {
do {
genreFromSQLite = cursor.getString(cursor.getColumnIndex(Movie.GENRE));
genres.add(genreFromSQLite);
} while (cursor.moveToNext());
}
else{
genreFromSQLite = cursor.getString(cursor.getColumnIndex(Movie.RELEASE_YEAR));
}
getDatabase.close();
//more irelevant code comes here
genreView.setText(genreFromSQLite);
genreView.setFocusable(false);
genreView.setClickable(false);
return rootView;
}
}
The method that returns the table from SQLite:
public ArrayList<Movie> getMovies() {
String[] columns = {
Movie.ID,
Movie.TITLE,
Movie.IMAGE_URL,
Movie.RATING,
Movie.RELEASE_YEAR,
Movie.GENRE
};
// sorting orders
String sortOrder =
Movie.RELEASE_YEAR + " ASC";
ArrayList<Movie> moviesList = new ArrayList<>();
Cursor cursor = db.query(TABLE_NAME, //Table to query
columns,
null,
null,
null,
null,
sortOrder);
if (cursor.moveToFirst()) {
do {
Movie movie = new Movie();
movie.setMovieId(Integer.parseInt(cursor.getString(cursor.getColumnIndex(Movie.ID))));
movie.setTitle(cursor.getString(cursor.getColumnIndex(Movie.TITLE)));
movie.setImageUrl(cursor.getString(cursor.getColumnIndex(Movie.IMAGE_URL)));
movie.setRating(cursor.getDouble(cursor.getColumnIndex(Movie.RATING)));
movie.setReleaseYear(cursor.getInt(cursor.getColumnIndex(Movie.RELEASE_YEAR)));
List<String> genreArray = new ArrayList<>();
while(cursor.moveToNext()){
String genre = cursor.getString(cursor.getColumnIndex(Movie.GENRE));
genreArray.add(genre);
}
movie.setGenre(Collections.singletonList(String.valueOf(genreArray)));
// Adding a movie to the list
moviesList.add(movie);
} while (cursor.moveToNext());
}
Log.d(TAG, "The movies list from sqlite: " + moviesList);
cursor.close();
db.close();
return moviesList;
}
I believe your issue is with :-
for (int k = 0; k < genresArray.length(); k++) {
insertValues.put(Movie.GENRE, genres.get(k));
}
That will result in just the last value in the loop being inserted as the key/column name (first parameter of the put) does not change (and probably can't as you only have the one column).
You could use :-
StringBuilder sb = new StringBuilder();
for (int k = 0; k < genresArray.length(); k++) {
if (k > 0) {
sb.append(",");
}
sb.append(genres.get(k));
}
insertValues.put(Movie.GENRE, sb.toString());
Note the above code is in-principle code. It has not been tested or run and may therefore contains errors.
This would insert all the data as a CSV into the GENRE column.
BUT that is not a very good way as far as utilising databases. It would be far better if the Genre's were a separate table and probably that a mapping table were used (but that should be another question).
This is going to cause you issues as well :-
if (cursor.moveToFirst()) {
do {
Movie movie = new Movie();
movie.setMovieId(Integer.parseInt(cursor.getString(cursor.getColumnIndex(Movie.ID))));
movie.setTitle(cursor.getString(cursor.getColumnIndex(Movie.TITLE)));
movie.setImageUrl(cursor.getString(cursor.getColumnIndex(Movie.IMAGE_URL)));
movie.setRating(cursor.getDouble(cursor.getColumnIndex(Movie.RATING)));
movie.setReleaseYear(cursor.getInt(cursor.getColumnIndex(Movie.RELEASE_YEAR)));
List<String> genreArray = new ArrayList<>();
while(cursor.moveToNext()){
String genre = cursor.getString(cursor.getColumnIndex(Movie.GENRE));
genreArray.add(genre);
}
movie.setGenre(Collections.singletonList(String.valueOf(genreArray)));
// Adding a movie to the list
moviesList.add(movie);
} while (cursor.moveToNext());
That is you move to the first row of the Cursor, extract some data MoveieId,Title ... ReleaseYear.
Then
a) if there any other rows you move to the next (which would be for a different Movie) and the next until you finally reached the last row adding elements to the genreArray.
or
b) If there is only the one row in the Cursor genreArray is empty.
You then add the 1 and only movie to the movieList and return.
1 move (row) in the Cursor will exist per movie and there is only the 1 GENRE column per movie. You have to extract the data in that column and then split the data into the genreArray without moving (see the previous fix that will create a CSV (note that would be messed up if the data contained commas)).
IF you used the previous fix and store the multiple genres as a CSV, then you could use :-
if (cursor.moveToFirst()) {
do {
Movie movie = new Movie();
movie.setMovieId(Integer.parseInt(cursor.getString(cursor.getColumnIndex(Movie.ID))));
movie.setTitle(cursor.getString(cursor.getColumnIndex(Movie.TITLE)));
movie.setImageUrl(cursor.getString(cursor.getColumnIndex(Movie.IMAGE_URL)));
movie.setRating(cursor.getDouble(cursor.getColumnIndex(Movie.RATING)));
movie.setReleaseYear(cursor.getInt(cursor.getColumnIndex(Movie.RELEASE_YEAR)));
List<String> genreArray = new List<>(Arrays.asList((cursor.getString(cursor.getColumnIndex(Movie.GENRE))).split(",",0)));
movie.setGenre(Collections.singletonList(String.valueOf(genreArray)));
// Adding a movie to the list
moviesList.add(movie);
} while (cursor.moveToNext());
Note the above code is in-principle code. It has not been tested or run and may therefore contains errors.

Vaadin How to handle IndexedContainer.Property.ValueChangeEvent for specific Item or PropertyId?

I want to handle Property.ValueChangeEvent in my IndexedContainer, when the value is changed in specific row. I expected the "event" passed as argument will supply the information what has been changed.
IndexedContainer container = new IndexedContainer();
.
. add container properties, insert items ...
.
container.addValueChangeListener(this)
.
.
public void valueChange(ValueChangeEvent event) {
// How to get itemId and another properties from the same item?
event.getProperty(); // This returns only whole container
}
The getProperty() function doesn't give me the information, from which row and column it comes. Thanks for help.JH
Usually a container is used within a component, and is that component that listens for "valueChange".
The way to get the selected item is:
Item itemSelected = component.getContainerDataSource().getItem(component.getValue());
//get for example "id" property
Integer id = (Integer) itemSelected.getItemProperty("id").getValue();
Otherwise, are you just using a container alone? If so can you be more specific? because it's not a "standard" way.
Regards
It's not very efficient, but it works:
container.addValueChangeListener(new ValueChangeListener() {
private static final long serialVersionUID = 1L;
#Override
public void valueChange(ValueChangeEvent event) {
for (Object itemId : container.getItemIds()) {
for (Object propId : container.getContainerPropertyIds()) {
Property<?> p = container.getContainerProperty(itemId, propId);
Object o = ((EventObject) event).getSource();
if (p.equals(o)) {
System.out.println("itemId: " + itemId);
System.out.println("propertyId: " + propId);
return;
}
}
}
}
});
The event is a PropertyValueChangeEvent and the getSource() method returns IndexedContainerProperty.
The problem is that those are private classes.
The IndexedContainerProperty contains the itemId and the propertyId too.

Sorting data held by JTable after insertion

I populate a JTable from a database. The data in JTable is sorted based on the auto-generated primary key in descending order. The table looks like the following.
The data in the table is held by a list which contains a list of objects of a JPA entity - List<Country>.
Since I display data in descending order by countryId (primary key), the list needs to be sorted in descending by countryId after data is inserted and before the fireTableRowsInserted(size, size) method is executed.
After sorting this list in descending order by countryId, the table looks wonky as follows.
Values through the given text fields are submitted after validation, when the given add button is pressed.
The row is added to database and to the list and the list is also sorted as mentioned but the data in the table are not shown as they are in the list.
See the last two rows in the preceding snap shot. The actual row which is created is not displayed. The last row is duplicated instead which is different from the underlying list where the new object is added and the list sorted too.
My AbstractTableModel is as follows.
package admin.model;
import entity.Country;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.swing.table.AbstractTableModel;
public final class CountryAbstractTableModel extends AbstractTableModel
{
private List<Country> countries;
private List<String> columnNames;
public CountryAbstractTableModel(List<Country> countries)
{
this.countries = countries;
columnNames=getTableColumnNames();
}
//This is the method which sorts the list after adding a JPA entity object.
public void add(Country country)
{
int size = countries.size();
countries.add(country);
Comparator<Country> comparator = new Comparator<Country>()
{
#Override
public int compare(Country o1, Country o2)
{
return o2.getCountryId().compareTo(o1.getCountryId());
}
};
Collections.sort(countries, comparator);
fireTableRowsInserted(size, size);
}
#Override
public String getColumnName(int column)
{
return columnNames.get(column);
}
#Override
public Class<?> getColumnClass(int columnIndex)
{
switch (columnIndex)
{
case 0:
return String.class;
case 1:
return String.class;
case 2:
return String.class;
case 3:
return String.class;
default:
return String.class;
}
}
private List<String> getTableColumnNames()
{
List<String> names = new ArrayList<>();
names.add("Index");
names.add("Id");
names.add("Country Name");
names.add("Country Code");
return names;
}
#Override
public int getRowCount()
{
return countries.size();
}
#Override
public int getColumnCount()
{
return 4;
}
public void remove(List<Long>list)
{
Iterator<Country> iterator = countries.iterator();
while(iterator.hasNext())
{
Country country = iterator.next();
Iterator<Long> it = list.iterator();
while(it.hasNext())
{
if(country.getCountryId().equals(it.next()))
{
iterator.remove();
int index = countries.indexOf(country);
fireTableRowsDeleted(index, index);
break;
}
}
}
}
#Override
public Object getValueAt(int rowIndex, int columnIndex)
{
Country country = countries.get(rowIndex);
switch (columnIndex)
{
case 0:
return rowIndex+1;
case 1:
return country.getCountryId();
case 2:
return country.getCountryName();
case 3:
return country.getCountryCode();
}
return "";
}
#Override
public void setValueAt(Object value, int rowIndex, int columnIndex)
{
Country country = countries.get(rowIndex);
if(value instanceof String)
{
String stringValue = value.toString();
switch(columnIndex)
{
case 2:
country.setCountryName(stringValue);
break;
case 3:
country.setCountryCode(stringValue);
break;
}
}
fireTableCellUpdated(rowIndex, columnIndex);
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return columnIndex>1?true:false;
}
}
If I remove the given Comparator as in the add() method in the code snippet (i.e sorting is not done) then, the table is updated as it should be with the newly created row at the end of the table (which should be on top of the table. Hence sorting is necessary).
Why does this happen when the underlying list is sorted? (Again it doesn't happen, when the list is not sorted, it is left untouched.)
This is happening because you are telling the model that an element has been added at position 'size' (ie the last position in the list) but because you are sorting the list it is actually in the model at position 0 (in this example).
Probably the simplest way to fix this is to call fireTableDataChanged() and not worry about the index - I think your table would have to be pretty big for this to cause performance problems. Otherwise you could use list.indexOf() to find out where your new element ended up after sorting and call fireTableRowsInserted() with the correct indices.

Filling a JTable row with more than one list data java

I'm filling a JTable dynamicaly from a list of 'steps'. Step of sort 'opening'defares from others steps, by having the posibility of containing more than one action, while all other steps type contain one action only. Therefor I would like -when reaching a step of 'open' type- to add all its actions both in same table raw like this:
step name Action on Object value result
opening full open door1 30 ... delete replace
haulgh open door2 40 delete replace
wholeOpen door1 10 delete replace
comparison compare state1 .. ...
where 'delete' and 'replace' are extended of JButton.
the code I've written is the following:
public DefaultTableModel ListToTableModel(Object[] l, String tableName) throws Exception {
Vector<String> columnNames = null;
Vector<Vector<Object>> data = new Vector<>();
columnNames = new Vector<>(Arrays.asList(" Step name: "," Action: "," On object: "," Action value: "," Action result: "," "," "));
for (int i = 0; i < l.length; i++) {
for(int j=0;j<((Step) l[i]).action.size();j++){
Vector<Object> vector = new Vector<>();
String string="";int k=0;
if(((Step) l[i]).Name=="opening"){
vector.add(((Step) l[i]).Name);
for(k=0;k<((Step) l[i]).action.size();k++){
string+=((Step) l[i]).action.get(k)+"\n";
}
vector.add(string);
string="";
for( k=0;k<((Step) l[i]).onObject.size();k++){
string+=((Step) l[i]).onObject.get(k)+"\n";
}
vector.add(string);
string="";
for(k=0;k<((Step) l[i]).value.size();k++){
string+=((Step) l[i]).value.get(k)+"\n";
}
vector.add(string);
string="";
for( k=0;k<((Step) l[i]).result.size();k++){
string+=((Step) l[i]).result.get(k)+"\n";
}
vector.add(string);
break;
}
else
{
vector.add(((Step) l[i]).Name);
vector.add(((Step) l[i]).action.get(j));
vector.add(((Step) l[i]).onObject.get(j));
vector.add(((Step) l[i]).value.get(j));
vector.add(((Step) l[i]).result.get(j));
vector.add("delete");
vector.add("Replace");
}
data.add(vector);
}
}
return new DefaultTableModel(data, columnNames) {
#Override
public boolean isCellEditable(int rowIndex, int mColIndex) {
return true;
}
};
}
and Step class is:
public class Step {
public String Name=null;
public List<String> action=null;
public List<String> onObject=null;
public List<String> value=null;
public List<String> result=null;
public Step(String n){
Name=n;
action=new ArrayList<String>();
onObject=new ArrayList<String>();
value=new ArrayList<String>();
result=new ArrayList<String>();
}
public void add(String act,String onobject ,String val,String res){
action.add(act);
onObject.add(onobject);
value.add(val);
result.add(res);
}
but the only result I get when calling:
Step step=new Step("opening");
step.add("full open","door1","30.0","door_1");
step.add("haulgh open","door2","40.0","door_2");
step.add("whole open","door3","40.0","door_3");
Controller.getStepList().add(step);
step=new Step("comparison");
step.add("compare","state1","--","state_1");
Controller.getStepList().add(step);
is:
step name Action on Object value result
comparison compare state1 -- state_1
Does anyOne have an idea whats wrong with it?
Thank in advance!
ok, got it.
When reaching an "oppening" step type, index j from the external loop points to the step's action list. but in the internal loop k runs over the action list by itself and ends by adding the string to the raw. The prob is that J is continuing to run over the right same action list, in next loops..so in the last final loop the string contains just nothing..
My way of resolving the prob is by changing the Step class stracture to contain the whole step data in one uniq string, and then I just fill the 'oppening' step data in the same way as all other steps type with no inner loop. I know it could be a better way for resolving it, but changing the Step class stracture is needed for me for some other reasons, so I take advantage of it..

JTable row color change based on a column value- on pop up click

My jTable is loaded with data and this is where I call my Pop up functionality on jTable.
jTable.addMouseListener(new TablePopupListener(jTable));
displayTable();
So basically, if I right-click a row, a popup(credit check) comes up and if I click it is setting a value to the last cell in that row. Now, based on this column cell value I have to define the color of a row. Let's say if the cell value fails then turn the row to red else to green. I have tried customCellRenderer and defined my condition but there is no change in row color. The custom cell renderer worked great for a button functionality that I had to write, though. The below code uses prepare cellRenderer which I felt is easy but I don't see any change in row color.
I am missing some connection, plz provide me help.
Thanks in advance.
class TablePopupListener extends MouseAdapter implements ActionListener {
JPopupMenu popup;
JTable table;
int[] selRows;
TableModel model;
ArrayList rowValueList = new ArrayList();
JMenuItem creditCheck = new JMenuItem("Credit Check");
public TablePopupListener(JTable jTable) {
this.table = jTable;
model = table.getModel();
popup = new JPopupMenu();
JMenuItem creditCheck = new JMenuItem("Credit Check");
creditCheck.addActionListener(this);
popup.add(creditCheck);
}
public void mousePressed(MouseEvent me) {
firePopup(me);
}
public void mouseReleased(MouseEvent me) {
firePopup(me);
}
public void firePopup(MouseEvent me) {
/*
* The popup menu will be shown only if there is a row selection in the
* table
*/
// popup.show(table, me.getX(), me.getY());
if (me.isPopupTrigger() && table.getModel().getRowCount() != 0
&& table.getSelectedRow() != -1) {
// popup.show(table,me.getX(),me.getY());
if (me.isPopupTrigger()) {
JTable source = (JTable) me.getSource();
int row = source.rowAtPoint(me.getPoint());
int column = source.columnAtPoint(me.getPoint());
if (!source.isRowSelected(row))
source.changeSelection(row, column, false, false);
popup.show(table, me.getX(), me.getY());
}
}
}
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Credit Check")) {
System.out.println("you have clicked creditCheckpopup");
selRows = table.getSelectedRows();
if (selRows.length > 0) {
for (int i = 0; i < selRows.length; i++) {
// get Table data
for (int j = 1; j < (table.getColumnCount()) - 1; j++) {
rowValueList.add(model.getValueAt(selRows[i], j));
}
System.out.println("Selection : " + rowValueList);
}
} else {
System.out.println("you have clicked something idiot");
}
int result = new COpxDeal(rowValueList).CheckCredit();
if (result == 1)
rowValueList.add("pass");
else
rowValueList.add("fail");
String aValue = (String) rowValueList.get(14);
for (int i = 0; i < selRows.length; i++) {
model.setValueAt(aValue, selRows[i], 15);
}
// inserted comment (Kleopatra): where are we? that's outside of the TablePopup?
// okay, nothing like copying the code into an IDE and let that do the formatting, silly me ;-)
// this is indeed _inside_ the popup, that is the table is recreated
table = new JTable(model) {
public Component prepareRenderer(TableCellRenderer renderer,
int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
JComponent jc = (JComponent) c;
// if (!isRowSelected(row)){
// c.setBackground(getBackground());
// System.out.println(isRowSelected(row));
// }
int modelRow = convertRowIndexToModel(row);
String strTestValue = "fail";
String strTblValue = (String) getModel().getValueAt(
modelRow, 15);
System.out.println("result :" + strTblValue);
if (strTblValue == null || strTblValue.equals(""))
System.out.println("there is nothing in strTblValue");
else if (strTestValue.equals(strTblValue)) {
jc.setBackground(Color.RED);
} else {
jc.setBackground(Color.green);
}
return c;
}
};
}
}
}
after some formatting (believe me, it's important for code to be readable ;-) seems like you instantiate a new table inside your popupMenu and only that table has the custom renderer. Which you can do, but doesn't have any effect on the your real table.
Move the prepareRenderer into your real table (the one you pass into the popup as parameter) and you should see the coloring. Beware: due to a bug in DefaultTableCellRenderer, you have to set the color always, that is
if (nothingToDo) {
setBackground(normal)
} else if ... {
setBackground(one)
} else {
setBackground(other)
}
Edit: trying to explain the changes in code structure, pseudo-code snippets
Current state, that's what you are doing:
JTable table = new JTable();
table.addMouseListener(new TablePopupListener(table));
// keep listener-local reference to table
JTable table = table;
....
// in the listener guts, the reference is replaced
table = new JTable() {
#Override
Component prepareRenderer(...
}
Change to, that's what you should do:
JTable table = new JTable() {
#Override
Component prepareRenderer(...
};
table.addMouseListener(new TablePopupListener(table));
// keep listener-local reference to table
JTable table = table;
// don't replace ever, it's for reading only
edit 2:
- changed the pseudo-code to actually register the listener)
- the code indented below the addMouseListener is mean as an outline of the code inside the TablePopupListener