package org.yamcs.ui.archivebrowser; import org.yamcs.protobuf.Yamcs.ArchiveRecord; import org.yamcs.protobuf.Yamcs.NamedObjectId; import org.yamcs.ui.PrefsObject; import org.yamcs.ui.UiColors; import org.yamcs.ui.archivebrowser.ArchivePanel.IndexChunkSpec; import javax.swing.*; import javax.swing.border.Border; import java.awt.*; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.*; import java.util.List; import java.util.prefs.Preferences; /** * Represents a collection of IndexLine shown vertically * @author nm * */ public class IndexBox extends Box implements MouseListener { private static final long serialVersionUID = 1L; DataView dataView; JLabel popupLabelItem; JSeparator popupLabelSeparator; JPopupMenu packetPopup; JMenuItem removePacketMenuItem, removeExceptPacketMenuItem, removePayloadMenuItem, changeColorMenuItem, copyOpsnameMenuItem; IndexLineSpec selectedPacket; static final int tmRowHeight = 20; HashMap<String,IndexLineSpec> allPackets; HashMap<String,ArrayList<IndexLineSpec>> groups; HashMap<String,TreeSet<IndexChunkSpec>> tmData; private ZoomSpec zoom; private String name; /** * because the histogram contains regular splits each 3600 seconds, merge here the records * that are close enough to each other. *-1 means no merging */ long mergeTime=-1; Preferences prefs; private JPanel topPanel; private JPanel centerPanel; private List<IndexLine> indexLines = new ArrayList<IndexLine>(); private JLabel titleLabel; IndexBox(DataView dataView, String name) { super(BoxLayout.Y_AXIS); topPanel = new JPanel(new GridBagLayout()); // In panel, so that border can fill width Border outsideBorder = BorderFactory.createMatteBorder(0, 0, 1, 0, UiColors.BORDER_COLOR); Border insideBorder = BorderFactory.createEmptyBorder(10, 0, 2, 0); topPanel.setBorder(BorderFactory.createCompoundBorder(outsideBorder, insideBorder)); topPanel.setBackground(Color.WHITE); GridBagConstraints cons = new GridBagConstraints(); cons.fill = GridBagConstraints.HORIZONTAL; cons.weightx = 1; cons.gridx = 0; titleLabel = new JLabel(name); titleLabel.setBackground(Color.red); titleLabel.setMaximumSize(new Dimension(titleLabel.getMaximumSize().width, titleLabel.getPreferredSize().height)); titleLabel.setForeground(new Color(51, 51, 51)); topPanel.setMaximumSize(new Dimension(topPanel.getMaximumSize().width, titleLabel.getPreferredSize().height+13)); topPanel.add(titleLabel, cons); topPanel.setAlignmentX(Component.LEFT_ALIGNMENT); add(topPanel); centerPanel = new JPanel(); centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS)); centerPanel.setBorder(BorderFactory.createEmptyBorder()); centerPanel.setOpaque(false); centerPanel.setAlignmentX(Component.LEFT_ALIGNMENT); add(centerPanel); addMouseListener(this); this.dataView=dataView; this.name=name; allPackets = new HashMap<String,IndexLineSpec>(); groups = new HashMap<String,ArrayList<IndexLineSpec>>(); tmData = new HashMap<String,TreeSet<IndexChunkSpec>>(); prefs = Preferences.userNodeForPackage(IndexBox.class).node(name); } void removeIndexLines() { centerPanel.removeAll(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); int panelWidth = getWidth(); int panelHeight = getHeight(); g2d.setPaint(new GradientPaint(0, topPanel.getHeight(), new Color(251,251,251), 0, panelHeight, Color.WHITE)); g2d.fillRect(0, topPanel.getHeight(), panelWidth, panelHeight-topPanel.getHeight()); } protected void buildPopup() { if (groups.isEmpty()) { packetPopup = null; } else { packetPopup = new JPopupMenu(); popupLabelItem = new JLabel(); popupLabelItem.setEnabled(false); Box hbox = Box.createHorizontalBox(); hbox.add(Box.createHorizontalGlue()); hbox.add(popupLabelItem); hbox.add(Box.createHorizontalGlue()); packetPopup.insert(hbox, 0); popupLabelSeparator = new JSeparator(); packetPopup.add(popupLabelSeparator); JMenu packetmenu = new JMenu("Add Packets"); packetPopup.add(packetmenu); removePacketMenuItem = new JMenuItem(); // text is set when showing the popup menu removePacketMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { removeSelectedPacket(); } }); packetPopup.add(removePacketMenuItem); removeExceptPacketMenuItem = new JMenuItem("Hide Other Packets"); removeExceptPacketMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { removeAllButThisLine(); } }); packetPopup.add(removeExceptPacketMenuItem); removePayloadMenuItem = new JMenuItem(); // text is set when showing the popup menu removePayloadMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { removeGroupLines(); } }); packetPopup.add(removePayloadMenuItem); copyOpsnameMenuItem = new JMenuItem("Copy Ops-Name to Clipboard"); copyOpsnameMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if ( selectedPacket != null ) { StringSelection strsel = new StringSelection(selectedPacket.lineName); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(strsel, strsel); } } }); packetPopup.add(copyOpsnameMenuItem); changeColorMenuItem = new JMenuItem("Change Color"); changeColorMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { selectedPacket.newColor(); dataView.setBusyPointer(); redrawTmPanel(selectedPacket); dataView.setNormalPointer(); } }); packetPopup.add(changeColorMenuItem); //populateMenuItem = new JMenuItem("Populate From Current Channel"); //populateMenuItem.addActionListener(this); //populateMenuItem.setActionCommand("populate-from-current-channel"); //packetPopup.add(populateMenuItem); JMenuItem menuItem; final String[] plkeys = groups.keySet().toArray(new String[0]); Arrays.sort(plkeys); for (final String key:plkeys) { ArrayList<IndexLineSpec> tm = groups.get(key); JMenu submenu = new JMenu(key); packetmenu.add(submenu); final IndexLineSpec[] tmarray = tm.toArray(new IndexLineSpec[0]); Arrays.sort(tmarray); for (final IndexLineSpec pkt:tmarray) { if (dataView.hideResponsePackets && pkt.lineName.contains("_Resp_")) continue; menuItem = new JMenuItem(pkt.toString()); pkt.assocMenuItem = menuItem; if (pkt.enabled) { menuItem.setVisible(false); } menuItem.addActionListener(pkt); submenu.add(menuItem); } // "All Packets" item submenu.addSeparator(); menuItem = new JMenuItem("All Packets"); menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { enableAllPackets(key); } }); submenu.add(menuItem); } } } void updatePrefsVisiblePackets() { ArrayList<String> visiblePackets = new ArrayList<String>(); for ( ArrayList<IndexLineSpec> plvec:groups.values()) { for (IndexLineSpec pkt:plvec) { if (pkt.enabled) { visiblePackets.add(pkt.lineName); } } } PrefsObject.putObject(prefs, "indexLines", visiblePackets); } @Override public void mousePressed(MouseEvent e) { selectedPacket=null; if(e.isPopupTrigger()) { showPopup(e); } } public void doMouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { showPopup(e); } } @Override public void mouseExited(MouseEvent e) {} @Override public void mouseClicked(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseReleased(MouseEvent e) { doMouseReleased(e); } void showPopup(final MouseEvent e) { if (packetPopup != null) { if(selectedPacket!=null) { popupLabelItem.setVisible(true); popupLabelSeparator.setVisible(true); removePayloadMenuItem.setVisible(true); removeExceptPacketMenuItem.setVisible(true); removePacketMenuItem.setVisible(true); copyOpsnameMenuItem.setVisible(true); changeColorMenuItem.setVisible(true); popupLabelItem.setText(selectedPacket.lineName); removePacketMenuItem.setText(String.format("Hide %s Packet", selectedPacket.lineName)); removePayloadMenuItem.setText(String.format("Hide All %s Packets", selectedPacket.grpName)); } else { popupLabelItem.setVisible(false); popupLabelSeparator.setVisible(false); removePayloadMenuItem.setVisible(false); removePacketMenuItem.setVisible(false); removeExceptPacketMenuItem.setVisible(false); copyOpsnameMenuItem.setVisible(false); changeColorMenuItem.setVisible(false); } packetPopup.validate(); packetPopup.show(e.getComponent(), e.getX(), e.getY()); } } void enableAllPackets(String plname) { ArrayList<IndexLineSpec> pltm = groups.get(plname); if (pltm != null) { for (IndexLineSpec pkt:pltm) { if ( pkt.assocMenuItem != null ) { // response packets might be hidden from the popup. // in this case, assocMenuItem is not set. // remove item from popup menu pkt.assocMenuItem.setVisible(false); // create entry in TM display pkt.enabled = true; } } updatePrefsVisiblePackets(); dataView.refreshDisplay(); } } void enableTMPacket(IndexLineSpec pkt) { // remove item from popup menu pkt.assocMenuItem.setVisible(false); // create entry in TM display pkt.enabled = true; dataView.refreshDisplay(); updatePrefsVisiblePackets(); } void removeSelectedPacket() { dataView.setBusyPointer(); selectedPacket.assocMenuItem.setVisible(true); selectedPacket.enabled = false; remove(selectedPacket.assocIndexLine); selectedPacket.assocIndexLine = null; updatePrefsVisiblePackets(); if(getComponents().length==0) { showEmptyLabel("Right click for "+name+" data"); } dataView.refreshDisplay(); dataView.setNormalPointer(); } void removeGroupLines() { ArrayList<IndexLineSpec> pltm = groups.get(selectedPacket.grpName); if (pltm != null) { dataView.setBusyPointer(); for (IndexLineSpec pkt:pltm) { if (pkt.assocMenuItem != null) { pkt.assocMenuItem.setVisible(true); } pkt.enabled = false; if (pkt.assocIndexLine != null) { remove(pkt.assocIndexLine); pkt.assocIndexLine = null; } } if(getComponents().length==0) { showEmptyLabel("Right click for "+name+" data"); } updatePrefsVisiblePackets(); dataView.refreshDisplay(); dataView.setNormalPointer(); } } void removeAllButThisLine() { dataView.setBusyPointer(); for (ArrayList<IndexLineSpec> plvec:groups.values()) { for (IndexLineSpec pkt:plvec) { if (selectedPacket != pkt) { if (pkt.assocMenuItem != null) { pkt.assocMenuItem.setVisible(true); } pkt.enabled = false; if (pkt.assocIndexLine != null) { remove(pkt.assocIndexLine); pkt.assocIndexLine = null; } } } } updatePrefsVisiblePackets(); dataView.refreshDisplay(); dataView.setNormalPointer(); } public String getPacketsStatus() { // this appears in the "packets" status label StringBuffer txt = new StringBuffer(); String tmp; for (String plname:groups.keySet()) { final ArrayList<IndexLineSpec> plvec = groups.get(plname); int count = 0; for (IndexLineSpec pkt:plvec) { if ( pkt.enabled ) ++count; } tmp = plname + " (" + count + "/" + plvec.size() + ")"; if ( txt.length() > 0 ) txt.append(", "); txt.append(tmp); } if (txt.length() == 0) txt.append("(none)"); return txt.toString(); } public void setToZoom(ZoomSpec zoom) { this.zoom=zoom; removeIndexLines(); packetPopup = null; if(groups.isEmpty()) { showEmptyLabel("No "+name+" data loaded"); } else { boolean empty=true; for ( ArrayList<IndexLineSpec> plvec:groups.values()) { for (IndexLineSpec pkt: plvec) { if (pkt.enabled) { empty=false; // create panel that contains the index blocks IndexLine line = new IndexLine(this, pkt); centerPanel.add(line); indexLines.add(line); redrawTmPanel(pkt); } } } if(empty) { showEmptyLabel("Right click for "+name+" data"); } buildPopup(); } } private void showEmptyLabel(String msg) { JLabel nodata=new JLabel(msg); nodata.setFont(new Font(Font.SANS_SERIF, Font.ITALIC, nodata.getFont().getSize())); nodata.setForeground(Color.lightGray); nodata.addMouseListener(this); Box b=Box.createHorizontalBox(); b.setBorder(BorderFactory.createEmptyBorder()); b.add(nodata); b.add(Box.createHorizontalGlue()); b.setMaximumSize(new Dimension(b.getMaximumSize().width, b.getPreferredSize().height)); centerPanel.add(b); } public void receiveArchiveRecords(List<ArchiveRecord> records) { String[] nameparts; synchronized (tmData) { //progressMonitor.setProgress(30); //progressMonitor.setNote("Receiving data"); for (ArchiveRecord r:records) { //debugLog(r.packet+"\t"+r.num+"\t"+new Date(r.first)+"\t"+new Date(r.last)); NamedObjectId id=r.getId(); String grpName=null; String shortName=null; //split the id into group->name if(!id.hasNamespace()) { int idx=id.getName().lastIndexOf("/"); if(idx!=-1) { grpName=id.getName().substring(0, idx+1); shortName=id.getName().substring(idx+1); } } if(grpName==null) { nameparts = id.getName().split("[_\\.]", 2); if(nameparts.length>1) { grpName = nameparts[0]; shortName = nameparts[1].replaceFirst("INST_", "").replaceFirst("Tlm_Pkt_", ""); } else { grpName=""; shortName=id.getName(); } } if (!tmData.containsKey(id.getName())) { tmData.put(id.getName(), new TreeSet<IndexChunkSpec>()); } TreeSet<IndexChunkSpec> al=tmData.get(id.getName()); String info=r.hasInfo()?r.getInfo():null; IndexChunkSpec tnew=new IndexChunkSpec(r.getFirst(), r.getLast(), r.getNum(), info); IndexChunkSpec told=al.floor(tnew); if((told==null) || (mergeTime==-1) || (!told.merge(tnew, mergeTime))) { al.add(tnew); } if (!allPackets.containsKey(id.getName()) ) { IndexLineSpec pkt = new IndexLineSpec(id.getName(), grpName, shortName); allPackets.put(id.getName(), pkt); ArrayList<IndexLineSpec> plvec; if ( (plvec = groups.get(grpName)) == null ) { plvec = new ArrayList<IndexLineSpec>(); groups.put(grpName, plvec); } plvec.add(pkt); } } titleLabel.setText(name+" "+getPacketsStatus()); } } public void startReloading() { allPackets.clear(); groups.clear(); tmData.clear(); } public List<String> getPacketsForSelection(Selection selection) { ArrayList<String> packets = new ArrayList<String>(); for (ArrayList<IndexLineSpec> plvec: groups.values()) { for (IndexLineSpec pkt:plvec) { if ( pkt.enabled ) { packets.add(pkt.lineName); } } } return packets; } public void dataLoadFinished() { Object o=PrefsObject.getObject(prefs, "indexLines"); if(o==null) return; ArrayList<String> visibleLines=(ArrayList<String>)o; for (String linename:visibleLines) { IndexLineSpec pkt = allPackets.get(linename); if (pkt != null) { pkt.enabled = true; } else { ArchivePanel.debugLog("could not enable packet '" + linename + "', removing line from view"); } } } void redrawTmPanel(IndexLineSpec pkt) { IndexLine indexLine = pkt.assocIndexLine; indexLine.setOpaque(false); final int stopx = zoom.getPixels(); final Insets in = indexLine.getInsets(); final int panelw = zoom.getPixels(); JLabel pktlab; Font font = null; int x1, y = 0;//, count = 0; indexLine.removeAll(); //debugLog("redrawTmPanel() "+pkt.name+" mark 1"); // set labels x1 = 10; indexLine.setBackground(Color.RED); do { pktlab = new JLabel(pkt.lineName); pktlab.setForeground(pkt.color); if (font == null) { font = pktlab.getFont(); font = font.deriveFont((float)(font.getSize() - 3)); } pktlab.setFont(font); pktlab.setBounds(x1 + in.left, in.top, pktlab.getPreferredSize().width, pktlab.getPreferredSize().height); indexLine.add(pktlab); if (y == 0) { y = in.top + pktlab.getSize().height; indexLine.setPreferredSize(new Dimension(panelw, y + tmRowHeight + in.bottom)); indexLine.setMinimumSize(indexLine.getPreferredSize()); indexLine.setMaximumSize(indexLine.getPreferredSize()); } x1 += 600; } while (x1 < panelw - pktlab.getSize().width); TreeSet<IndexChunkSpec> ts=tmData.get(pkt.lineName); if(ts!=null) { Timeline tmt=new Timeline(this, pkt, ts,zoom, in.left); tmt.setBounds(in.left,y,stopx, tmRowHeight); indexLine.add(tmt); } // centerPanel.setPreferredSize(new Dimension(panelw, centerPanel.getPreferredSize().height)); // centerPanel.setMaximumSize(centerPanel.getPreferredSize()); indexLine.revalidate(); indexLine.repaint(); // System.out.println("indexLine.preferred size: "+indexLine.getPreferredSize()); //pkt.assocLabel.setToolTipText(pkt.opsname + ", " + count + " Chunks"); } class IndexLineSpec implements Comparable<IndexLineSpec>, ActionListener { String shortName, lineName; String grpName; boolean enabled; Color color; JMenuItem assocMenuItem; JComponent assocLabel; IndexLine assocIndexLine; public IndexLineSpec(String lineName, String grpName, String shortName) { this.lineName = lineName; this.grpName = grpName; this.shortName = shortName; enabled = false; assocMenuItem = null; assocIndexLine = null; assocLabel = null; newColor(); } public void newColor() { color = new Color((float)(Math.random() * 0.4 + 0.2), (float)(Math.random() * 0.4 + 0.2), (float)(Math.random() * 0.4 + 0.2)); } @Override public String toString() { return shortName; } @Override public int compareTo(IndexLineSpec o) { return shortName.compareTo(o.shortName); } @Override public void actionPerformed(ActionEvent ae) { enableTMPacket(this); } } public void setMergeTime(long mt) { this.mergeTime=mt; } }