package tide.editor.linemessages;
import javax.swing.text.TabSet;
import snow.utils.gui.Icons;
import snow.utils.gui.GUIUtils;
import javax.swing.text.TabStop;
import snow.utils.gui.CloseControlPanel;
import snow.texteditor.SimpleDocument;
import snow.utils.DateUtils;
import snow.sortabletable.*;
import javax.swing.table.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Insets;
import java.awt.event.*;
import tide.compiler.CompilerOutputPanel;
import tide.sources.FileItem;
import tide.editor.MainEditorFrame;
/** Displays all the messages. (singleton added in the output tabs)
* TODO: store column sizes
*/
public final class MessagesTable extends JPanel
{
final private List<LineMessage> messages = new ArrayList<LineMessage>();
final private static String[] COLUMN_NAMES = {"line", "message", "priority", "tool", "cat", "age", "relevance"};
final private static int[] COLUMN_PREFERED_SIZES = {20, 40, 2, 4, 5, 4, 4};
final private MessagesTableModel messagesTableModel;
final private SortableTableModel sortableTableModel;
final JTable table;
final JSplitPane splitPane;
final CompilerOutputPanel tracePanel = new CompilerOutputPanel();
final private MultiSearchPanel asp;
private static MessagesTable instance;
public static synchronized MessagesTable getInstance()
{
if(instance == null) { instance = new MessagesTable(); }
return instance;
}
private MessagesTable()
{
super(new BorderLayout(0,0));
setBorder(null);
messagesTableModel = new MessagesTableModel();
sortableTableModel = new SortableTableModel(messagesTableModel);
table = new JTable(sortableTableModel);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
sortableTableModel.installGUI(table);
JScrollPane sp = new JScrollPane(table);
sp.setBorder(null);
sp.getViewport().setBorder(null);
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, sp, tracePanel);
splitPane.setOneTouchExpandable(true);
EventQueue.invokeLater(new Runnable() { public void run() {
splitPane.setDividerLocation(230); //.2 does NOT work. (maybe must wait until shown...)
}});
add(splitPane, BorderLayout.CENTER);
splitPane.setBorder(null);
asp = new MultiSearchPanel("Filter: ", null, sortableTableModel);
JPanel northPan = new JPanel();
northPan.setLayout(new BoxLayout(northPan, BoxLayout.X_AXIS));
northPan.add(GUIUtils.wrapLeft(asp,0));
add(northPan, BorderLayout.NORTH);
northPan.add(Box.createHorizontalStrut(10));
northPan.add(GUIUtils.createHelpLabel("Hint: use CTRL+up/down/1/9 to navigate messages."));
northPan.add(Box.createHorizontalStrut(10));
final JButton opts = new JButton("Options");
northPan.add(opts);
opts.setMargin(new Insets(0,2,0,2));
opts.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
JPopupMenu popup = new JPopupMenu();
JMenuItem st =new JMenuItem("View statistics", Icons.sharedStat);
popup.add(st);
st.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
showStats(false);
} });
JMenuItem st2 =new JMenuItem("View statistics of relevant messages", Icons.sharedStat);
popup.add(st2);
st2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
showStats(true);
} });
JMenuItem cd =new JMenuItem("View defined categories");
popup.addSeparator();
popup.add(cd);
cd.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
CategoriesTable.showTable(MainEditorFrame.instance);
} });
JMenuItem cat =new JMenuItem("Remove messages defined as irrelevant", Icons.sharedCross);
popup.addSeparator();
popup.add(cat);
cat.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
LineMessagesManager.getInstance().removeIrrelevantMessages();
refresh();
} });
popup.show(opts, 0, opts.getHeight());
} });
// show the message
table.getSelectionModel().addListSelectionListener(
new ListSelectionListener()
{
public void valueChanged(ListSelectionEvent lse)
{
if(lse.getValueIsAdjusting()) return;
int posView = table.getSelectedRow() ;
if(posView==-1)
{
tracePanel.doc.setText("no selection");
return;
}
int posModel = sortableTableModel.getIndexInUnsortedFromTablePos(posView);
if(posModel!=-1)
{
LineMessage lm = messages.get(posModel);
tracePanel.doc.setText(lm.getMessageForOutputPanel());
}
}
});
table.addMouseListener(new MouseAdapter()
{
@Override public void mousePressed(MouseEvent me)
{
if(me.getClickCount()==2)
{
//select the line on double click
int pos = sortableTableModel.getIndexInUnsortedFromTablePos( table.getSelectedRow() );
if(pos<0) return;
final LineMessage lm = messagesTableModel.getMessageAt(pos);
jumpToSourceForMessage(lm);
return;
}
if(me.isPopupTrigger())
{
showTablePopup(me);
return;
}
}
@Override public void mouseReleased(MouseEvent me)
{
if(me.isPopupTrigger())
{
showTablePopup(me);
return;
}
}
});
}
/** Also called from editorpanel.
*/
public void jumpToSourceForMessage(LineMessage lm)
{
FileItem fi = MainEditorFrame.instance.getFileItem(lm.getSourceJavaName(), null);
MainEditorFrame.instance.setSourceOrItemToEditOrView(fi, true);
int line = lm.getLine();
if(line<1) line=1; // enforce showing
MainEditorFrame.instance.editorPanel.selectLinePart(line-1, lm.getColumn());
}
private void showTablePopup(MouseEvent me)
{
JPopupMenu popup = new JPopupMenu();
/*
if(table.getSelectedRowCount()==1)
{
int pos = sortableTableModel.getIndexInUnsortedFromTablePos( table.getSelectedRow() );
if(pos<0) return;
final LineMessage lm = messagesTableModel.getMessageAt(pos);
}*/
if(table.getSelectedRowCount()<table.getRowCount() && table.getRowCount()>1)
{
JMenuItem sal =new JMenuItem("select all "+table.getRowCount()+" messages");
popup.add(sal);
sal.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
table.selectAll();
} });
}
if(table.getSelectedRowCount()>0)
{
final List<LineMessage> sel = new ArrayList<LineMessage>();
for(int r: table.getSelectedRows())
{
int pos = sortableTableModel.getIndexInUnsortedFromTablePos( r );
sel.add(messagesTableModel.getMessageAt(pos));
}
JMenuItem remove =new JMenuItem("remove "+sel.size()+" messages", Icons.sharedCross);
popup.addSeparator();
popup.add(remove);
remove.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
messagesTableModel.remove(sel);
if(sel.size()>0)
{
refresh();
}
} });
popup.addSeparator();
JMenuItem defR =new JMenuItem("Define categories as relevant");
popup.add(defR);
defR.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
for(LineMessage lm : sel)
{
LineMessagesManager.getInstance().getRelevantCategories().add(lm.getCategory());
}
messagesTableModel.update();
} });
JMenuItem defIR =new JMenuItem("Define categories as irrelevant");
popup.add(defIR);
defIR.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
for(LineMessage lm : sel)
{
LineMessagesManager.getInstance().getIrrelevantCategories().add(lm.getCategory());
}
messagesTableModel.update();
} });
popup.addSeparator();
JMenuItem defS =new JMenuItem("Remove relevance definition", Icons.sharedCross);
popup.add(defS);
defS.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) {
for(LineMessage lm : sel)
{
LineMessagesManager.getInstance().getIrrelevantCategories().remove(lm.getCategory());
LineMessagesManager.getInstance().getRelevantCategories().remove(lm.getCategory());
}
messagesTableModel.update();
} });
}
popup.show(table, me.getX(), me.getY());
}
// called in the EDT !
public void selectMessage(LineMessage lm)
{
int pos = messages.indexOf(lm);
if(pos==-1) return;
int tp = sortableTableModel.getIndexInFoundFromBasicIndex(pos);
if(tp>=0)
{
table.getSelectionModel().setSelectionInterval(tp,tp);
table.scrollRectToVisible( table.getCellRect(tp,0,true) );
}
}
/** Must be called in the EDT !
* directly calls the linepanel to refresh.
* called from LineMessManager
*
* 1) reread all messages from manager
* 2) update the count
* 3) call the linepanel view refresh
*/
public void refresh()
{
this.messagesTableModel.refresh();
MainEditorFrame.instance.outputPanels.setMessagesCount(messages.size());
MainEditorFrame.instance.editorPanel.getLinePanel().refreshMessages();
}
public void setFilter(String filter)
{
asp.setQueries(Query.simpleSearchQuery(filter));
}
public void requestFocusForSearch()
{
asp.getFirstTextField().requestFocus();
}
//@net.jcip.annotations.ThreadSafe
public void setShowLatest()
{
EventQueue.invokeLater(new Runnable() { public void run() {
sortableTableModel.setSortedColumnAndOrder(5, false);
sortableTableModel.sort(5, false);
}});
}
private void showStats(boolean relevantOnly)
{
JDialog d = new JDialog(MainEditorFrame.instance, "Messages statistics", false);
SimpleDocument doc =new SimpleDocument();
JTextPane tp = new JTextPane(doc);
tp.setEditable(false);
d.add(new JScrollPane(tp), BorderLayout.CENTER);
CloseControlPanel ccpd = new CloseControlPanel(d, false, true, "Close");
d.add(ccpd, BorderLayout.SOUTH);
//d.pack();
d.setSize(720, 600);
d.setLocationRelativeTo(MainEditorFrame.instance);
d.setVisible(true);
// fill the stats
doc.appendLine(""+messages.size()+" messages");
//NO influence here ! doc.setTabsForDoc(20, 10); // todo: tabright for numbers TabStop.
// m(tool, m(cat, count))
Map<String, HashMap<String, Integer>> categoriesCount = new HashMap<String, HashMap<String, Integer>>();
int relm = 0;
for(LineMessage mi : messages)
{
String ci = mi.getCategory();
String ti = mi.getMessageOriginator();
if(LineMessagesManager.getInstance().getRelevantCategories().contains(ci))
{
relm++;
}
else if(relevantOnly)
{
continue;
}
if(!categoriesCount.containsKey(ti))
{
categoriesCount.put(ti, new HashMap<String, Integer>());
}
Map<String, Integer> catmap = categoriesCount.get(ti);
if(!catmap.containsKey(ci))
{
catmap.put(ci,1);
}
else
{
catmap.put(ci,1 + catmap.get(ci));
}
}
doc.appendLine(""+relm+" relevant messages");
TreeSet<String> toolNames = new TreeSet<String>( categoriesCount.keySet() );
for(String ti : toolNames)
{
Map<String, Integer> catmap = categoriesCount.get(ti);
int tot = 0;
for(int ni : catmap.values())
{
tot+=ni;
}
doc.appendLine("\n"+ti+": "+tot+" message"+(tot==1?"":"s"));
TreeSet<String> catNames = new TreeSet<String>(catmap.keySet());
for(String ci : catNames)
{
int ni = catmap.get(ci);
doc.append("\t"+ni+" \t"+ci); //+" message"+(ni==1?"":"s"));
if(LineMessagesManager.getInstance().getRelevantCategories().contains(ci))
{
doc.appendError(" [Relevant]");
}
else if(LineMessagesManager.getInstance().getIrrelevantCategories().contains(ci))
{
doc.append(" [Irrelevant]");
}
/*else
{
}*/
doc.append("\r\n");
}
}
TabStop[] tabs = new TabStop[2];
tabs[0] = new TabStop(60f, TabStop.ALIGN_RIGHT, TabStop.LEAD_NONE );
tabs[1] = new TabStop(75f, TabStop.ALIGN_LEFT, TabStop.LEAD_NONE );
TabSet tabSet = new TabSet(tabs);
doc.setTabsForDoc(tabSet);
tp.setCaretPosition(0);
}
class MessagesTableModel extends FineGrainTableModel
{
/*public MessagesTableModel()
{
//no: refresh();
}*/
public void refresh()
{
synchronized(LineMessagesManager.getInstance())
{
messages.clear();
for(String jn : LineMessagesManager.getInstance().messagesForFile.keySet())
{
messages.addAll( LineMessagesManager.getInstance().messagesForFile.get(jn) );
}
}
update();
}
public int getRowCount() { return messages.size(); }
public int getColumnCount() { return COLUMN_NAMES.length; }
public LineMessage getMessageAt(int row)
{
return messages.get(row);
}
public void remove(List<LineMessage> mess)
{
fireTableModelWillChange();
for(LineMessage lm: mess)
{
LineMessagesManager.getInstance().remove(lm);
}
messages.removeAll(mess);
fireTableDataChanged();
fireTableModelHasChanged();
}
public Object getValueAt(int row, int col)
{
if(row<0) return "bad row:"+row;
if(row>=messages.size()) return "bad row:"+row;
LineMessage pi = messages.get(row);
if(col==0) return pi.getSourceLineForTableColumn();
if(col==1) return pi.getMessageForTableColumn();
if(col==2) return pi.getPriority();
if(col==3) return pi.getMessageOriginator();
if(col==4) return pi.getCategory();
if(col==5) return DateUtils.formatTimeDifference(System.currentTimeMillis()-pi.created);
if(col==6)
{
if(LineMessagesManager.getInstance().getRelevantCategories().contains(pi.getCategory())) return "Relevant";
if(LineMessagesManager.getInstance().getIrrelevantCategories().contains(pi.getCategory())) return "Irrelevant";
return "?";
}
return "?";
}
public void update()
{
fireTableDataChanged();
fireTableModelHasChanged();
}
@Override
public String getColumnName(int column)
{
if(column>=0 && column<COLUMN_NAMES.length) return COLUMN_NAMES[column];
return "??";
}
@Override
public int getPreferredColumnWidth(int column)
{
if(column>=0 && column<COLUMN_PREFERED_SIZES.length) return COLUMN_PREFERED_SIZES[column];
return -1;
}
@Override
public int compareForColumnSort(int pos1, int pos2, int col)
{
synchronized(LineMessagesManager.getInstance()) // [Oct2007]: missin was causing NPEs
{
if(pos1>=messages.size() || pos2>=messages.size()) return 0; // NEEDED, maybe caused by a bad sync problem
if(col==0)
{
LineMessage m1 = messages.get(pos1);
LineMessage m2 = messages.get(pos2);
int comp = m1.getSourceJavaName().compareTo(m2.getSourceJavaName());
if(comp!=0) return comp;
return super.compareDoubles(m1.getLine(), m2.getLine());
}
else if(col==5)
{
LineMessage m1 = messages.get(pos1);
LineMessage m2 = messages.get(pos2);
if(m1==null || m2==null) return 0; // [Oct2007]
return Long.valueOf(m1.created).compareTo(m2.created);
}
else
{
return super.compareForColumnSort(pos1,pos2,col);
}
}
}
}
}
|