/******************************************************************************* * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com) * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt ******************************************************************************/ package com.opendoorlogistics.studio.tables.grid; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.TreeMap; import java.util.TreeSet; import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableModelEvent; import javax.swing.table.TableModel; final public class PasteLogic { private PasteLogic() { } private static Rectangle getBoundingRectangleFromRows(List<List<Point>> selectedRows) { if (selectedRows.size() == 0) { return null; } Point min = new Point(selectedRows.get(0).get(0)); Point max = new Point(min); for (List<Point> row : selectedRows) { for (Point point : row) { updateMinMax(point, min, max); } } // turn into rectangle Rectangle rectangle = new Rectangle(min.x, min.y, max.x - min.x + 1, max.y - min.y + 1); return rectangle; } private static Rectangle getBoundingRectangle(List<Point> points) { if (points.size() == 0) { return null; } Point min = new Point(points.get(0)); Point max = new Point(min); for (Point point : points) { updateMinMax(point, min, max); } // turn into rectangle Rectangle rectangle = new Rectangle(min.x, min.y, max.x - min.x + 1, max.y - min.y + 1); return rectangle; } private static void updateMinMax(Point point, Point min, Point max) { if (point.x < min.x) min.x = point.x; if (point.y < min.y) min.y = point.y; if (point.x > max.x) max.x = point.x; if (point.y > max.y) max.y = point.y; } private static List<PasteRegion> getPasteRegions(List<List<Point>> selectedRows) { Rectangle bounds = getBoundingRectangleFromRows(selectedRows); if (bounds == null) { return null; } // allocate row-column matrix int[][] matrix = new int[bounds.height][]; for (int row = 0; row < bounds.height; row++) { matrix[row] = new int[bounds.width]; Arrays.fill(matrix[row], Integer.MAX_VALUE); } // fill in all points with a unique id for each source point int counter = 0; for (List<Point> row : selectedRows) { for (Point point : row) { matrix[point.y - bounds.y][point.x - bounds.x] = counter++; } } // repeatedly scan and merge until no more merges happen int nbMerges; do { nbMerges = 0; for (int row = 0; row < bounds.height; row++) { for (int col = 0; col < bounds.width; col++) { int val = matrix[row][col]; if (val != Integer.MAX_VALUE) { // get neighbouring values and our own ArrayList<Integer> tmp = new ArrayList<>(5); tmp.add(val); // get value to the left if (col > 0) { tmp.add(matrix[row][col - 1]); } // get value to the right if (col < bounds.width-1) { tmp.add(matrix[row][col + 1]); } // get the value above if (row > 0) { tmp.add(matrix[row - 1][col]); } // get the value below if (row < bounds.height - 1) { tmp.add(matrix[row + 1][col]); } // get min and set the cell to this int min = Collections.min(tmp); if (val != min) { matrix[row][col] = min; nbMerges++; } } } } } while (nbMerges > 0); // now scan again reading out each distinct area TreeMap<Integer, List<Point>> map = new TreeMap<>(); for (int row = 0; row < bounds.height; row++) { for (int col = 0; col < bounds.width; col++) { int val = matrix[row][col]; if (val != Integer.MAX_VALUE) { List<Point> list = map.get(val); if (list == null) { list = new ArrayList<>(); map.put(val, list); } list.add(new Point(bounds.x + col,bounds.y + row )); } } } // get the bounding box for each distinct area ArrayList<PasteRegion> ret = new ArrayList<>(); for (List<Point> area : map.values()) { Rectangle bounding = getBoundingRectangle(area); PasteRegion info = new PasteRegion(area, bounding); ret.add(info); } return ret; } private static class PasteRegion{ private final List<Point> points; private final Rectangle boundingArea; PasteRegion(List<Point> points, Rectangle boundingArea) { super(); this.points = points; this.boundingArea = boundingArea; } } private static String[][] splitCopiedStringIntoCells(String s){ String lines[] = s.split("\\r?\\n", -1); String [][] ret = new String[lines.length][]; for (int i = 0; i < lines.length; i++) { ret[i] = lines[i].split("\t", -1); } return ret; } /** * For the selected copied string, which is assumed to be tab-separated * with line delimiters, and for the input selected points which should * be ordered by row first then column, paste into the table. * @param tabSeparatedCopyString * @param selectedRows * @param out * @return */ public static TableModelEvent paste(String tabSeparatedCopyString,List<List<Point>> selectedRows,int lastSettableRow, TableModel out){ // get copied cells String[][] copiedCells = splitCopiedStringIntoCells(tabSeparatedCopyString); // get max copy width int maxCopyWidth =0; for(String[] srcRow : copiedCells){ maxCopyWidth = Math.max(srcRow.length, maxCopyWidth); } if(maxCopyWidth==0){ return null; } // get distinct paste regions List<PasteRegion> pasteRegions = getPasteRegions(selectedRows); if(pasteRegions==null || pasteRegions.size()==0){ return null; } int minModifiedRow = Integer.MAX_VALUE; int maxModifiedRow= Integer.MIN_VALUE; // paste into each distinct region for(PasteRegion region : pasteRegions){ int row0 = region.boundingArea.y; int col0 = region.boundingArea.x; // paste *at least* the size of the copied strings for(int rowIndx =0 ; rowIndx < copiedCells.length ; rowIndx++){ int row = row0 + rowIndx; if(row <= lastSettableRow){ String[] srcRowArray = copiedCells[rowIndx]; for(int colIndx = 0 ; colIndx < srcRowArray.length ; colIndx++){ int col = col0 + colIndx ; out.setValueAt(srcRowArray[colIndx], row, col); // update min and max modified rows if(row < minModifiedRow){ minModifiedRow = row; } if(row > maxModifiedRow){ maxModifiedRow = row; } } } } // now go over the selected points; paste these as well and wrap // for any larger than the copied strings for(Point point : region.points){ // get source row and column using the wrapping logic int srcRow = point.y - row0; srcRow %= copiedCells.length; int srcCol = point.x - col0; srcCol %= maxCopyWidth; // see if we have a source cell, paste if so String[] srcRowArray = copiedCells[srcRow]; if(srcCol < srcRowArray.length && point.y<=lastSettableRow){ out.setValueAt(srcRowArray[srcCol],point.y, point.x); // update min and max modified rows if(point.y < minModifiedRow){ minModifiedRow = point.y; } if(point.y > maxModifiedRow){ maxModifiedRow = point.y; } } } } return new TableModelEvent(out, minModifiedRow,maxModifiedRow); } public static List<Point> toSingleList(List<List<Point>> points){ ArrayList<Point> ret = new ArrayList<>(); for(List<Point> list : points){ ret.addAll(list); } return ret; } /** * Get distinct columns in ascending order * @param points * @return */ public static List<Integer> getOrderedDistinctColumns(List<Point> points){ // distinct columns TreeSet<Integer> cols = new TreeSet<>(); for(Point point : points){ cols.add(point.x); } // get array ArrayList<Integer> list = new ArrayList<>(cols); return list; } public static List<List<Integer>> getContiguousGroups(List<Integer> numbers){ ArrayList<Integer> tmp = new ArrayList<>(numbers); Collections.sort(tmp); ArrayList<List<Integer>> ret = new ArrayList<>(); ret.add(new ArrayList<Integer>()); for(int i =0 ; i < tmp.size(); i++){ // start a new list? if(i>0){ List<Integer> current = ret.get(ret.size()-1); if(current.get(current.size()-1)!= tmp.get(i)-1){ ret.add(new ArrayList<Integer>()); } } ret.get(ret.size()-1).add(tmp.get(i)); } return ret; } public static ListSelectionEvent mergeSelectionEvents(ListSelectionEvent a, ListSelectionEvent b) { if (a!=null && b==null){ return a; } if(a==null && b!=null){ return b; } if(a==null && b==null){ return null; } return new ListSelectionEvent(a.getSource(), Math.min(a.getFirstIndex(), b.getFirstIndex()), Math.max(a.getLastIndex(), b.getLastIndex()), false); } }