All Downloads are FREE. Search and download functionalities are using the official Maven repository.

studio.ui.QGrid Maven / Gradle / Ivy

Go to download

Studio for kdb+ is a rapid development environment for the ultra-fast database kdb+ from Kx Systems: http://www.kx.com.

There is a newer version: dz4.1
Show newest version
package studio.ui;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.fife.ui.rtextarea.SearchContext;
import studio.kdb.*;
import studio.ui.action.CopyTableSelectionAction;
import studio.ui.action.TableUserAction;
import studio.ui.search.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.*;
import java.util.Objects;
import java.util.regex.PatternSyntaxException;

//@TODO: Should it be really a JPanel? It looks it should be just a JTabel. And anyway any additional components could be added to TabPanel
public class QGrid extends JPanel implements MouseWheelListener, SearchPanelListener {
    private StudioWindow studioWindow;
    private final TableModel model;
    private final JTable table;
    private final WidthAdjuster widthAdjuster;
    private final TableRowHeader tableRowHeader;
    private final TableHeaderRenderer tableHeaderRenderer;
    private final JScrollPane scrollPane;
    private final CellRenderer cellRenderer;

    private final TableMarkers markers;
    private SearchContext lastSearchContext;
    private Position lastSearchPos;

    private KFormatContext formatContext = KFormatContext.DEFAULT;

    private static final Logger log = LogManager.getLogger();

    public JTable getTable() {
        return table;
    }

    public int getRowCount() {
        return model.getRowCount();
    }

    private final JPopupMenu popupMenu = new JPopupMenu();
    private final UserAction copyExcelFormatAction;
    private final UserAction copyHtmlFormatAction;
    private final TableUserAction showSeparateAction;

    private long doubleClickTimeout;

    public void setFormatContext(KFormatContext formatContext) {
        this.formatContext = formatContext;
        cellRenderer.setFormatContext(formatContext);
        table.repaint();
    }

    public QGrid(StudioWindow studioWindow, KTableModel model) {
        this.studioWindow = studioWindow;
        this.model = model;

        InputMap inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        ActionMap actionMap = getActionMap();

        Action action = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                SearchPanel searchPanel = studioWindow.getResultSearchPanel();
                searchPanel.setVisible(true);
            }
        };

        KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F, StudioWindow.menuShortcutKeyMask);
        KeyStroke keyStroke1 = KeyStroke.getKeyStroke(KeyEvent.VK_F, StudioWindow.menuShortcutKeyMask | InputEvent.SHIFT_MASK);

        inputMap.put(keyStroke, "searchPanel");
        inputMap.put(keyStroke1, "searchPanel");
        actionMap.put("searchPanel", action);

        keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        action = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                closeSearchPanel();
            }
        };
        inputMap.put(keyStroke, "closeSearchPanel");
        actionMap.put("closeSearchPanel", action);

        setDoubleClickTimeout(Config.getInstance().getInt(Config.EMULATED_DOUBLE_CLICK_TIMEOUT));

        table = new JTable(model) {
            @Override
            public int convertRowIndexToView(int modelRowIndex) {
                throw new IllegalStateException("Not yet implemented");
            }

            @Override
            public int convertRowIndexToModel(int viewRowIndex) {
                return model.getIndex()[viewRowIndex];
            }
        };

        tableHeaderRenderer = new TableHeaderRenderer();
        table.getTableHeader().setDefaultRenderer(tableHeaderRenderer);
        table.setShowHorizontalLines(true);

        table.setDragEnabled(true);
        table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
        table.setCellSelectionEnabled(true);

        ToolTipManager.sharedInstance().unregisterComponent(table);
        ToolTipManager.sharedInstance().unregisterComponent(table.getTableHeader());

        markers = new TableMarkers(model.getColumnCount());
        cellRenderer = new CellRenderer(markers);

        for (int i = 0; i < model.getColumnCount(); i++) {
            TableColumn col = table.getColumnModel().getColumn(i);
            col.setCellRenderer(cellRenderer);
        }

        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        table.setShowVerticalLines(true);
        table.getTableHeader().setReorderingAllowed(true);
        scrollPane = new JScrollPane(table);
        scrollPane.addMouseWheelListener(this);

        tableRowHeader = new TableRowHeader(table);
        scrollPane.setRowHeaderView(tableRowHeader);

        scrollPane.getRowHeader().addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent ev) {
                Point header_pt = ((JViewport) ev.getSource()).getViewPosition();
                Point main_pt = main.getViewPosition();
                if (header_pt.y != main_pt.y) {
                    main_pt.y = header_pt.y;
                    main.setViewPosition(main_pt);
                }
            }

            final JViewport main = scrollPane.getViewport();
        });

        widthAdjuster = new WidthAdjuster(table, scrollPane);

        scrollPane.setWheelScrollingEnabled(true);
        scrollPane.getViewport().setBackground(UIManager.getColor("Table.background"));

        JLabel rowCountLabel = new IndexHeader(model, scrollPane);
        rowCountLabel.setHorizontalAlignment(SwingConstants.CENTER);
        rowCountLabel.setVerticalAlignment(SwingConstants.BOTTOM);
        rowCountLabel.setOpaque(false);
        rowCountLabel.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
        rowCountLabel.setFont(UIManager.getFont("TableHeader.font"));
        rowCountLabel.setBackground(UIManager.getColor("TableHeader.background"));
        rowCountLabel.setForeground(UIManager.getColor("TableHeader.foreground"));
        scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, rowCountLabel);

        rowCountLabel = new JLabel("");
        rowCountLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        rowCountLabel.setVerticalAlignment(SwingConstants.CENTER);
        rowCountLabel.setOpaque(true);
        rowCountLabel.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
        rowCountLabel.setFont(UIManager.getFont("Table.font"));
        rowCountLabel.setBackground(UIManager.getColor("TableHeader.background"));
        rowCountLabel.setForeground(UIManager.getColor("TableHeader.foreground"));
        scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER, rowCountLabel);

        setLayout(new BorderLayout());
        add(scrollPane, BorderLayout.CENTER);

        copyExcelFormatAction = UserAction.create("Copy (Excel format)",
                Util.COPY_ICON,"Copy the selected cells to the clipboard using Excel format",
                KeyEvent.VK_E,null,
                new CopyTableSelectionAction(CopyTableSelectionAction.Format.Excel, table));

        copyHtmlFormatAction = UserAction.create("Copy (HTML)",
                Util.COPY_ICON, "Copy the selected cells to the clipboard using HTML",
                KeyEvent.VK_H, null,
                new CopyTableSelectionAction(CopyTableSelectionAction.Format.Html, table));

        showSeparateAction = new TableUserAction.ShowSeparateAction(studioWindow, table);

        popupMenu.add(showSeparateAction);
        popupMenu.add(new JMenuItem(copyExcelFormatAction));
        popupMenu.add(new JMenuItem(copyHtmlFormatAction));

        table.addMouseListener(new MouseAdapter() {
            private int lastRow = -1;
            private int lastCol = -1;
            private long lastTimestamp = -1;

            public void mousePressed(MouseEvent e) {
                if (maybeShowPopup(e)) return;

                int row = table.rowAtPoint(e.getPoint());
                int col = table.columnAtPoint(e.getPoint());

                if ((e.getModifiers() & InputEvent.ALT_MASK) == InputEvent.ALT_MASK ) copy(row, col);
                else if (row == lastRow && col == lastCol &&
                        System.currentTimeMillis() - lastTimestamp < doubleClickTimeout) {
                    copy(row, col);
                } else {
                    lastRow = row;
                    lastCol = col;
                    lastTimestamp = System.currentTimeMillis();
                }
            }

            public void mouseReleased(MouseEvent e) {
                maybeShowPopup(e);
            }

            private boolean maybeShowPopup(MouseEvent e) {
                if (!e.isPopupTrigger()) return false;

                JPopupMenu popupMenu = getPopupMenu(e.getPoint());
                popupMenu.show(e.getComponent(), e.getX(), e.getY());
                return true;
            }

            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() != 2) return;
                copy(lastRow, lastCol);
            }


            private void copy(int row, int col) {
                lastCol = lastRow = -1;
                lastTimestamp = -1;
                if (row == -1 || col == -1) return;

                K.KBase b = (K.KBase) table.getValueAt(row, col);

                int type = b.getType();
                if ( (type >= -19 && type <= -1) ||
                        (type >= 101 && type <= 103 ) ||
                        type == 10 ) {

                    //@TODO: we shouldn't duplicate the logic here.
                    KFormatContext formatContextForCell = new KFormatContext(formatContext);
                    formatContextForCell.setShowType(b instanceof K.KBaseVector);
                    Util.copyTextToClipboard(b.toString(formatContextForCell));
                } else {
                    showSeparateAction.setLocation(row, col);
                    showSeparateAction.actionPerformed(null);
                }
            }
        });

        studioWindow.getMainStatusBar().bindTable(table);
        initSearch();
    }

    private void resetSearch() {
        lastSearchContext = null;
        lastSearchPos = null;
    }

    private void initSearch() {
        resetSearch();
        table.getModel().addTableModelListener( e-> resetSearch() ); // happens during sorting
        table.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
            @Override
            public void columnAdded(TableColumnModelEvent e) {}
            @Override
            public void columnRemoved(TableColumnModelEvent e) {}
            @Override
            public void columnMarginChanged(ChangeEvent e) {}

            @Override
            public void columnMoved(TableColumnModelEvent e) {
                resetSearch();
            }

            @Override
            public void columnSelectionChanged(ListSelectionEvent e) {
                resetSearch();
            }
        });
        table.getSelectionModel().addListSelectionListener(e -> resetSearch() );
    }

    @Override
    public void mouseWheelMoved(MouseWheelEvent e) {
        if ((e.getModifiers() & StudioWindow.menuShortcutKeyMask) == 0) return;

        Font font = Config.getInstance().getFont(Config.FONT_TABLE);
        int newFontSize = font.getSize() + e.getWheelRotation();
        if (newFontSize < 6) return;
        font = font.deriveFont((float) newFontSize);

        Config.getInstance().setFont(Config.FONT_TABLE, font);
        StudioWindow.refreshResultSettings();
    }

    public void setFont(Font font) {
        super.setFont(font);
        if (table == null) return;

        table.setFont(font);
        tableHeaderRenderer.setFont(font);
        cellRenderer.setFont(font);
        int rowHeight = getFontMetrics(font).getHeight();
        table.setRowHeight(rowHeight);

        tableRowHeader.setFont(font);
        ((JComponent)tableRowHeader.getCellRenderer()).setFont(font);
        tableRowHeader.setFixedCellHeight(rowHeight);
        tableRowHeader.recalcWidth();
        widthAdjuster.revalidate();

        revalidate();
        repaint();
    }

    public void setDoubleClickTimeout(long doubleClickTimeout) {
        this.doubleClickTimeout = doubleClickTimeout;
    }

    public void setStudioWindow(StudioWindow studioWindow) {
        this.studioWindow = studioWindow;
    }

    private JPopupMenu getPopupMenu(Point point) {
        int row = table.rowAtPoint(point);
        int col = table.columnAtPoint(point);
        if (row == -1 || col == -1) return popupMenu;

        int rowModel = table.convertRowIndexToModel(row);
        int colModel = table.convertColumnIndexToModel(col);

        String[] connections = Config.getInstance().getTableConnExtractor().getConnections(table.getModel(), rowModel, colModel);

        JPopupMenu newPopupMenu;

        if (connections.length == 0) newPopupMenu = popupMenu;
        else {
            newPopupMenu = new JPopupMenu();
            for (String connection : connections) {
                Server server = Config.getInstance().getServerByConnectionString(connection);
                String name = server.getName().length() == 0 ? connection : server.getName();
                Action action = UserAction.create("Open " + connection,
                        "Open " + name + " in a new tab", 0,
                        e -> studioWindow.addTab(server, null));
                newPopupMenu.add(action);
            }
            newPopupMenu.add(new JSeparator());

            for (Component component: popupMenu.getComponents()) {
                if (component instanceof JSeparator) newPopupMenu.addSeparator();
                if (component instanceof JMenuItem) {
                    Action action = ((JMenuItem) component).getAction();
                    if (action != null) newPopupMenu.add(action);
                }
            }
        }

        for (Component component: popupMenu.getComponents()) {
            if (component instanceof JMenuItem) {
                Action action = ((JMenuItem) component).getAction();
                if (action instanceof TableUserAction) {
                    ((TableUserAction)action).setLocation(row, col);
                }
            }
        }

        return newPopupMenu;
    }

    private boolean isSearchContinue(SearchContext context) {
        if (lastSearchContext == null) return false;

        return Objects.equals(context.getSearchFor(), lastSearchContext.getSearchFor()) &&
                context.getWholeWord() == lastSearchContext.getWholeWord() &&
                context.isRegularExpression() == lastSearchContext.isRegularExpression() &&
                context.getMatchCase() == lastSearchContext.getMatchCase() &&
                ! context.getMarkAll() && ! lastSearchContext.getMarkAll();
    }

    @Override
    public void search(SearchContext context, SearchAction action) {
        boolean markAll = context.getMarkAll();
        int markCount = 0;

        boolean searchingInSelection = table.getSelectedColumns().length > 1 || table.getSelectedRows().length > 1;
        boolean continueSearch = isSearchContinue(context);
        if (!continueSearch) {
            lastSearchContext = context;
            lastSearchPos = null;
        }
        if (!markAll) {
            markers.clear();
            table.repaint();
        }

        TableIterator tableIterator = new TableIterator(table, context.getSearchForward(), lastSearchPos);
        SearchEngine searchEngine;
        try {
            searchEngine = new SearchEngine(context);
        } catch (PatternSyntaxException e) {
            studioWindow.getMainStatusBar().setTemporaryStatus("Error in regular expression: " + e.getMessage());
            return;
        }

        Position startPos = lastSearchPos;
        while (true) {
            lastSearchPos = tableIterator.next();
            if (startPos == null) startPos = lastSearchPos;
            else if (startPos.equals(lastSearchPos)) break;

            int modelRow = table.convertRowIndexToModel(lastSearchPos.getRow());
            int modelColumn = table.convertColumnIndexToModel(lastSearchPos.getColumn());
            K.KBase value = (K.KBase)model.getValueAt(modelRow, modelColumn);
            String text = value.isNull() ? "" : value.toString(KFormatContext.NO_TYPE);

            if (searchEngine.containsIn(text)) {
                markers.mark(modelRow, modelColumn);
                if (markAll) {
                    markCount++;
                } else {
                    table.repaint();

                    Rectangle rectangle = table.getCellRect(lastSearchPos.getRow(), lastSearchPos.getColumn(), false);
                    table.scrollRectToVisible(rectangle);


                    studioWindow.getMainStatusBar().setTemporaryStatus(
                            (searchingInSelection ? "Found in selection: " : "Found: ")
                             + text);
                    studioWindow.getMainStatusBar().setRowColStatus(lastSearchPos);
                    return;
                }
            }
        }

        studioWindow.getMainStatusBar().resetStatuses();
        if (markAll) {
            studioWindow.getMainStatusBar().setTemporaryStatus(
                    (searchingInSelection ? "Marked in selection " : "Marked ")
                    + markCount + " cell(s)");
            table.repaint();
        } else {
            studioWindow.getMainStatusBar().setTemporaryStatus("Nothing was found" +
                    (searchingInSelection ? " in selection" : "") );
        }
    }

    @Override
    public void closeSearchPanel() {
        markers.clear();
        table.repaint();
        studioWindow.getResultSearchPanel().setVisible(false);
        table.requestFocus();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy