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

processing.app.contrib.ListPanel Maven / Gradle / Ivy

/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
  Part of the Processing project - http://processing.org

  Copyright (c) 2013-15 The Processing Foundation
  Copyright (c) 2011-12 Ben Fry and Casey Reas

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License version 2
  as published by the Free Software Foundation.

  This program 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 for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.
  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
package processing.app.contrib;

import java.awt.*;
import java.util.List;
import java.util.*;
import java.util.Map.Entry;

import javax.swing.*;
import javax.swing.RowSorter.SortKey;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.table.*;

import processing.app.Base;
import processing.app.Platform;
import processing.app.ui.Toolkit;


// The "Scrollable" implementation and its methods here take care of preventing
// the scrolling area from running exceptionally slowly. Not sure why they're
// necessary in the first place, however; seems like odd behavior.
// It also allows the description text in the panels to wrap properly.

public class ListPanel extends JPanel
implements Scrollable, ContributionListing.ChangeListener {
  ContributionTab contributionTab;
  TreeMap panelByContribution = new TreeMap(ContributionListing.COMPARATOR);
  Set visibleContributions = new TreeSet(ContributionListing.COMPARATOR);

  private DetailPanel selectedPanel;
  protected Contribution.Filter filter;
  protected ContributionListing contribListing = ContributionListing.getInstance();
  protected JTable table;
  DefaultTableModel model;
  JScrollPane scrollPane;

  static Icon upToDateIcon;
  static Icon updateAvailableIcon;
  static Icon incompatibleIcon;
  static Icon foundationIcon;
  static Icon downloadingIcon;

  // Should this be in theme.txt? Of course! Is it? No.
  static final Color HEADER_BGCOLOR = new Color(0xffEBEBEB);


  public ListPanel() {
    if (upToDateIcon == null) {
      upToDateIcon = Toolkit.getLibIconX("manager/up-to-date");
      updateAvailableIcon = Toolkit.getLibIconX("manager/update-available");
      incompatibleIcon = Toolkit.getLibIconX("manager/incompatible");
      foundationIcon = Toolkit.getLibIconX("icons/foundation", 16);
      downloadingIcon = Toolkit.getLibIconX("manager/downloading");
    }
  }


  public ListPanel(final ContributionTab contributionTab,
                   Contribution.Filter filter) {
    this.contributionTab = contributionTab;
    this.filter = filter;

    setLayout(new GridBagLayout());
    setOpaque(true);
    setBackground(Color.WHITE);
    model = new ContribTableModel();
    table = new JTable(model) {
      @Override
      public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        Component c = super.prepareRenderer(renderer, row, column);
        if (isRowSelected(row)) {
          c.setBackground(new Color(0xe0fffd));
        } else {
          c.setBackground(Color.white);
        }
        return c;
      }
    };

    // There is a space before Status
    String[] colName = { " Status", "Name", "Author" };
    model.setColumnIdentifiers(colName);
    scrollPane = new JScrollPane(table);
    scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    scrollPane.setBorder(BorderFactory.createEmptyBorder());
    table.setFillsViewportHeight(true);
    table.setDefaultRenderer(Contribution.class, new ContribStatusRenderer());
    table.setFont(ManagerFrame.NORMAL_PLAIN);
    table.setRowHeight(Toolkit.zoom(28));
    table.setRowMargin(Toolkit.zoom(6));
    table.getColumnModel().setColumnMargin(0);
    table.getColumnModel().getColumn(0).setMaxWidth(ManagerFrame.STATUS_WIDTH);
    table.getColumnModel().getColumn(2).setMinWidth(ManagerFrame.AUTHOR_WIDTH);
    table.getColumnModel().getColumn(2).setMaxWidth(ManagerFrame.AUTHOR_WIDTH);
    table.setShowGrid(false);
    table.setColumnSelectionAllowed(false);
    table.setCellSelectionEnabled(false);
    table.setAutoCreateColumnsFromModel(true);
    table.setAutoCreateRowSorter(false);
    table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

    table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
        public void valueChanged(ListSelectionEvent event) {
          //TODO this executes 2 times when clicked and 1 time when traversed using arrow keys
          //Ideally this should always be true but while clearing the table something fishy is going on
          if (table.getSelectedRow() != -1) {
            setSelectedPanel(panelByContribution.get(table.getValueAt(table
              .getSelectedRow(), 0)));
            // Preventing the focus to move out of filterField after typing every character
            if (!contributionTab.filterField.hasFocus()) {
              table.requestFocusInWindow();
            }
          }
        }
      });

    TableRowSorter sorter = new TableRowSorter(table.getModel());
    table.setRowSorter(sorter);
    sorter.setComparator(1, ContributionListing.COMPARATOR);
    sorter.setComparator(2, new Comparator() {

      @Override
      public int compare(Contribution o1, Contribution o2) {
        return getAuthorNameWithoutMarkup(o1.getAuthorList())
          .compareTo(getAuthorNameWithoutMarkup(o2.getAuthorList()));
      }
    });
    sorter.setComparator(0, new Comparator() {

      @Override
      public int compare(Contribution o1, Contribution o2) {
        int pos1 = 0;
        if (o1.isInstalled()) {
          pos1 = 1;
          if (contribListing.hasUpdates(o1)) {
            pos1 = 2;
          }
          if (!o1.isCompatible(Base.getRevision())) {
            pos1 = 3;
          }
        } else {
          pos1 = 4;
        }
        int pos2 = 0;
        if (o2.isInstalled()) {
          pos2 = 1;
          if (contribListing.hasUpdates(o2)) {
            pos2 = 2;
          }
          if (!o2.isCompatible(Base.getRevision())) {
            pos2 = 3;
          }
        } else {
          pos2 = 4;
        }
        return pos1 - pos2;
      }
    });
    table.getTableHeader().setDefaultRenderer(new ContribHeaderRenderer());

    GroupLayout layout = new GroupLayout(this);
    layout.setHorizontalGroup(layout.createParallelGroup().addComponent(scrollPane));
    layout.setVerticalGroup(layout.createSequentialGroup().addComponent(scrollPane));

    this.setLayout(layout);
    table.setVisible(true);
  }


  class ContribHeaderRenderer extends DefaultTableCellRenderer {

    public ContribHeaderRenderer() {
      setHorizontalTextPosition(LEFT);
      setOpaque(true);
    }

    /**
     * Returns the default table header cell renderer.
     * 

* If the column is sorted, the appropriate icon is retrieved from the * current Look and Feel, and a border appropriate to a table header cell * is applied. *

* Subclasses may override this method to provide custom content or * formatting. * * @param table the JTable. * @param value the value to assign to the header cell * @param isSelected This parameter is ignored. * @param hasFocus This parameter is ignored. * @param row This parameter is ignored. * @param column the column of the header cell to render * @return the default table header cell renderer */ @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); JTableHeader tableHeader = table.getTableHeader(); if (tableHeader != null) { setForeground(tableHeader.getForeground()); } setFont(ManagerFrame.SMALL_PLAIN); setIcon(getSortIcon(table, column)); setBackground(HEADER_BGCOLOR); // if (column % 2 == 0) { // setBackground(new Color(0xdfdfdf)); // } else { // setBackground(new Color(0xebebeb)); // } setBorder(null); return this; } /** * Overloaded to return an icon suitable to the primary sorted column, or null if * the column is not the primary sort key. * * @param table the JTable. * @param column the column index. * @return the sort icon, or null if the column is unsorted. */ protected Icon getSortIcon(JTable table, int column) { SortKey sortKey = getSortKey(table, column); if (sortKey != null && table.convertColumnIndexToView(sortKey.getColumn()) == column) { switch (sortKey.getSortOrder()) { case ASCENDING: return UIManager.getIcon("Table.ascendingSortIcon"); case DESCENDING: return UIManager.getIcon("Table.descendingSortIcon"); } } return null; } /** * Returns the current sort key, or null if the column is unsorted. * * @param table the table * @param column the column index * @return the SortKey, or null if the column is unsorted */ protected SortKey getSortKey(JTable table, int column) { RowSorter rowSorter = table.getRowSorter(); if (rowSorter == null) { return null; } List sortedColumns = rowSorter.getSortKeys(); if (sortedColumns.size() > 0) { return (SortKey) sortedColumns.get(0); } return null; } } private class ContribStatusRenderer extends DefaultTableCellRenderer { @Override public void setVerticalAlignment(int alignment) { super.setVerticalAlignment(SwingConstants.CENTER); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Contribution contribution = (Contribution) value; JLabel label = new JLabel(); if (value == null) { // Working on https://github.com/processing/processing/issues/3667 //System.err.println("null value seen in getTableCellRendererComponent()"); // TODO this is now working, but the underlying issue is not fixed return label; } if (column == 0) { Icon icon = null; label.setFont(ManagerFrame.NORMAL_PLAIN); if (contribution.isInstalled()) { icon = upToDateIcon; if (contribListing.hasUpdates(contribution)) { icon = updateAvailableIcon; } if (!contribution.isCompatible(Base.getRevision())) { icon = incompatibleIcon; } } if ((panelByContribution.get(contribution)).updateInProgress || (panelByContribution.get(contribution)).installInProgress) { // Display "Loading icon" if download/install in progress label.setIcon(downloadingIcon); } else { label.setIcon(icon); } label.setHorizontalAlignment(SwingConstants.CENTER); if (isSelected) { label.setBackground(new Color(0xe0fffd)); } label.setOpaque(true); // return table.getDefaultRenderer(Icon.class).getTableCellRendererComponent(table, icon, isSelected, false, row, column); } else if (column == 1) { // Generating ellipses based on fontMetrics final Font boldFont = ManagerFrame.NORMAL_BOLD; String fontFace = ""; FontMetrics fontMetrics = table.getFontMetrics(boldFont); //table.getFont()); int colSize = table.getColumnModel().getColumn(1).getWidth(); String sentence = contribution.getSentence(); //int currentWidth = table.getFontMetrics(table.getFont().deriveFont(Font.BOLD)).stringWidth(contribution.getName() + " | "); int currentWidth = table.getFontMetrics(boldFont).stringWidth(contribution.getName() + " | "); int ellipsesWidth = fontMetrics.stringWidth("..."); //String name = "" + contribution.getName(); String name = "" + fontFace + contribution.getName(); if (sentence == null) { label.setText(name + ""); } else { sentence = " | " + sentence; currentWidth += ellipsesWidth; int i = 0; for (i = 0; i < sentence.length(); i++) { currentWidth += fontMetrics.charWidth(sentence.charAt(i)); if (currentWidth >= colSize) { break; } } // Adding ellipses only if text doesn't fits into the column if(i != sentence.length()){ label.setText(name + sentence.substring(0, i) + "..."); }else { label.setText(name + sentence + ""); } } if (!contribution.isCompatible(Base.getRevision())) { label.setForeground(Color.LIGHT_GRAY); } if (table.isRowSelected(row)) { label.setBackground(new Color(0xe0fffd)); } label.setFont(ManagerFrame.NORMAL_PLAIN); label.setOpaque(true); } else { if (contribution.isSpecial()) { label = new JLabel(foundationIcon); } else { label = new JLabel(); } String authorList = contribution.getAuthorList(); String name = getAuthorNameWithoutMarkup(authorList); label.setText(name); label.setHorizontalAlignment(SwingConstants.LEFT); if(!contribution.isCompatible(Base.getRevision())){ label.setForeground(Color.LIGHT_GRAY); }else{ label.setForeground(Color.BLACK); } if (table.isRowSelected(row)) { label.setBackground(new Color(0xe0fffd)); } label.setFont(ManagerFrame.NORMAL_BOLD); label.setOpaque(true); } return label; } } static private class ContribTableModel extends DefaultTableModel { @Override public boolean isCellEditable(int row, int column) { return false; } @Override public Class getColumnClass(int columnIndex) { return Contribution.class; } } String getAuthorNameWithoutMarkup(String authorList) { StringBuilder name = new StringBuilder(""); if (authorList != null) { for (int i = 0; i < authorList.length(); i++) { if (authorList.charAt(i) == '[' || authorList.charAt(i) == ']') { continue; } if (authorList.charAt(i) == '(') { i++; while (authorList.charAt(i) != ')') { i++; } } else { name.append(authorList.charAt(i)); } } } return name.toString(); } // Thread: EDT void updatePanelOrdering(Set contributionsSet) { model.getDataVector().removeAllElements(); int rowCount = 0; for (Contribution entry : contributionsSet) { model.addRow(new Object[]{entry, entry, entry}); if (selectedPanel != null && entry.getName().equals(selectedPanel.getContrib().getName())) { table.setRowSelectionInterval(rowCount, rowCount); } rowCount++; } model.fireTableDataChanged(); } // Thread: EDT public void contributionAdded(final Contribution contribution) { if (filter.matches(contribution)) { if (!panelByContribution.containsKey(contribution)) { DetailPanel newPanel = new DetailPanel(ListPanel.this); panelByContribution.put(contribution, newPanel); visibleContributions.add(contribution); newPanel.setContribution(contribution); add(newPanel); updatePanelOrdering(visibleContributions); updateColors(); // XXX this is the place } } } // Thread: EDT public void contributionRemoved(final Contribution contribution) { if (filter.matches(contribution)) { DetailPanel panel = panelByContribution.get(contribution); if (panel != null) { remove(panel); panelByContribution.remove(contribution); } visibleContributions.remove(contribution); updatePanelOrdering(visibleContributions); updateColors(); updateUI(); } } // Thread: EDT public void contributionChanged(final Contribution oldContrib, final Contribution newContrib) { if (filter.matches(oldContrib) || filter.matches(newContrib)) { DetailPanel panel = panelByContribution.get(oldContrib); if (panel == null) { contributionAdded(newContrib); } else { panelByContribution.remove(oldContrib); panel.setContribution(newContrib); panelByContribution.put(newContrib, panel); } if (visibleContributions.contains(oldContrib)) { visibleContributions.remove(oldContrib); visibleContributions.add(newContrib); } updatePanelOrdering(visibleContributions); } } // Thread: EDT public void filterLibraries(List filteredContributions) { visibleContributions.clear(); for (Contribution contribution : panelByContribution.keySet()) { if (contribution.getType() == contributionTab.contribType) { // contains() uses equals() and there can be multiple instances, // so Contribution.equals() has to be overridden if (filteredContributions.contains(contribution)) { if (panelByContribution.keySet().contains(contribution)) { visibleContributions.add(contribution); } } } } // TODO: Make the following loop work for optimization // for (Contribution contribution : filteredContributions) { // if (contribution.getType() == contributionTab.contribType) { // if(panelByContribution.keySet().contains(contribution)){ // visibleContributions.add(contribution); // } // } // } updatePanelOrdering(visibleContributions); } // Thread: EDT protected void setSelectedPanel(DetailPanel contributionPanel) { contributionTab.updateStatusPanel(contributionPanel); if (selectedPanel == contributionPanel) { selectedPanel.setSelected(true); } else { DetailPanel lastSelected = selectedPanel; selectedPanel = contributionPanel; if (lastSelected != null) { lastSelected.setSelected(false); } contributionPanel.setSelected(true); updateColors(); requestFocusInWindow(); } } protected DetailPanel getSelectedPanel() { return selectedPanel; } // Thread: EDT /** * Updates the colors of all library panels that are visible. */ protected void updateColors() { int count = 0; for (Entry entry : panelByContribution.entrySet()) { DetailPanel panel = entry.getValue(); if (panel.isVisible() && panel.isSelected()) { panel.setBackground(UIManager.getColor("List.selectionBackground")); panel.setForeground(UIManager.getColor("List.selectionForeground")); panel.setBorder(UIManager.getBorder("List.focusCellHighlightBorder")); count++; } else { Border border = null; if (panel.isVisible()) { if (Platform.isMacOS()) { if (count % 2 == 1) { border = UIManager.getBorder("List.oddRowBackgroundPainter"); } else { border = UIManager.getBorder("List.evenRowBackgroundPainter"); } } else { if (count % 2 == 1) { panel.setBackground(new Color(219, 224, 229)); } else { panel.setBackground(new Color(241, 241, 241)); } } count++; } if (border == null) { border = BorderFactory.createEmptyBorder(1, 1, 1, 1); } panel.setBorder(border); panel.setForeground(UIManager.getColor("List.foreground")); } } } @Override public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } /** * Amount to scroll to reveal a new page of items */ @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { if (orientation == SwingConstants.VERTICAL) { int blockAmount = visibleRect.height; if (direction > 0) { visibleRect.y += blockAmount; } else { visibleRect.y -= blockAmount; } blockAmount += getScrollableUnitIncrement(visibleRect, orientation, direction); return blockAmount; } return 0; } /** * Amount to scroll to reveal the rest of something we are on or a new item */ @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { if (orientation == SwingConstants.VERTICAL) { int lastHeight = 0, height = 0; int bottomOfScrollArea = visibleRect.y + visibleRect.height; for (Component c : getComponents()) { if (c.isVisible()) { if (c instanceof DetailPanel) { Dimension d = c.getPreferredSize(); int nextHeight = height + d.height; if (direction > 0) { // scrolling down if (nextHeight > bottomOfScrollArea) { return nextHeight - bottomOfScrollArea; } } else { // scrolling up if (nextHeight > visibleRect.y) { if (visibleRect.y != height) { return visibleRect.y - height; } else { return visibleRect.y - lastHeight; } } } lastHeight = height; height = nextHeight; } } } } return 0; } @Override public boolean getScrollableTracksViewportHeight() { return false; } @Override public boolean getScrollableTracksViewportWidth() { return true; } public int getRowCount() { return panelByContribution.size(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy