diff --git a/app/src/processing/app/contrib/ContributionListing.java b/app/src/processing/app/contrib/ContributionListing.java index e5fe94273..c20b4dc40 100644 --- a/app/src/processing/app/contrib/ContributionListing.java +++ b/app/src/processing/app/contrib/ContributionListing.java @@ -53,18 +53,18 @@ public class ContributionListing { Map librariesByImportHeader; // TODO: Every contribution is getting added twice // and nothing is replaced ever. - List allContributions; + Set allContributions; boolean listDownloaded; boolean listDownloadFailed; ReentrantLock downloadingListingLock; private ContributionListing() { - listeners = new ArrayList(); - advertisedContributions = new ArrayList(); - librariesByCategory = new HashMap>(); - librariesByImportHeader = new HashMap(); - allContributions = new ArrayList(); + listeners = new ArrayList<>(); + advertisedContributions = new ArrayList<>(); + librariesByCategory = new HashMap<>(); + librariesByImportHeader = new HashMap<>(); + allContributions = new LinkedHashSet<>(); downloadingListingLock = new ReentrantLock(); //listingFile = Base.getSettingsFile("contributions.txt"); @@ -96,7 +96,6 @@ public class ContributionListing { for (Contribution contribution : advertisedContributions) { addContribution(contribution); } - Collections.sort(allContributions, COMPARATOR); } @@ -139,11 +138,8 @@ public class ContributionListing { } } - for (int i = 0; i < allContributions.size(); i++) { - if (allContributions.get(i) == oldLib) { - allContributions.set(i, newLib); - } - } + allContributions.remove(oldLib); + allContributions.add(newLib); notifyChange(oldLib, newLib); } @@ -169,7 +165,6 @@ public class ContributionListing { } allContributions.add(contribution); notifyAdd(contribution); - Collections.sort(allContributions, COMPARATOR); } } @@ -234,43 +229,7 @@ public class ContributionListing { } -// public List getAllContributions() { -// return new ArrayList(allContributions); -// } - - -// public List getLibararies(String category) { -// ArrayList libinfos = -// new ArrayList(librariesByCategory.get(category)); -// Collections.sort(libinfos, nameComparator); -// return libinfos; -// } - - - protected List getFilteredLibraryList(String category, List filters) { - ArrayList filteredList = - new ArrayList(allContributions); - - Iterator it = filteredList.iterator(); - while (it.hasNext()) { - Contribution libInfo = it.next(); - //if (category != null && !category.equals(libInfo.getCategory())) { - if (category != null && !libInfo.hasCategory(category)) { - it.remove(); - } else { - for (String filter : filters) { - if (!matches(libInfo, filter)) { - it.remove(); - break; - } - } - } - } - return filteredList; - } - - - private boolean matches(Contribution contrib, String typed) { + public boolean matches(Contribution contrib, String typed) { int colon = typed.indexOf(":"); if (colon != -1) { String isText = typed.substring(0, colon); @@ -601,11 +560,7 @@ public class ContributionListing { } - static public Comparator COMPARATOR = new Comparator() { - public int compare(Contribution o1, Contribution o2) { - return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase()); - } - }; + static public Comparator COMPARATOR = Comparator.comparing(o -> o.getName().toLowerCase()); public interface ChangeListener { diff --git a/app/src/processing/app/contrib/ContributionTab.java b/app/src/processing/app/contrib/ContributionTab.java index 532d7f9ed..9028e0644 100644 --- a/app/src/processing/app/contrib/ContributionTab.java +++ b/app/src/processing/app/contrib/ContributionTab.java @@ -73,24 +73,14 @@ public class ContributionTab extends JPanel { this.contribDialog = dialog; this.contribType = type; - filter = new Contribution.Filter() { - public boolean matches(Contribution contrib) { - return contrib.getType() == contribType; - } - }; + filter = contrib -> contrib.getType() == contribType; contribListing = ContributionListing.getInstance(); statusPanel = new StatusPanel(this, 650); - contributionListPanel = new ListPanel(this, filter); + contributionListPanel = new ListPanel(this, filter, false); contribListing.addListener(contributionListPanel); } - -// public boolean hasUpdates(Base base) { -// return contribListing.hasUpdates(base); -// } - - public void showFrame(final Editor editor, boolean error, boolean loading) { this.editor = editor; @@ -277,9 +267,7 @@ public class ContributionTab extends JPanel { protected void filterLibraries(String category, List filters) { - List filteredLibraries = - contribListing.getFilteredLibraryList(category, filters); - contributionListPanel.filterLibraries(filteredLibraries); + contributionListPanel.filterLibraries(category, filters); } @@ -449,15 +437,16 @@ public class ContributionTab extends JPanel { contributionListPanel.panelByContribution.values(); for (DetailPanel detailPanel : collection) { detailPanel.update(); - - // Refreshing the ContributionUpdateTab's status icons - contributionListPanel.updatePanelOrdering(contributionListPanel - .panelByContribution.keySet()); } + contributionListPanel.model.fireTableDataChanged(); } protected boolean hasUpdates() { return contributionListPanel.getRowCount() > 0; } + + public boolean filterHasFocus() { + return filterField != null && filterField.hasFocus(); + } } diff --git a/app/src/processing/app/contrib/ListPanel.java b/app/src/processing/app/contrib/ListPanel.java index d9b36ce12..cf73db21d 100644 --- a/app/src/processing/app/contrib/ListPanel.java +++ b/app/src/processing/app/contrib/ListPanel.java @@ -35,6 +35,7 @@ import javax.swing.table.*; import processing.app.Base; import processing.app.Platform; import processing.app.ui.Toolkit; +import sun.swing.SwingUtilities2; // The "Scrollable" implementation and its methods here take care of preventing @@ -45,14 +46,13 @@ import processing.app.ui.Toolkit; public class ListPanel extends JPanel implements Scrollable, ContributionListing.ChangeListener { ContributionTab contributionTab; - TreeMap panelByContribution = new TreeMap(ContributionListing.COMPARATOR); - Set visibleContributions = new TreeSet(ContributionListing.COMPARATOR); + TreeMap panelByContribution = new TreeMap<>(ContributionListing.COMPARATOR); private DetailPanel selectedPanel; - protected Contribution.Filter filter; - protected ContributionListing contribListing = ContributionListing.getInstance(); + protected ContributionRowFilter filter; protected JTable table; - DefaultTableModel model; + protected TableRowSorter sorter; + ContributionTableModel model; JScrollPane scrollPane; static Icon upToDateIcon; @@ -63,7 +63,15 @@ implements Scrollable, ContributionListing.ChangeListener { // Should this be in theme.txt? Of course! Is it? No. static final Color HEADER_BGCOLOR = new Color(0xffEBEBEB); + static final Color SECTION_COLOR = new Color(0xFFf8f8f8); + static final Color SELECTION_COLOR = new Color(0xffe0fffd); + static final SectionHeaderContribution[] sections = { + new SectionHeaderContribution(ContributionType.LIBRARY), + new SectionHeaderContribution(ContributionType.MODE), + new SectionHeaderContribution(ContributionType.TOOL), + new SectionHeaderContribution(ContributionType.EXAMPLES) + }; public ListPanel() { if (upToDateIcon == null) { @@ -77,30 +85,42 @@ implements Scrollable, ContributionListing.ChangeListener { public ListPanel(final ContributionTab contributionTab, - Contribution.Filter filter) { + final Contribution.Filter filter, + final boolean enableSections, + final ContributionColumn... columns) { + this(); this.contributionTab = contributionTab; - this.filter = filter; + this.filter = new ContributionRowFilter(filter); setLayout(new GridBagLayout()); setOpaque(true); setBackground(Color.WHITE); - model = new ContribTableModel(); + model = new ContributionTableModel(columns); + model.enableSections(enableSections); 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)); + Object rowValue = getValueAt(row, column); + if (rowValue instanceof SectionHeaderContribution) { + c.setBackground(SECTION_COLOR); + } else if (isRowSelected(row)) { + c.setBackground(SELECTION_COLOR); } else { c.setBackground(Color.white); } return c; } + + @Override + public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) { + if (!(getValueAt(rowIndex, columnIndex) instanceof SectionHeaderContribution)) { + super.changeSelection(rowIndex, columnIndex, toggle, extend); + } + } }; // 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()); @@ -128,55 +148,22 @@ implements Scrollable, ContributionListing.ChangeListener { setSelectedPanel(panelByContribution.get(table.getValueAt(table .getSelectedRow(), 0))); // Preventing the focus to move out of filterField after typing every character - if (!contributionTab.filterField.hasFocus()) { + if (!contributionTab.filterHasFocus()) { table.requestFocusInWindow(); } } } }); - TableRowSorter sorter = new TableRowSorter(table.getModel()); + sorter = new TableRowSorter<>(model); 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.setRowFilter(this.filter); + for (int i=0; i < model.getColumnCount(); i++) { + if (model.columns[i] == ContributionColumn.NAME) { + sorter.setSortKeys(Collections.singletonList(new SortKey(i, SortOrder.ASCENDING))); } - }); - 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; - } - }); + sorter.setComparator(i, model.columns[i].getComparator()); + } table.getTableHeader().setDefaultRenderer(new ContribHeaderRenderer()); GroupLayout layout = new GroupLayout(this); @@ -187,6 +174,19 @@ implements Scrollable, ContributionListing.ChangeListener { table.setVisible(true); } + private static int getContributionStatusRank(Contribution c) { + int pos = 4; + if (c.isInstalled()) { + pos = 1; + if (ContributionListing.getInstance().hasUpdates(c)) { + pos = 2; + } + if (!c.isCompatible(Base.getRevision())) { + pos = 3; + } + } + return pos; + } class ContribHeaderRenderer extends DefaultTableCellRenderer { @@ -264,16 +264,11 @@ implements Scrollable, ContributionListing.ChangeListener { * @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; - } + return Optional.ofNullable(table.getRowSorter()) + .map(RowSorter::getSortKeys) + .map(columns -> columns.isEmpty() ? null : columns.get(0)) + .orElse(null); - List sortedColumns = rowSorter.getSortKeys(); - if (sortedColumns.size() > 0) { - return (SortKey) sortedColumns.get(0); - } - return null; } } @@ -292,130 +287,269 @@ implements Scrollable, ContributionListing.ChangeListener { int column) { Contribution contribution = (Contribution) value; JLabel label = new JLabel(); + ContributionColumn col = model.columns[column]; 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); + label.setOpaque(true); + + if (value instanceof SectionHeaderContribution && col != ContributionColumn.NAME) { + return label; + } + switch (col) { + case STATUS: + case STATUS_NO_HEADER: + configureStatusColumnLabel(label, contribution); + break; + case NAME: + configureNameColumnLabel(table, label, contribution); + break; + case AUTHOR: + configureAuthorsColumnLabel(label, contribution); + break; + case INSTALLED_VERSION: + label.setText(contribution.getBenignVersion()); + break; + case AVAILABLE_VERSION: + label.setText(ContributionListing.getInstance().getLatestPrettyVersion(contribution)); + break; + } + + if(!contribution.isCompatible(Base.getRevision())){ + label.setForeground(Color.LIGHT_GRAY); } return label; } + + private void configureStatusColumnLabel(JLabel label, Contribution contribution) { + Icon icon = null; + label.setFont(ManagerFrame.NORMAL_PLAIN); + if ((panelByContribution.get(contribution)).updateInProgress || + (panelByContribution.get(contribution)).installInProgress) { + // Display "Loading icon" if download/install in progress + icon = downloadingIcon; + } else if (contribution.isInstalled()) { + if (!contribution.isCompatible(Base.getRevision())) { + icon = incompatibleIcon; + } else if (ContributionListing.getInstance().hasUpdates(contribution)) { + icon = updateAvailableIcon; + } else if (panelByContribution.get(contribution).installInProgress + || panelByContribution.get(contribution).updateInProgress) { + icon = downloadingIcon; + } else { + icon = upToDateIcon; + } + } + + label.setIcon(icon); + label.setHorizontalAlignment(SwingConstants.CENTER); + } + + private void configureNameColumnLabel(JTable table, JLabel label, Contribution contribution) { + // Generating ellipses based on fontMetrics + final Font boldFont = ManagerFrame.NORMAL_BOLD; + FontMetrics fontMetrics = table.getFontMetrics(boldFont); //table.getFont()); + int colSize = table.getColumnModel().getColumn(1).getWidth(); + int currentWidth = fontMetrics.stringWidth(contribution.getName() + " | ..."); + String sentence = contribution.getSentence(); + StringBuilder text = new StringBuilder("") + .append(contribution.getName()); + + if (sentence == null) { + text.append(""); + } else { + int i = 0; + for (i = 0; i < sentence.length(); i++) { + currentWidth += fontMetrics.charWidth(sentence.charAt(i)); + if (currentWidth >= colSize) { + break; + } + } + text.append(" | ").append(sentence, 0, i); + // Adding ellipses only if text doesn't fits into the column + if(i != sentence.length()) { + text.append("..."); + } + } + text.append(""); + label.setText(text.toString()); + label.setFont(ManagerFrame.NORMAL_PLAIN); + } + + private void configureAuthorsColumnLabel(JLabel label, Contribution contribution) { + if (contribution.isSpecial()) { + label.setIcon(foundationIcon); + } + String authorList = contribution.getAuthorList(); + String name = getAuthorNameWithoutMarkup(authorList); + label.setText(name); + label.setHorizontalAlignment(SwingConstants.LEFT); + label.setForeground(Color.BLACK); + label.setFont(ManagerFrame.NORMAL_BOLD); + } } + protected enum ContributionColumn { + STATUS(" Status"), + NAME("Name"), + AUTHOR("Author"), + INSTALLED_VERSION("Installed"), + AVAILABLE_VERSION("Available"), + STATUS_NO_HEADER(""); + + final String name; + + ContributionColumn(String name) { + this.name = name; + } + + Comparator getComparator() { + Comparator comparator = Comparator.comparing(Contribution::getType) + .thenComparingInt(contribution -> contribution instanceof SectionHeaderContribution ? 0 : 1); + switch (this) { + case STATUS: + case STATUS_NO_HEADER: + return comparator.thenComparingInt(ListPanel::getContributionStatusRank); + case AUTHOR: + return comparator.thenComparing(contribution -> getAuthorNameWithoutMarkup(contribution.getAuthorList())); + case NAME: + default: + return comparator.thenComparing(Contribution::getName); + } + } + } + + protected class ContributionTableModel extends AbstractTableModel { + + ContributionColumn[] columns = { ContributionColumn.STATUS, ContributionColumn.NAME, ContributionColumn.AUTHOR }; + boolean sectionsEnabled; + + ContributionTableModel(ContributionColumn... columns) { + if (columns.length > 0) { + this.columns = columns; + } + } - static private class ContribTableModel extends DefaultTableModel { @Override - public boolean isCellEditable(int row, int column) { - return false; + public int getRowCount() { + return ContributionListing.getInstance().allContributions.size() + (sectionsEnabled ? 4 : 0); + } + + @Override + public int getColumnCount() { + return columns.length; + } + + @Override + public String getColumnName(int column) { + if (column < 0 || column > columns.length) { + return ""; + } + + return columns[column].name; } @Override public Class getColumnClass(int columnIndex) { return Contribution.class; } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + if (rowIndex >= ContributionListing.getInstance().allContributions.size()) { + return sections[rowIndex - ContributionListing.getInstance().allContributions.size()]; + } + + return ContributionListing.getInstance().allContributions.stream().skip(rowIndex).findFirst().orElse(null); + } + + public void setColumns(ContributionColumn[] columns) { + this.columns = columns; + } + + public void enableSections(boolean enable) { + this.sectionsEnabled = enable; + } } + protected class ContributionRowFilter extends RowFilter { + Contribution.Filter contributionFilter; + Optional categoryFilter = Optional.empty(); + List stringFilters = Collections.emptyList(); - String getAuthorNameWithoutMarkup(String authorList) { - StringBuilder name = new StringBuilder(""); + ContributionRowFilter(Contribution.Filter contributionFilter) { + this.contributionFilter = contributionFilter; + } + + public void setCategoryFilter(String categoryFilter) { + this.categoryFilter = Optional.ofNullable(categoryFilter); + } + + public void setStringFilters(List filters) { + this.stringFilters = filters; + } + + @Override + public boolean include(Entry entry) { + Contribution contribution = (Contribution) entry.getValue(0); + if (contribution instanceof SectionHeaderContribution) { + return includeSection((SectionHeaderContribution) contribution); + } + return includeContribution(contribution); + } + + private boolean includeContribution(Contribution contribution) { + return contributionFilter.matches(contribution) + && categoryFilter.map(contribution::hasCategory).orElse(true) + && stringFilters.stream().allMatch(pattern -> ContributionListing.getInstance().matches(contribution, pattern)); + } + + private boolean includeSection(SectionHeaderContribution section) { + return ContributionListing.getInstance().allContributions.stream() + .filter(contribution -> contribution.getType() == section.getType()) + .anyMatch(this::includeContribution); + } + } + + protected static class SectionHeaderContribution extends Contribution { + ContributionType type; + + SectionHeaderContribution(ContributionType type) { + this.type = type; + this.name = getTypeName(); + } + + @Override + public ContributionType getType() { + return type; + } + + @Override + public boolean isInstalled() { + return false; + } + } + + static String getAuthorNameWithoutMarkup(String authorList) { + StringBuilder name = new StringBuilder(); if (authorList != null) { + int parentheses = 0; 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 { + parentheses++; + } else if (authorList.charAt(i) == ')') { + parentheses--; + } else if (parentheses == 0) { name.append(authorList.charAt(i)); } } @@ -423,59 +557,36 @@ implements Scrollable, ContributionListing.ChangeListener { 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 - } + if (!panelByContribution.containsKey(contribution)) { + DetailPanel newPanel = + new DetailPanel(this); + panelByContribution.put(contribution, newPanel); + newPanel.setContribution(contribution); + add(newPanel); + model.fireTableDataChanged(); + 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); + model.fireTableDataChanged(); 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); @@ -483,39 +594,16 @@ implements Scrollable, ContributionListing.ChangeListener { panelByContribution.remove(oldContrib); panel.setContribution(newContrib); panelByContribution.put(newContrib, panel); + model.fireTableDataChanged(); } - 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); + public void filterLibraries(String category, List filters) { + filter.setCategoryFilter(category); + filter.setStringFilters(filters); + model.fireTableDataChanged(); } @@ -554,38 +642,35 @@ implements Scrollable, ContributionListing.ChangeListener { int count = 0; for (Entry entry : panelByContribution.entrySet()) { DetailPanel panel = entry.getValue(); + Border border = BorderFactory.createEmptyBorder(1, 1, 1, 1); - if (panel.isVisible() && panel.isSelected()) { - panel.setBackground(UIManager.getColor("List.selectionBackground")); - panel.setForeground(UIManager.getColor("List.selectionForeground")); - panel.setBorder(UIManager.getBorder("List.focusCellHighlightBorder")); + if (panel.isVisible()) { + boolean oddRow = count % 2 == 1; + Color bgColor = null; + Color fgColor = UIManager.getColor("List.foreground"); + + if (panel.isSelected()) { + bgColor = UIManager.getColor("List.selectionBackground"); + fgColor = UIManager.getColor("List.selectionForeground"); + border = UIManager.getBorder("List.focusCellHighlightBorder"); + } else if (Platform.isMacOS()) { + border = oddRow + ? UIManager.getBorder("List.oddRowBackgroundPainter") + : UIManager.getBorder("List.evenRowBackgroundPainter"); + } else { + bgColor = oddRow + ? new Color(219, 224, 229) + : new Color(241, 241, 241); + } + + panel.setForeground(fgColor); + if (bgColor != null) { + panel.setBackground(bgColor); + } 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")); } + + panel.setBorder(border); } } @@ -623,39 +708,38 @@ implements Scrollable, ContributionListing.ChangeListener { */ @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; + if (orientation != SwingConstants.VERTICAL) { + return 0; + } + int lastHeight = 0; + int height = 0; + int bottomOfScrollArea = visibleRect.y + visibleRect.height; - for (Component c : getComponents()) { - if (c.isVisible()) { - if (c instanceof DetailPanel) { - Dimension d = c.getPreferredSize(); + for (Component c : getComponents()) { + if (!(c.isVisible() && c instanceof DetailPanel)) { + continue; + } + Dimension d = c.getPreferredSize(); - int nextHeight = height + d.height; + 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; - } + if (direction > 0) { + // scrolling down + if (nextHeight > bottomOfScrollArea) { + return nextHeight - bottomOfScrollArea; + } + } else if (nextHeight > visibleRect.y) { + if (visibleRect.y != height) { + return visibleRect.y - height; + } else { + return visibleRect.y - lastHeight; } } + + lastHeight = height; + height = nextHeight; } + return 0; } @@ -673,6 +757,7 @@ implements Scrollable, ContributionListing.ChangeListener { public int getRowCount() { - return panelByContribution.size(); + // This will count section headers, but it is only used to check if any rows are shown + return sorter.getViewRowCount(); } } diff --git a/app/src/processing/app/contrib/ManagerFrame.java b/app/src/processing/app/contrib/ManagerFrame.java index 85c36353e..3e25531bf 100644 --- a/app/src/processing/app/contrib/ManagerFrame.java +++ b/app/src/processing/app/contrib/ManagerFrame.java @@ -73,7 +73,7 @@ public class ManagerFrame { modesTab = new ContributionTab(this, ContributionType.MODE); toolsTab = new ContributionTab(this, ContributionType.TOOL); examplesTab = new ContributionTab(this, ContributionType.EXAMPLES); - updatesTab = new UpdateContributionTab(this, null); + updatesTab = new UpdateContributionTab(this); } diff --git a/app/src/processing/app/contrib/UpdateContributionTab.java b/app/src/processing/app/contrib/UpdateContributionTab.java index af4a8d288..a3de07f38 100644 --- a/app/src/processing/app/contrib/UpdateContributionTab.java +++ b/app/src/processing/app/contrib/UpdateContributionTab.java @@ -11,18 +11,18 @@ import processing.app.ui.Toolkit; public class UpdateContributionTab extends ContributionTab { - public UpdateContributionTab(ManagerFrame dialog, ContributionType type) { + public UpdateContributionTab(ManagerFrame dialog) { super(); this.contribDialog = dialog; - this.contribType = type; - filter = new Contribution.Filter() { - public boolean matches(Contribution contrib) { - if (contrib instanceof LocalContribution) { - return ContributionListing.getInstance().hasUpdates(contrib); - } - return false; + filter = contrib -> { + if (contrib instanceof ListPanel.SectionHeaderContribution) { + return true; } + if (contrib instanceof LocalContribution) { + return ContributionListing.getInstance().hasUpdates(contrib); + } + return false; }; contributionListPanel = new UpdateListPanel(this, filter); // contributionListPanel.setBorder(new EmptyBorder(8, 8, 8, 8)); @@ -67,4 +67,9 @@ public class UpdateContributionTab extends ContributionTab { setBackground(Color.WHITE); } + + @Override + public void updateStatusPanel(DetailPanel contributionPanel) { + // Do nothing + } } diff --git a/app/src/processing/app/contrib/UpdateListPanel.java b/app/src/processing/app/contrib/UpdateListPanel.java index 11b23bfd6..75bcca3b6 100644 --- a/app/src/processing/app/contrib/UpdateListPanel.java +++ b/app/src/processing/app/contrib/UpdateListPanel.java @@ -1,236 +1,39 @@ package processing.app.contrib; -import java.awt.Color; -import java.awt.Component; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Set; -import java.util.TreeMap; - -import javax.swing.BorderFactory; -import javax.swing.GroupLayout; -import javax.swing.Icon; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableCellRenderer; - -import processing.app.Base; - - public class UpdateListPanel extends ListPanel { - static final Color SECTION_COLOR = new Color(0xFFf8f8f8); - static final String[] PLURAL_TYPES = { - ContributionType.LIBRARY.getPluralTitle(), - ContributionType.MODE.getPluralTitle(), - ContributionType.TOOL.getPluralTitle(), - ContributionType.EXAMPLES.getPluralTitle(), - }; - Set sectionNames = new HashSet(Arrays.asList(PLURAL_TYPES)); + + Contribution.Filter filter; public UpdateListPanel(ContributionTab contributionTab, Contribution.Filter filter) { - this.contributionTab = contributionTab; + super(contributionTab, filter, true, + ContributionColumn.STATUS_NO_HEADER, + ContributionColumn.NAME, + ContributionColumn.AUTHOR, + ContributionColumn.INSTALLED_VERSION, + ContributionColumn.AVAILABLE_VERSION); + this.filter = filter; - - setOpaque(true); - setBackground(Color.WHITE); - - model = new DefaultTableModel() { - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - @Override - public Class getColumnClass(int columnIndex) { - return (columnIndex == 0) ? Icon.class : String.class; - } - }; - - model.setColumnIdentifiers(new String[] { - "", "Name", "Author", "Installed", "Available" - }); - - table = new JTable(model) { - @Override - public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { - Component c = super.prepareRenderer(renderer, row, column); - String title = (String) getValueAt(row, 1); - if (sectionNames.contains(title)) { - c.setBackground(SECTION_COLOR); - } else { - c.setBackground(Color.WHITE); - } - return c; - } - - @Override - public void changeSelection(int rowIndex, int columnIndex, - boolean toggle, boolean extend) { - String title = (String) getValueAt(rowIndex, 1); - // Disallow selection on the fake rows - if (!sectionNames.contains(title)) { - super.changeSelection(rowIndex, columnIndex, toggle, extend); - } - } - }; - - scrollPane = new JScrollPane(table); - scrollPane.setBorder(BorderFactory.createEmptyBorder()); - - table.setFillsViewportHeight(true); - table.setSelectionBackground(new Color(0xe0fffd)); - table.setSelectionForeground(table.getForeground()); - table.setFont(ManagerFrame.NORMAL_PLAIN); - table.setRowHeight(30); - table.setRowMargin(6); - table.getColumnModel().setColumnMargin(-1); - table.getColumnModel().getColumn(0).setMaxWidth(60); - table.setShowGrid(false); - table.setCellSelectionEnabled(false); - table.setRowSelectionAllowed(true); - table.setAutoCreateColumnsFromModel(true); - table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - table.setDefaultRenderer(String.class, new DefaultTableCellRenderer() { - @Override - public Component getTableCellRendererComponent(JTable table, - Object value, - boolean isSelected, - boolean hasFocus, - int row, int column) { - return super.getTableCellRendererComponent(table, value, isSelected, - false, row, column); - } - }); - - table.getTableHeader().setDefaultRenderer(new ContribHeaderRenderer()); /* { - @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()); - } - setIcon(getSortIcon(table, column)); - setBackground(new Color(0xebebeb)); - return this; - } - });*/ - - GroupLayout layout = new GroupLayout(this); - layout.setHorizontalGroup(layout.createParallelGroup().addComponent(scrollPane)); - layout.setVerticalGroup(layout.createSequentialGroup().addComponent(scrollPane)); - - setLayout(layout); - table.setVisible(true); - - panelByContribution = new TreeMap(new Comparator() { - @Override - public int compare(Contribution o1, Contribution o2) { - int diff = - ContributionManager.getTypeIndex(o1.getType()) - - ContributionManager.getTypeIndex(o2.getType()); - if (diff == 0) { - diff = o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase()); - } - return diff; - } - }); + table.getTableHeader().setEnabled(false); } - // Thread: EDT - @Override - void updatePanelOrdering(Set contributionsSet) { - model.getDataVector().removeAllElements(); - ContributionType currentType = null; - - String fontFace = ""; - - for (Contribution entry : contributionsSet) { - if (entry.getType() != currentType) { - currentType = entry.getType(); - model.addRow(new Object[] { - null, currentType.getPluralTitle(), null, null, null - }); - } - //TODO Make this into a function - StringBuilder name = new StringBuilder(""); - String authorList = entry.getAuthorList(); - 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)); - } - } - } - Icon icon = null; - if (entry.isInstalled()) { - icon = upToDateIcon; - if (contribListing.hasUpdates(entry)) { - icon = updateAvailableIcon; - } - if (!entry.isCompatible(Base.getRevision())) { - icon = incompatibleIcon; - } - } - if ((panelByContribution.get(entry)).updateInProgress || - (panelByContribution.get(entry)).installInProgress) { - // Display "Loading icon" if download/install in progress - icon = downloadingIcon; - } - model.addRow(new Object[] { - icon, - "" + fontFace + entry.getName() + "", - name, - entry.getBenignVersion(), - contributionTab.contribListing.getLatestPrettyVersion(entry) - }); - } - model.fireTableDataChanged(); - UpdateContributionTab tab = (UpdateContributionTab) contributionTab; - ((UpdateStatusPanel) tab.statusPanel).update(); - } - - // Thread: EDT @Override public void contributionAdded(final Contribution contribution) { + // Ensures contributionAdded in ListPanel is only run on LocalContributions if (filter.matches(contribution)) { - // TODO make this longer and more contorted [fry] - DetailPanel newPanel = - contributionTab.contribDialog.getTab(contribution.getType()).contributionListPanel.panelByContribution.get(contribution); - if (newPanel == null) { - newPanel = new DetailPanel(UpdateListPanel.this); - } - if (!panelByContribution.containsKey(contribution)) { - panelByContribution.put(contribution, newPanel); - } - visibleContributions.add(contribution); - newPanel.setContribution(contribution); - add(newPanel); - updatePanelOrdering(panelByContribution.keySet()); - updateColors(); // XXX this is the place + super.contributionAdded(contribution); + ((UpdateStatusPanel) contributionTab.statusPanel).update(); // Enables update button } } + // Thread: EDT + @Override + public void contributionRemoved(final Contribution contribution) { + super.contributionRemoved(contribution); + ((UpdateStatusPanel) contributionTab.statusPanel).update(); // Disables update button on last contribution + } + // Thread: EDT @Override public void contributionChanged(final Contribution oldContrib, @@ -240,8 +43,8 @@ public class UpdateListPanel extends ListPanel { contributionAdded(newContrib); } else if (newContrib.isInstalled()) { panelByContribution.remove(oldContrib); - visibleContributions.remove(oldContrib); } - updatePanelOrdering(visibleContributions); + model.fireTableDataChanged(); } + }