changeset 5735:119c77c83ede

Initial commit: LogViewer GUI
author Katrin Strassl <k.strassl@gmx.net>
date Fri, 22 Jun 2012 23:14:42 +0200
parents 6a812002a918
children 915952ed97c0
files visualizer/LogViewer/nbproject/suite.properties visualizer/LogViewer/src/META-INF/services/com.oracle.graal.visualizer.editor.CompilationViewerFactory visualizer/LogViewer/src/at/ssw/visualizer/logviewer/Bundle.properties visualizer/LogViewer/src/at/ssw/visualizer/logviewer/LogCompilationViewer.java visualizer/LogViewer/src/at/ssw/visualizer/logviewer/LogCompilationViewerFactory.java visualizer/LogViewer/src/at/ssw/visualizer/logviewer/layer.xml visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/BookmarkDialog.java visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/BookmarkableLogViewer.java visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/ImportLogErrorDialog.java visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/LogScene.java visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/actions/ImportLogAction.java visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/arrow_down.png visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/arrow_up.png visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/bookmark_back.png visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/bookmark_forward.png visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/bookmark_list.png visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/import_log.png visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/loading.gif visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/model/LogTableModel.java visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/model/TableLine.java visualizer/nbproject/project.properties
diffstat 21 files changed, 1405 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/nbproject/suite.properties	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,1 @@
+suite.dir=${basedir}/..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/META-INF/services/com.oracle.graal.visualizer.editor.CompilationViewerFactory	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,1 @@
+at.ssw.visualizer.logviewer.LogCompilationViewerFactory
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/Bundle.properties	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,1 @@
+OpenIDE-Module-Name=LogViewer
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/LogCompilationViewer.java	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+package at.ssw.visualizer.logviewer;
+
+import at.ssw.visualizer.logviewer.scene.LogScene;
+import com.oracle.graal.visualizer.editor.CompilationViewer;
+import com.sun.hotspot.igv.data.InputGraph;
+import java.awt.Component;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+
+class LogCompilationViewer implements CompilationViewer {
+
+    private LogScene scene;
+
+    public LogCompilationViewer() {
+        this.scene = new LogScene();
+    }
+
+    @Override
+    public Lookup getLookup() {
+        return Lookups.fixed();
+    }
+
+    @Override
+    public Component getComponent() {
+        return scene;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/LogCompilationViewerFactory.java	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,24 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package at.ssw.visualizer.logviewer;
+
+import com.oracle.graal.visualizer.editor.CompilationViewer;
+import com.oracle.graal.visualizer.editor.SplitCompilationViewerFactory;
+import com.sun.hotspot.igv.data.InputGraph;
+
+public class LogCompilationViewerFactory extends SplitCompilationViewerFactory {
+
+    @Override
+    public String getName() {
+        return "Log";
+    }
+
+    @Override
+    protected CompilationViewer createViewer(InputGraph graph) {
+        return new LogCompilationViewer();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/layer.xml	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
+<filesystem>
+    <folder name="CompilationViewer">
+        <folder name="Log">
+            <folder name="Actions">
+                <file name="at-ssw-visualizer-logviewer-scene-actions-ImportLogAction.shadow">
+                    <attr name="originalFile" stringvalue="Actions/File/at-ssw-visualizer-logviewer-scene-actions-ImportLogActions.instance"/>
+                </file>
+            </folder>
+        </folder>
+    </folder>
+</filesystem>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/BookmarkDialog.java	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,94 @@
+package at.ssw.visualizer.logviewer.scene;
+
+import at.ssw.visualizer.logviewer.model.LogLine;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.List;
+import javax.swing.*;
+
+/**
+ *
+ * @author Katrin Strassl
+ */
+public class BookmarkDialog extends JDialog {
+    
+    private JList lstBookmarks;
+    private List<LogLine> logLines;
+    
+    public BookmarkDialog(List<LogLine> bookmarks, Window parent) {
+        super(parent, JDialog.ModalityType.APPLICATION_MODAL);
+        
+        this.logLines = bookmarks;
+        
+        DefaultListModel mdlBookmarks = new DefaultListModel();
+        lstBookmarks = new JList(mdlBookmarks);
+        JScrollPane jspBookmarks = new JScrollPane(lstBookmarks);
+        
+        for (LogLine bookmark : bookmarks) {
+            mdlBookmarks.addElement(bookmark.getLineNumber() + ": Method " + bookmark.getMethod() + " - Scope " + bookmark.getScope() + " - " + bookmark.getText());
+        }
+        
+        JButton btnOk = new JButton("Go to");
+        JButton btnCancel = new JButton("Cancel");
+
+        // init listeners
+        lstBookmarks.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent e) {
+                if (e.getClickCount() == 2) {
+                    close();
+                }
+            }
+        });
+        
+        btnOk.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                if (lstBookmarks.isSelectionEmpty())
+                    return;
+                close();
+            }
+        });
+        btnCancel.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                lstBookmarks.clearSelection();
+                close();
+            }
+        });
+        
+        // build layout
+        JPanel pnlButtons = new JPanel();
+        
+        pnlButtons.setLayout(new GridLayout(1, 2));
+        pnlButtons.add(btnOk);
+        pnlButtons.add(btnCancel);
+        
+        this.setLayout(new BorderLayout());
+        this.add(jspBookmarks, BorderLayout.CENTER);
+        this.add(pnlButtons, BorderLayout.SOUTH);
+        
+        this.pack();
+        this.setMinimumSize(new Dimension(600, this.getPreferredSize().height));
+        this.setLocationRelativeTo(parent);
+        this.setResizable(false);
+        this.setVisible(true);
+    }
+    
+    public LogLine getTarget() {
+        if (lstBookmarks.isSelectionEmpty())
+            return null;
+        return logLines.get(lstBookmarks.getSelectedIndex());
+    }
+    
+    private void close() {
+        this.setVisible(false);
+        this.dispose();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/BookmarkableLogViewer.java	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,358 @@
+package at.ssw.visualizer.logviewer.scene;
+
+import at.ssw.visualizer.logviewer.model.LogLine;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextPane;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultHighlighter;
+import javax.swing.text.Highlighter;
+import javax.swing.text.JTextComponent;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author Katrin Strassl
+ */
+public class BookmarkableLogViewer extends JPanel {
+
+    private static final Color BOOKMARK_COLOR = Color.CYAN;
+    
+    private static final Highlighter.HighlightPainter BOOKMARK_PAINTER =
+            new DefaultHighlighter.DefaultHighlightPainter(BOOKMARK_COLOR);
+    
+    private JTextPane txpText;
+    private JTextPane txpLines;
+    private Highlighter textHighlighter;
+    private Highlighter lineHighlighter;
+    private FontMetrics fm;
+    private Map<Integer, Bookmark> bookmarks = new TreeMap<>();
+    private List<LogLine> logLines = new ArrayList<>();
+
+    public BookmarkableLogViewer() {
+        init();
+    }
+
+    private void init() {
+        txpText = new JTextPane();
+        txpLines = new JTextPane();
+
+        // needed for correct layouting
+        Insets ins = txpLines.getInsets();
+        txpLines.setMargin(new Insets(ins.top + 1, ins.left, ins.bottom, ins.right));
+
+        textHighlighter = new BookmarkHighlighter();
+        lineHighlighter = new BookmarkHighlighter();
+
+        txpText.setHighlighter(textHighlighter);
+        //txpText.setMinimumSize(new Dimension(100, 100));
+        txpText.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
+        txpText.setEditable(false);
+
+        txpLines.setHighlighter(lineHighlighter);
+        txpLines.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
+        txpLines.setBackground(Color.LIGHT_GRAY);
+        txpLines.setEnabled(false);
+        txpLines.setForeground(Color.BLACK);
+        txpLines.addMouseListener(mouseInputListener);
+
+        fm = txpText.getFontMetrics(txpText.getFont());
+
+        JPanel pnlBookmarks = new JPanel();
+        pnlBookmarks.setLayout(new BorderLayout());
+        pnlBookmarks.add(txpText, BorderLayout.CENTER);
+
+        JScrollPane jspBookmarks = new JScrollPane(pnlBookmarks);
+        jspBookmarks.setRowHeaderView(txpLines);
+        jspBookmarks.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+        jspBookmarks.getVerticalScrollBar().setUnitIncrement(15);
+
+        this.setLayout(new BorderLayout());
+        this.add(jspBookmarks, BorderLayout.CENTER);
+    }
+   
+    public int getCurrentlyDisplayedTopLine() {
+        Rectangle view = txpText.getVisibleRect();
+        return getAbsoluteLine((int) Math.ceil(view.y/fm.getHeight()));
+    }
+    
+    public int getCurrentTopLine() {
+        return logLines.get(0).getLineNumber();
+    }
+    
+    public int getCurrentBottomLine() {
+        return logLines.get(logLines.size()-1).getLineNumber();
+    }
+
+    public void clearLogLines() {
+        this.logLines = new ArrayList<>();
+        txpText.setText("");
+        txpLines.setText("");
+        clearHighlighter();
+    }
+
+    public void setLogLines(List<LogLine> logLines) {
+        this.logLines = logLines;
+
+        clearHighlighter();
+        clearLineNumbers();
+        String text = "";
+        for (int i = 0; i < logLines.size(); i++) {
+            text += logLines.get(i).getText();
+            if (i < logLines.size() - 1) {
+                text += "\n";
+            }
+        }
+        txpText.setText(text);
+        setLineNumbers();
+   
+        txpText.setCaretPosition(0);
+        txpLines.setCaretPosition(0);
+
+        for (int bookmark : bookmarks.keySet()) {
+            if (bookmark < logLines.get(0).getLineNumber()) {
+                continue;
+            }
+            if (bookmark > logLines.get(logLines.size() - 1).getLineNumber()) {
+                break;
+            }
+
+            int line = getRelativeLine(bookmark);
+
+            addHighlight(logLines.get(line));
+        }
+    }
+
+    public void setLogLines(List<LogLine> logLines, int focus) {
+        setLogLines(logLines);
+        
+        //does not work for some reason - jtextpane jumps to caret position afterwards
+        //moveToBookmark(focus);
+        
+        //workaround - set caret position bevore moving
+        Rectangle visible = txpText.getVisibleRect();
+        int visibleLines = (int)(Math.ceil(visible.height/fm.getHeight()));
+
+        String text = txpText.getText();
+        int line = logLines.get(0).getLineNumber();
+        for (int i = 0; i < text.length(); i++) {
+            if (text.charAt(i) == '\n')
+                line++;
+            if (line == focus+visibleLines || i == text.length() - 1) {
+                txpText.setCaretPosition(i);
+                break;
+            }
+        }
+        moveToBookmark(focus);
+    }
+
+    public List<LogLine> getBookmarkedLines() {
+        List<LogLine> lines = new ArrayList<>();
+        for (int bookmark : bookmarks.keySet()) {
+            lines.add(bookmarks.get(bookmark).logLine);
+        }
+        return lines;
+    }
+
+    public int tryLastBookmark() {
+        int line = getCurrentlyDisplayedTopLine();
+        
+        int firstShownLine = getCurrentTopLine();
+
+        int bookmarkKey = -1;
+        for (int key : bookmarks.keySet()) {
+            if (key >= line) {
+                break;
+            }
+
+            bookmarkKey = key;
+        }
+
+        if (bookmarkKey >= firstShownLine) {
+            moveToBookmark(bookmarkKey);
+            return -1;
+        }
+
+        return bookmarkKey;
+    }
+
+    public int tryNextBookmark() {
+        Rectangle visible = txpText.getVisibleRect();
+        int lastLine = (int) Math.floor((visible.y + visible.height) / fm.getHeight());
+        lastLine = getAbsoluteLine(lastLine);
+
+        int lastShownLine = getCurrentBottomLine();
+
+        int bookmarkKey = -1;
+        for (int key : bookmarks.keySet()) {
+            if (key > lastLine) {
+                bookmarkKey = key;
+                break;
+            }
+            if (key > lastShownLine) {
+                break;
+            }
+        }
+
+        if (bookmarkKey > 0 && bookmarkKey <= lastShownLine) {
+            moveToBookmark(bookmarkKey);
+            return -1;
+        }
+
+        return bookmarkKey;
+    }
+
+    private void moveToBookmark(int line) {
+        int relLine = getRelativeLine(line);
+        Rectangle visible = txpText.getVisibleRect();
+        Rectangle position = new Rectangle(0, relLine * fm.getHeight(), visible.width, visible.height);
+        txpText.scrollRectToVisible(position);
+    }
+
+    private int getAbsoluteLine(int line) {
+        return logLines.get(0).getLineNumber() + line;
+    }
+
+    private int getRelativeLine(int line) {
+        return line - logLines.get(0).getLineNumber();
+    }
+
+    private int getStartOffset(JTextComponent component, int line) {
+        return component.getDocument().getDefaultRootElement().getElement(line).getStartOffset();
+    }
+
+    private int getEndOffset(JTextComponent component, int line) {
+        return component.getDocument().getDefaultRootElement().getElement(line).getEndOffset();
+    }
+
+    private void addHighlight(LogLine logLine) {
+        try {
+            int line = logLine.getLineNumber();
+            int relativeLine = getRelativeLine(line);
+
+            Object hl1 = textHighlighter.addHighlight(getStartOffset(txpText, relativeLine), getEndOffset(txpText, relativeLine), BOOKMARK_PAINTER);
+            Object hl2 = lineHighlighter.addHighlight(getStartOffset(txpLines, relativeLine), getEndOffset(txpLines, relativeLine), BOOKMARK_PAINTER);
+            bookmarks.put(line, new Bookmark(logLine, hl1, hl2));
+        } catch (BadLocationException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    private void removeHighlight(int line) {
+        int abs = getAbsoluteLine(line);
+        Bookmark bookmark = bookmarks.get(abs);
+        textHighlighter.removeHighlight(bookmark.hl1);
+        lineHighlighter.removeHighlight(bookmark.hl2);
+        bookmarks.remove(abs);
+    }
+
+    public void clearBookmarks() {
+        bookmarks = new TreeMap<>();
+        clearHighlighter();
+    }
+
+    private void clearHighlighter() {
+        textHighlighter.removeAllHighlights();
+        lineHighlighter.removeAllHighlights();
+    }
+
+    private void clearLineNumbers() {
+        txpLines.setText("");
+    }
+
+    private void setLineNumbers() {
+        clearLineNumbers();
+
+        if (logLines.isEmpty()) {
+            return;
+        }
+
+        int colCnt = String.valueOf(logLines.get(logLines.size() - 1).getLineNumber()).length() + 2;
+
+        int firstNr = logLines.get(0).getLineNumber();
+        String text = String.format("%" + colCnt + "s", firstNr + " ");
+        for (int i = 1; i < logLines.size(); i++) {
+            text += "\n";
+            text += String.format("%" + colCnt + "s", (firstNr + i) + " ");
+        }
+        txpLines.setText(text);
+    }
+
+    private MouseListener mouseInputListener = new MouseAdapter() {
+
+        @Override
+        public void mouseClicked(MouseEvent me) {
+            if (txpLines.getText().length() == 0) {
+                return;
+            }
+            if (me.getClickCount() == 2) {
+                int caretPos = txpLines.getCaretPosition();
+
+                int lineOffset = txpLines.getDocument().getDefaultRootElement().getElementIndex(caretPos);
+                if (txpLines.getText().charAt(caretPos - 1) == '\n') {
+                    lineOffset--;
+                }
+
+                if (bookmarks.containsKey(getAbsoluteLine(lineOffset))) {
+                    removeHighlight(lineOffset);
+                } else {
+                    addHighlight(logLines.get(lineOffset));
+                }
+            }
+        }
+    };
+
+    class Bookmark {
+
+        public final LogLine logLine;
+        public final Object hl1;
+        public final Object hl2;
+
+        public Bookmark(LogLine logLine, Object hl1, Object hl2) {
+            this.logLine = logLine;
+            this.hl1 = hl1;
+            this.hl2 = hl2;
+        }
+    }
+
+    class BookmarkHighlighter extends DefaultHighlighter {
+
+        private JTextComponent component;
+
+        @Override
+        public void install(JTextComponent component) {
+            super.install(component);
+            this.component = component;
+        }
+
+        @Override
+        public void deinstall(JTextComponent component) {
+            super.deinstall(component);
+            this.component = null;
+        }
+
+        @Override
+        public void paint(Graphics g) {
+            Highlighter.Highlight[] highlights = getHighlights();
+
+            for (int i = 0; i < highlights.length; i++) {
+                Highlighter.Highlight hl = highlights[i];
+                Rectangle bg = component.getBounds();
+                Insets insets = component.getInsets();
+                bg.x = insets.left;
+                bg.y = insets.top;
+                bg.height = insets.top + insets.bottom;
+                Highlighter.HighlightPainter painter = hl.getPainter();
+                painter.paint(g, hl.getStartOffset(), hl.getEndOffset(), bg, component);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/ImportLogErrorDialog.java	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,31 @@
+package at.ssw.visualizer.logviewer.scene;
+
+import at.ssw.visualizer.logviewer.model.LogParser;
+import java.awt.Component;
+import java.util.List;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+/**
+ *
+ * @author Katrin Strassl
+ */
+public class ImportLogErrorDialog {
+    
+    public static void showDialog(Component parent, List<LogParser.ParseError> errors) {
+        JTextArea txaErrors = new JTextArea();
+        
+        for (LogParser.ParseError error : errors) {
+            txaErrors.append("Error at line " + error.getLineNumber());
+            txaErrors.append(": " + error.getMessage());
+            txaErrors.append("\n");
+            txaErrors.append("Log line: " + error.getLine());
+            txaErrors.append("\n");
+        }
+        
+        JScrollPane scpErrors = new JScrollPane(txaErrors);
+        
+        JOptionPane.showMessageDialog(parent, scpErrors, "Parse errors", JOptionPane.ERROR_MESSAGE);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/LogScene.java	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,654 @@
+package at.ssw.visualizer.logviewer.scene;
+
+import at.ssw.visualizer.logviewer.model.LogLine;
+import at.ssw.visualizer.logviewer.model.LogModel;
+import at.ssw.visualizer.logviewer.model.LogParser;
+import at.ssw.visualizer.logviewer.model.filter.Filter;
+import at.ssw.visualizer.logviewer.model.filter.FilterManager;
+import at.ssw.visualizer.logviewer.model.io.ProgressMonitor;
+import at.ssw.visualizer.logviewer.scene.model.LogTableModel;
+import at.ssw.visualizer.logviewer.scene.model.TableLine;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.regex.PatternSyntaxException;
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import org.openide.util.Exceptions;
+import org.openide.util.NbPreferences;
+import sun.awt.CausedFocusEvent;
+
+/**
+ *
+ * @author Katrin Strassl
+ */
+public class LogScene extends JPanel {
+    private static final String PREFERENCE_DIR = "dir";
+    
+    private static final String ICON_PREFIX = "/at/ssw/visualizer/logviewer/scene/icons/";
+    private static final String ICON_ARROW_DOWN = ICON_PREFIX + "arrow_down.png";
+    private static final String ICON_ARROW_UP = ICON_PREFIX + "arrow_up.png";
+    private static final String ICON_BOOKMARK_BACK = ICON_PREFIX + "bookmark_back.png";
+    private static final String ICON_BOOKMARK_FWD = ICON_PREFIX + "bookmark_forward.png";
+    private static final String ICON_BOOKMARK_LIST = ICON_PREFIX + "bookmark_list.png";
+    private static final String ICON_LOAD = ICON_PREFIX + "loading.gif";
+    
+    private static final ImageIcon IMGICON_LOAD = new ImageIcon(LogScene.class.getResource(ICON_LOAD));
+    
+    private static final int LOG_LINES = 50;
+    private static final int KEYSTROKE_RATE = 100; // rate at which the keystrokes are checked
+    private static final int KEYSTROKE_TRESHOLD = 400; // time in ms after which a search is triggered
+            
+    private FilterManager filterManager;
+    private Thread filterRunThread;
+    
+    private int timeSinceLastKeystroke = -1;
+    
+    private JLabel lblMessage;
+    private JLabel lblIcon;
+    
+    private JLabel lblMethodFilter;
+    private JLabel lblScopeFilter;
+    private JLabel lblNodeFilter;
+    private JLabel lblFulltextFilter;
+    private JTextField txfMethodFilter;
+    private JTextField txfScopeFilter;
+    private JTextField txfNodeFilter;
+    private JTextField txfFulltextFilter;
+    
+    private JTable tblResult;
+    private LogTableModel tblResultModel;
+    
+    private BookmarkableLogViewer logViewer;
+    private JButton btnBookmarkBack;
+    private JButton btnBookmarkFwd;
+    private JButton btnBookmarkList;
+    private JButton btnLoadMoreUp;
+    private JButton btnLoadMoreDown;
+    
+    private LogParser parser = new LogParser();
+    private LogModel logModel = null;
+    private LogStatus logStatus = LogStatus.NO_LOG;
+    
+    private static LogScene instance;
+    
+    private enum LogStatus {
+        NO_LOG, LOADING, ACTIVE
+    }
+
+    public static LogScene getInstance() {
+        return instance;
+    }
+    
+    public LogScene() {
+        initComponents();
+    }
+    
+    private void initComponents() {
+        filterManager = new FilterManager();
+        
+        lblMessage = new JLabel("No logfile loaded.");
+        lblIcon = new JLabel("");
+        
+        // Initialize filter components
+        lblMethodFilter = new JLabel("Target Method:");
+        lblScopeFilter = new JLabel("Scope:");
+        lblNodeFilter = new JLabel("Node:");
+        lblFulltextFilter = new JLabel("Fulltext:");
+        
+        txfMethodFilter = new JTextField();
+        txfScopeFilter = new JTextField();
+        txfNodeFilter = new JTextField();
+        txfFulltextFilter = new JTextField();
+        
+        tblResultModel = new LogTableModel();
+        tblResult = new JTable(tblResultModel);
+        tblResult.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        tblResult.getTableHeader().setReorderingAllowed(false);
+        tblResult.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+        
+        Dimension dim = tblResult.getPreferredScrollableViewportSize();
+        tblResult.setPreferredScrollableViewportSize(new Dimension(dim.width, 10*tblResult.getRowHeight() + tblResult.getTableHeader().getHeight()));
+        JScrollPane scpTblResult = new JScrollPane(tblResult);
+        
+        logViewer = new BookmarkableLogViewer();
+        
+        btnLoadMoreUp = new JButton(new ImageIcon(LogScene.class.getResource(ICON_ARROW_UP)));
+        btnLoadMoreDown = new JButton(new ImageIcon(LogScene.class.getResource(ICON_ARROW_DOWN)));
+        
+        btnLoadMoreUp.setFocusable(false);
+        btnLoadMoreUp.setToolTipText("Load more previous log lines");
+        
+        btnLoadMoreDown.setFocusable(false);
+        btnLoadMoreDown.setToolTipText("Load more following log lines");
+        
+        btnBookmarkBack = new JButton(new ImageIcon(LogScene.class.getResource(ICON_BOOKMARK_BACK)));
+        btnBookmarkList = new JButton(new ImageIcon(LogScene.class.getResource(ICON_BOOKMARK_LIST)));
+        btnBookmarkFwd = new JButton(new ImageIcon(LogScene.class.getResource(ICON_BOOKMARK_FWD)));
+        
+        btnBookmarkBack.setFocusable(false);
+        btnBookmarkBack.setToolTipText("Navigate to the last bookmark.");
+        
+        btnBookmarkFwd.setFocusable(false);
+        btnBookmarkFwd.setToolTipText("Navigate to the next bookmark.");
+        
+        btnBookmarkList.setFocusable(false);
+        btnBookmarkList.setToolTipText("List all known bookmarks.");
+        
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new GridBagLayout());
+        pnl.setOpaque(false);
+        
+        JPanel logViewerPanel = new JPanel();
+        logViewerPanel.setLayout(new GridBagLayout());
+        logViewerPanel.setOpaque(false);
+        
+        JPanel messagePanel = new JPanel();
+        messagePanel.setLayout(new BoxLayout(messagePanel, BoxLayout.LINE_AXIS));
+        messagePanel.setOpaque(false);
+        
+        // Initialize Listeners
+        initListeners();
+        
+        // Layout components
+        GridBagConstraints gbc = new GridBagConstraints();
+        
+        messagePanel.add(lblMessage);
+        messagePanel.add(Box.createHorizontalGlue());
+        messagePanel.add(lblIcon);
+        
+        Insets standardInsets = gbc.insets;
+        
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.anchor = GridBagConstraints.WEST;
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.weightx = 1;
+        gbc.weighty = 0;
+        gbc.gridwidth = 2;
+        gbc.insets = new Insets(2, 0, 2, 0);
+        pnl.add(messagePanel, gbc);
+        
+        gbc.fill = GridBagConstraints.NONE;
+        gbc.gridwidth = 1;
+        gbc.weightx = 0;
+        gbc.weighty = 0;
+        gbc.gridy++;
+        gbc.insets = standardInsets;
+        pnl.add(lblMethodFilter, gbc);
+        
+        gbc.gridy++;
+        pnl.add(lblScopeFilter, gbc);
+        
+        gbc.gridy++;
+        pnl.add(lblNodeFilter, gbc);
+        
+        gbc.gridy++;
+        pnl.add(lblFulltextFilter, gbc);
+        
+        gbc.gridx++;
+        gbc.gridy = 1;
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.weightx = 0.75;
+        pnl.add(txfMethodFilter, gbc);
+        
+        gbc.gridy++;
+        pnl.add(txfScopeFilter, gbc);
+        
+        gbc.gridy++;
+        pnl.add(txfNodeFilter, gbc);
+        
+        gbc.gridy++;
+        pnl.add(txfFulltextFilter, gbc);
+        
+        gbc.gridx = 0;
+        //gbc.gridy++;
+        gbc.gridwidth = 2;
+        gbc.weightx = 1;
+        gbc.anchor = GridBagConstraints.FIRST_LINE_START;
+        //pnl.add(btnSearch, gbc);
+        
+        gbc.gridy++;
+        pnl.add(scpTblResult, gbc);
+        
+        gbc.gridy++;
+        gbc.weighty = 1;
+        gbc.fill = GridBagConstraints.BOTH;
+        pnl.add(logViewerPanel, gbc);
+        
+        gbc = new GridBagConstraints();
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.gridheight = 6;
+        gbc.weightx = 1;
+        gbc.weighty = 1;
+        gbc.fill = GridBagConstraints.BOTH;
+        logViewerPanel.add(logViewer, gbc);
+        
+        gbc.gridx++;
+        gbc.weightx = 0;
+        gbc.weighty = 0;
+        gbc.fill = GridBagConstraints.NONE;
+        gbc.anchor = GridBagConstraints.PAGE_START;
+        gbc.gridheight = 1;
+        logViewerPanel.add(btnLoadMoreUp, gbc);
+        
+        gbc.gridy++;
+        logViewerPanel.add(btnLoadMoreDown, gbc);
+        
+        gbc.gridy++;
+        logViewerPanel.add(Box.createRigidArea(btnLoadMoreUp.getSize()));
+        
+        gbc.gridy++;
+        logViewerPanel.add(btnBookmarkBack, gbc);
+        
+        gbc.gridy++;
+        logViewerPanel.add(btnBookmarkFwd, gbc);
+        
+        gbc.gridy++;
+        logViewerPanel.add(btnBookmarkList, gbc);
+        
+        pnl.setMinimumSize(new Dimension(500,800));
+        pnl.setMaximumSize(new Dimension(500,800));
+        pnl.setPreferredSize(new Dimension(500,800));
+        
+        this.setLayout(new BorderLayout());
+        this.add(pnl, BorderLayout.CENTER);
+        
+        Timer keyTimer = new Timer();
+        keyTimer.scheduleAtFixedRate(new KeystrokeTimer(), KEYSTROKE_RATE, KEYSTROKE_RATE);
+        
+        LogScene.instance = this;
+    }
+    
+    private void initListeners() {
+        txfMethodFilter.addKeyListener(new FilterKeyListener());
+        txfScopeFilter.addKeyListener(new FilterKeyListener());
+        txfNodeFilter.addKeyListener(new FilterKeyListener());
+        txfFulltextFilter.addKeyListener(new FilterKeyListener());
+        
+        txfMethodFilter.addFocusListener(new FilterFocusListener());
+        txfScopeFilter.addFocusListener(new FilterFocusListener());
+        txfNodeFilter.addFocusListener(new FilterFocusListener());
+        txfFulltextFilter.addFocusListener(new FilterFocusListener());
+        
+        btnLoadMoreUp.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                int line = logViewer.getCurrentlyDisplayedTopLine();
+                int top = logViewer.getCurrentTopLine();
+                int bottom = logViewer.getCurrentBottomLine();
+                logViewer.setLogLines(logModel.range(line, line - top + LOG_LINES, bottom - line), line); 
+            } 
+        });
+        
+        btnLoadMoreDown.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                int line = logViewer.getCurrentlyDisplayedTopLine();
+                int top = logViewer.getCurrentTopLine();
+                int bottom = logViewer.getCurrentBottomLine();
+                logViewer.setLogLines(logModel.range(line, line - top, bottom - line + LOG_LINES), line); 
+            } 
+        });
+        
+        btnBookmarkBack.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                int result = logViewer.tryLastBookmark();
+                if (result >= 0) {
+                    tblResult.clearSelection();
+                    logViewer.setLogLines(logModel.range(result, LOG_LINES, LOG_LINES), result); 
+                }
+            }
+        });
+        
+        btnBookmarkFwd.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                int result = logViewer.tryNextBookmark();
+                if (result >= 0) {
+                    tblResult.clearSelection();
+                    logViewer.setLogLines(logModel.range(result, LOG_LINES, LOG_LINES), result);
+                }
+            }
+        });
+        
+        btnBookmarkList.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                List<LogLine> bookmarkedLines = logViewer.getBookmarkedLines();
+                if (bookmarkedLines.size() > 0) {
+                    BookmarkDialog bd = new BookmarkDialog(bookmarkedLines, null);
+                    if (bd.getTarget() != null) {
+                        LogLine target = bd.getTarget();
+                        int line = target.getLineNumber();
+                        logViewer.setLogLines(logModel.range(line, LOG_LINES, LOG_LINES), line); 
+                    }
+                } else {
+                    JOptionPane.showMessageDialog(LogScene.this, "No bookmarks set.", "", JOptionPane.INFORMATION_MESSAGE);
+                }
+            }
+        });
+        
+        tblResult.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+            @Override
+            public void valueChanged(ListSelectionEvent lse) {
+                if (tblResult.getSelectedRow() == -1) {
+                    logViewer.clearLogLines();
+                    return;
+                }
+                
+                TableLine selectedLine = tblResultModel.getTableLine(tblResult.getSelectedRow());
+                int firstLine = selectedLine.getFirstLine();
+                int lastLine = selectedLine.getLastLine();
+                
+                if (firstLine == lastLine) { 
+                    // only display selected line
+                    List<LogLine> line = new ArrayList<>();
+                    line.add(selectedLine.getLogLine());
+                    logViewer.setLogLines(line);
+                } else {
+                    // display selected range
+                    int pre = firstLine==lastLine?LOG_LINES:selectedLine.getLogLine().getLineNumber()-firstLine;
+                    int post = firstLine==lastLine?LOG_LINES:lastLine-selectedLine.getLogLine().getLineNumber();
+
+                    logViewer.setLogLines(logModel.range(selectedLine.getLogLine(), pre, post));           
+                }
+            }
+        });
+    }
+    
+    private boolean executeFilters() {
+        if (logStatus == LogStatus.NO_LOG) {
+            loadLogFile();
+            return false;
+        }
+        if (logStatus == LogStatus.LOADING) {
+            return false;
+        }
+
+        boolean execute = trySetFilter(filterManager.getMethodFilter(), txfMethodFilter.getText().trim(), lblMethodFilter);
+        execute = trySetFilter(filterManager.getScopeFilter(), txfScopeFilter.getText().trim(), lblScopeFilter) || execute;
+        try {
+            // node number
+            int node = Integer.parseInt(txfNodeFilter.getText().trim());
+            execute = trySetFilter(filterManager.getNodeFilter(), node, lblNodeFilter) || execute;
+        } catch (Exception e) {
+            // node name
+            execute = trySetFilter(filterManager.getNodeFilter(), txfNodeFilter.getText().trim(), lblNodeFilter) || execute;
+        }
+        execute = trySetFilter(filterManager.getFullTextFilter(), txfFulltextFilter.getText().trim(), lblFulltextFilter) || execute;
+
+        if (!execute)
+            return true;
+        
+        tryInterruptFilter();
+
+        filterRunThread = new Thread(new Runnable() {
+
+            @Override
+            public void run() {
+                try {
+                    List<LogLine> result = filterManager.execute(logModel);
+                    lblIcon.setIcon(null);
+
+                    if (!Thread.interrupted())
+                        buildTableEntries(result);
+                } catch (InterruptedException e) {
+                }
+            }
+
+        });
+        lblIcon.setIcon(IMGICON_LOAD);
+        filterRunThread.start();
+        
+        return true;
+    }
+    
+    private boolean trySetFilter(Filter filter, Object constraint, JLabel marker) {
+        try {
+            filter.setConstraints(constraint);
+            marker.setForeground(Color.BLACK);
+            return constraint != null && (!(constraint instanceof String) || (constraint instanceof String && ((String)constraint).trim().length() > 0));
+        } catch (PatternSyntaxException e) {
+            filter.setConstraints(new Object[]{});
+            marker.setForeground(Color.RED);
+            return false;
+        }
+    }
+    
+    private void tryInterruptFilter() {
+        if (filterRunThread != null && (filterRunThread.isAlive() || !filterRunThread.isInterrupted())) {
+            lblIcon.setIcon(null);
+            filterRunThread.interrupt();
+        }
+    }
+    
+    private void buildTableEntries(List<LogLine> filterResult) {
+        List<TableLine> tableEntries;
+        
+        int methodLength = txfMethodFilter.getText().trim().length();
+        int scopeLength = txfScopeFilter.getText().trim().length();
+        int nodeLength = txfNodeFilter.getText().trim().length();
+        int fulltextLength = txfFulltextFilter.getText().trim().length();
+        
+        boolean preventGrouping = nodeLength != 0 || fulltextLength != 0;
+        
+        if (!preventGrouping && methodLength > 0 && scopeLength == 0) {
+            tableEntries = groupByMethod(filterResult);
+        } else if (!preventGrouping && scopeLength > 0) {
+            tableEntries = groupByScope(filterResult);
+        } else {
+            tableEntries = showAllLines(filterResult);
+        }
+        
+        tblResultModel.setLogEntries(tableEntries);
+        
+        int width = 0;
+        for (int i = 0; i < tableEntries.size(); i++) {
+            TableCellRenderer tcr = tblResult.getCellRenderer(i, 0);
+            Component c = tcr.getTableCellRendererComponent(tblResult, tblResultModel.getValueAt(i, 0), false, false, i, 0);
+            if (c instanceof JLabel) {
+                JLabel l = (JLabel) c;
+                FontMetrics fm = l.getFontMetrics(l.getFont());
+                width = Math.max(width, fm.stringWidth(l.getText()));
+            }
+        }
+       
+        // proper resizing of column
+        // setWidth and setPreferredWidth do not trigger resize
+        TableColumn col = tblResult.getColumnModel().getColumn(0);
+        col.setMinWidth(width+5);
+        col.setMaxWidth(width+5);
+        
+        // reenable resizing for user
+        col.setMinWidth(0);
+        col.setMaxWidth(999);
+    }
+    
+    private List<TableLine> groupByMethod(List<LogLine> filterResult) {
+        List<TableLine> tableEntries = new ArrayList<>();
+        int firstLine = -1;
+        for (int i = 0; i < filterResult.size(); i++) {
+            LogLine line = filterResult.get(i);
+            
+            if (firstLine < 0)
+                firstLine = line.getLineNumber();
+
+            if (i < filterResult.size() - 1) {
+                LogLine next = filterResult.get(i+1);
+                if (line.getMethod() != next.getMethod()) {
+                    tableEntries.add(new TableLine(line, firstLine));
+                    firstLine = next.getLineNumber();
+                }
+            } else {
+                tableEntries.add(new TableLine(line, firstLine));
+            }
+        }
+        return tableEntries;
+    }
+    
+    private List<TableLine> groupByScope(List<LogLine> filterResult) {
+        List<TableLine> tableEntries = new ArrayList<>();
+        LogLine firstScopeLine = null;
+        for (int i = 0; i < filterResult.size(); i++) {
+            LogLine line = filterResult.get(i);
+            
+            if (firstScopeLine == null)
+                firstScopeLine = line;
+
+            if (i < filterResult.size() - 1) {
+                LogLine next = filterResult.get(i+1);
+                if (line.getScope() != next.getScope()) {
+                    tableEntries.add(new TableLine(firstScopeLine, line.getLineNumber()));
+                    firstScopeLine = next;
+                }
+            } else {
+                tableEntries.add(new TableLine(firstScopeLine, line.getLineNumber()));
+            }
+        }
+        return tableEntries;
+    }
+    
+    private List<TableLine> showAllLines(List<LogLine> filterResult) {
+        List<TableLine> tableEntries = new ArrayList<>();
+        for (LogLine line : filterResult) {
+            tableEntries.add(new TableLine(line));
+        }
+        return tableEntries;
+    }
+    
+    public void loadLogFile() {
+        JFileChooser fc = new JFileChooser();
+        fc.setFileFilter(new javax.swing.filechooser.FileFilter() {
+
+            @Override
+            public boolean accept(File f) {
+                return f.isDirectory() ||
+                       f.getName().toLowerCase().endsWith(".txt") ||
+                       f.getName().toLowerCase().endsWith(".log");
+            }
+
+            @Override
+            public String getDescription() {
+                return "Log files (*.txt, *.log)";
+            }
+        });
+        fc.setCurrentDirectory(new File(NbPreferences.forModule(LogScene.class).get(PREFERENCE_DIR, "~")));
+        fc.setDialogTitle("Load log file");
+
+        if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
+            tryInterruptFilter();
+            
+            final File file = fc.getSelectedFile();
+            
+            NbPreferences.forModule(LogScene.class).put(PREFERENCE_DIR, file.getParent());
+            
+            lblIcon.setIcon(IMGICON_LOAD);
+            logStatus = LogStatus.LOADING;
+            new Thread(new Runnable(){
+
+                @Override
+                public void run() {
+                    try {
+                        logModel = parser.parse(file, new LoadProgressMonitor(lblMessage, "Loading file " + file.getName() + "..."));
+                        lblIcon.setIcon(null);
+                        lblMessage.setText("Current logfile: " + file.getName());
+                        logStatus = LogStatus.ACTIVE;
+                    } catch (IOException ex) {
+                        Exceptions.printStackTrace(ex);
+                    }
+                }
+
+            }).start();
+        }
+    }
+    
+    private class FilterKeyListener extends KeyAdapter {
+        @Override
+        public void keyTyped(KeyEvent e) {
+            tryInterruptFilter();
+            timeSinceLastKeystroke = 0;
+        }
+    }
+    
+    private class FilterFocusListener implements FocusListener {
+        private boolean ignore = false;
+        
+        @Override
+        public void focusGained(FocusEvent fe) {
+            if (!ignore && logStatus == LogStatus.NO_LOG) {
+                ignore = true;
+                loadLogFile();
+            }
+        }
+
+        @Override
+        public void focusLost(FocusEvent fe) {
+            if (fe instanceof CausedFocusEvent) {
+                CausedFocusEvent cfe = (CausedFocusEvent) fe;
+                ignore = ignore && // don't change ignore to true if it is already false
+                         cfe.getCause() == CausedFocusEvent.Cause.ACTIVATION; // ACTIVATION is triggered if the
+                                                                              // FileChooser is opened
+            }
+        }
+        
+    }
+    
+    private class KeystrokeTimer extends TimerTask {
+        @Override
+        public void run() {
+            if (timeSinceLastKeystroke < 0)
+                return;
+            
+            timeSinceLastKeystroke += KEYSTROKE_RATE;
+            
+            if (timeSinceLastKeystroke >= KEYSTROKE_TRESHOLD) {
+                int save = timeSinceLastKeystroke;
+                timeSinceLastKeystroke = -1; // needs to be set to -1 to prevent 
+                                             // multiple parallel filter execution
+                
+                if (!executeFilters())
+                    timeSinceLastKeystroke = save;
+            }
+        }
+    }
+    
+    private class LoadProgressMonitor implements ProgressMonitor {
+        private String staticText;
+        private JLabel lblStatus;
+        
+        public LoadProgressMonitor(JLabel lblStatus, String staticText) {
+            this.lblStatus = lblStatus;
+            this.staticText = staticText;
+            
+            lblStatus.setText(staticText);
+        }
+        
+        @Override
+        public void worked(float percentage) {
+            int perc = Math.round(percentage*100);
+            lblStatus.setText(staticText + " (" + perc + " %)");
+        }
+
+        @Override
+        public void finished() {
+            if (parser.hasErrors()) {
+                if (JOptionPane.showConfirmDialog(LogScene.this, "Parsing log file finished with errors. Show error messages?", "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) {
+                    List<LogParser.ParseError> errors = parser.getErrors();
+                    ImportLogErrorDialog.showDialog(LogScene.this, errors);
+                }
+            }
+        }
+        
+    };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/actions/ImportLogAction.java	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,25 @@
+package at.ssw.visualizer.logviewer.scene.actions;
+
+import at.ssw.visualizer.logviewer.scene.LogScene;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionReference;
+import org.openide.awt.ActionRegistration;
+
+
+/**
+ *
+ * @author Katrin Strassl
+ */
+
+@ActionID(id = "at.ssw.visualizer.logviewer.scene.actions.ImportLogActions", category = "File")
+@ActionRegistration(displayName = "Import Log", iconBase = "at/ssw/visualizer/logviewer/scene/icons/import_log.png")
+@ActionReference(path = "Menu/File", position = 600)
+public class ImportLogAction implements ActionListener {
+
+    @Override
+    public void actionPerformed(ActionEvent ae) {
+        LogScene.getInstance().loadLogFile();
+    }
+}
Binary file visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/arrow_down.png has changed
Binary file visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/arrow_up.png has changed
Binary file visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/bookmark_back.png has changed
Binary file visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/bookmark_forward.png has changed
Binary file visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/bookmark_list.png has changed
Binary file visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/import_log.png has changed
Binary file visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/icons/loading.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/model/LogTableModel.java	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,59 @@
+package at.ssw.visualizer.logviewer.scene.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ *
+ * @author Katrin Strassl
+ */
+public class LogTableModel extends AbstractTableModel {
+    private final static String[] columnNames = {"Line #", "Method", "Scope", "Node", "Log Text"};
+    
+    private List<TableLine> entries = new ArrayList<>();
+    
+    public void setLogEntries(List<TableLine> entries) {
+        this.entries = entries;
+        fireTableDataChanged();
+    }
+    
+    public TableLine getTableLine(int line) {
+        return entries.get(line);
+    }
+    
+    @Override
+    public int getRowCount() {
+        return entries.size();
+    }
+
+    @Override
+    public int getColumnCount() {
+        return columnNames.length;
+    }
+
+    @Override
+    public String getColumnName(int column) {
+        return columnNames[column];
+    }
+
+    @Override
+    public Object getValueAt(int i, int i1) {
+        switch (i1) {
+            case 0:
+                return entries.get(i).getLineNr();
+            case 1:
+                return entries.get(i).getLogLine().getMethod().getName();
+            case 2:
+                return entries.get(i).getLogLine().getScope()!=null?
+                           entries.get(i).getLogLine().getScope().getName():"";
+            case 3:
+                return entries.get(i).getLogLine().getNode()!=null?
+                           entries.get(i).getLogLine().getNode().getName():"";
+            case 4:
+                return entries.get(i).getLogLine().getText();
+        }
+        return null;
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/LogViewer/src/at/ssw/visualizer/logviewer/scene/model/TableLine.java	Fri Jun 22 23:14:42 2012 +0200
@@ -0,0 +1,43 @@
+package at.ssw.visualizer.logviewer.scene.model;
+
+import at.ssw.visualizer.logviewer.model.LogLine;
+
+/**
+ *
+ * @author Katrin Strassl
+ */
+public class TableLine {
+
+    private int lineNr;
+    private LogLine logLine;
+    
+    // display single log line
+    public TableLine(LogLine logLine) {
+        this(logLine, logLine.getLineNumber());
+    }
+    
+    // display line number range (from logLine to lineNr or vice versa)
+    public TableLine(LogLine logLine, int lineNr) {
+        this.logLine = logLine;
+        this.lineNr = lineNr;
+    }
+    
+    public int getFirstLine() {
+        return lineNr < logLine.getLineNumber()?lineNr:logLine.getLineNumber();
+    }
+    
+    public int getLastLine() {
+        return lineNr > logLine.getLineNumber()?lineNr:logLine.getLineNumber();
+    }
+    
+    public String getLineNr() {
+        if (lineNr == logLine.getLineNumber()) {
+            return String.valueOf(lineNr);
+        }
+        return getFirstLine() + "-" + getLastLine();
+    }
+    
+    public LogLine getLogLine() {
+        return logLine;
+    }
+}
--- a/visualizer/nbproject/project.properties	Fri Jun 22 23:13:34 2012 +0200
+++ b/visualizer/nbproject/project.properties	Fri Jun 22 23:14:42 2012 +0200
@@ -1,48 +1,50 @@
-app.icon=branding/core/core.jar/org/netbeans/core/startup/frame48.gif
-app.name=graalvisualizer
-app.title=Graal Visualizer
-branding.token=${app.name}
-modules=\
-    ${project.com.sun.hotspot.igv.graph}:\
-    ${project.com.sun.hotspot.igv.filter}:\
-    ${project.com.sun.hotspot.igv.hierarchicallayout}:\
-    ${project.com.sun.hotspot.igv.layout}:\
-    ${project.com.sun.hotspot.igv.data}:\
-    ${project.com.sun.hotspot.igv.view}:\
-    ${project.com.sun.hotspot.igv.bytecodes}:\
-    ${project.com.sun.hotspot.igv.difference}:\
-    ${project.com.sun.hotspot.igv.settings}:\
-    ${project.com.sun.hotspot.igv.util}:\
-    ${project.com.sun.hotspot.igv.svg}:\
-    ${project.com.sun.hotspot.igv.filterwindow}:\
-    ${project.com.sun.hotspot.igv.graal}:\
-    ${project.at.ssw.visualizer.cfg}:\
-    ${project.org.eclipse.draw2d}:\
-    ${project.com.oracle.graal.visualizer.editor}:\
-    ${project.com.oracle.graal.visualizer.outline}:\
-    ${project.com.oracle.graal.visualizer.snapshots}:\
-    ${project.com.oracle.graal.visualizer.sharedactions}
-project.at.ssw.visualizer.cfg=ControlFlowEditor
-project.com.oracle.graal.visualizer.editor=Editor
-project.com.oracle.graal.visualizer.outline=OutlineView
-project.com.oracle.graal.visualizer.sharedactions=SharedActions
-project.com.oracle.graal.visualizer.snapshots=SnapshotsView
-project.com.sun.hotspot.igv.bytecodes=Bytecodes
-project.com.sun.hotspot.igv.data=Data
-project.com.sun.hotspot.igv.difference=Difference
-project.com.sun.hotspot.igv.filter=Filter
-project.com.sun.hotspot.igv.filterwindow=FilterWindow
-project.com.sun.hotspot.igv.graal=Graal
-project.com.sun.hotspot.igv.graph=Graph
-project.com.sun.hotspot.igv.hierarchicallayout=HierarchicalLayout
-project.com.sun.hotspot.igv.layout=Layout
-project.com.sun.hotspot.igv.settings=Settings
-project.com.sun.hotspot.igv.svg=BatikSVGProxy
-project.com.sun.hotspot.igv.view=View
-project.com.sun.hotspot.igv.util=Util
-
-project.org.eclipse.draw2d=Draw2DLibrary
-# Disable assertions for RequestProcessor to prevent annoying messages in case
-# of multiple SceneAnimator update tasks in the default RequestProcessor.
-run.args.extra = -J-client -J-da:org.openide.util.RequestProcessor
-debug.args.extra = -J-client -J-da:org.openide.util.RequestProcessor
+app.icon=branding/core/core.jar/org/netbeans/core/startup/frame48.gif
+app.name=graalvisualizer
+app.title=Graal Visualizer
+branding.token=${app.name}
+modules=\
+    ${project.com.sun.hotspot.igv.graph}:\
+    ${project.com.sun.hotspot.igv.filter}:\
+    ${project.com.sun.hotspot.igv.hierarchicallayout}:\
+    ${project.com.sun.hotspot.igv.layout}:\
+    ${project.com.sun.hotspot.igv.data}:\
+    ${project.com.sun.hotspot.igv.view}:\
+    ${project.com.sun.hotspot.igv.bytecodes}:\
+    ${project.com.sun.hotspot.igv.difference}:\
+    ${project.com.sun.hotspot.igv.settings}:\
+    ${project.com.sun.hotspot.igv.util}:\
+    ${project.com.sun.hotspot.igv.svg}:\
+    ${project.com.sun.hotspot.igv.filterwindow}:\
+    ${project.com.sun.hotspot.igv.graal}:\
+    ${project.at.ssw.visualizer.cfg}:\
+    ${project.org.eclipse.draw2d}:\
+    ${project.com.oracle.graal.visualizer.editor}:\
+    ${project.com.oracle.graal.visualizer.outline}:\
+    ${project.com.oracle.graal.visualizer.snapshots}:\
+    ${project.com.oracle.graal.visualizer.sharedactions}:\
+    ${project.at.ssw.visualizer.logviewer}
+project.at.ssw.visualizer.cfg=ControlFlowEditor
+project.at.ssw.visualizer.logviewer=LogViewer
+project.com.oracle.graal.visualizer.editor=Editor
+project.com.oracle.graal.visualizer.outline=OutlineView
+project.com.oracle.graal.visualizer.sharedactions=SharedActions
+project.com.oracle.graal.visualizer.snapshots=SnapshotsView
+project.com.sun.hotspot.igv.bytecodes=Bytecodes
+project.com.sun.hotspot.igv.data=Data
+project.com.sun.hotspot.igv.difference=Difference
+project.com.sun.hotspot.igv.filter=Filter
+project.com.sun.hotspot.igv.filterwindow=FilterWindow
+project.com.sun.hotspot.igv.graal=Graal
+project.com.sun.hotspot.igv.graph=Graph
+project.com.sun.hotspot.igv.hierarchicallayout=HierarchicalLayout
+project.com.sun.hotspot.igv.layout=Layout
+project.com.sun.hotspot.igv.settings=Settings
+project.com.sun.hotspot.igv.svg=BatikSVGProxy
+project.com.sun.hotspot.igv.view=View
+project.com.sun.hotspot.igv.util=Util
+
+project.org.eclipse.draw2d=Draw2DLibrary
+# Disable assertions for RequestProcessor to prevent annoying messages in case
+# of multiple SceneAnimator update tasks in the default RequestProcessor.
+run.args.extra = -J-client -J-da:org.openide.util.RequestProcessor
+debug.args.extra = -J-client -J-da:org.openide.util.RequestProcessor