| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.tasklist.ui; |
| |
| import java.awt.EventQueue; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeSet; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.netbeans.modules.tasklist.impl.Accessor; |
| import org.netbeans.modules.tasklist.impl.TaskComparator; |
| import org.netbeans.modules.tasklist.impl.TaskList; |
| import org.netbeans.spi.tasklist.Task; |
| import org.netbeans.modules.tasklist.trampoline.TaskGroup; |
| |
| /** |
| * |
| * @author S. Aubrecht |
| */ |
| class FoldingTaskListModel extends TaskListModel { |
| |
| private final LinkedList<FoldingGroup> groups = new LinkedList<FoldingGroup>(); |
| private HashMap<String,FoldingGroup> groupMap = new HashMap<String,FoldingGroup>(10); |
| private final Logger LOG = Logger.getLogger(this.getClass().getName()); |
| |
| /** Creates a new instance of FoldingTaskListModel */ |
| public FoldingTaskListModel( TaskList taskList ) { |
| super( taskList ); |
| tasksAdded( taskList.getTasks() ); |
| } |
| |
| @Override |
| public int getRowCount() { |
| if( null == taskList ) |
| return 0; |
| int count = 0; |
| synchronized( groups ) { |
| for( FoldingGroup g : groups ) { |
| count += g.getRowCount(); |
| } |
| } |
| return count; |
| } |
| |
| @Override |
| public Class<?> getColumnClass( int column ) { |
| if( COL_GROUP == column ) |
| return FoldingGroup.class; |
| return super.getColumnClass( column ); |
| } |
| |
| @Override |
| protected Task getTaskAtRow( int row ) { |
| synchronized( groups ) { |
| int groupRow = 0; |
| for( FoldingGroup g : groups ) { |
| synchronized (g.TASK_LOCK) { |
| if( row < groupRow+g.getRowCount() ) { |
| int indexInGroup = row-groupRow-1; |
| if (indexInGroup == -1) { |
| return null; |
| } |
| return g.getTaskAt( indexInGroup); |
| } |
| groupRow += g.getRowCount(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Object getValueAt(int row, int col) { |
| FoldingGroup group = getGroupAtRow( row ); |
| if( null != group ) { |
| switch( col ) { |
| case COL_GROUP: { |
| return group; |
| } |
| default: |
| return null; |
| } |
| } |
| return super.getValueAt( row, col ); |
| } |
| |
| FoldingGroup getGroupAtRow( int row ) { |
| int groupRow = 0; |
| synchronized( groups ) { |
| for( FoldingGroup g : groups ) { |
| if( g.isEmpty() ) |
| continue; |
| if( row == groupRow ) |
| return g; |
| groupRow += g.getRowCount(); |
| } |
| } |
| return null; |
| } |
| |
| private Map<FoldingGroup,List<Task>> divideByGroup( List<? extends Task> tasks ) { |
| Map<FoldingGroup,List<Task>> grouppedTasksMap = new HashMap<FoldingGroup,List<Task>>( groupMap.size() ); |
| for( Task t : tasks ) { |
| TaskGroup tg = Accessor.getGroup( t ); |
| FoldingGroup group = groupMap.get( tg.getName() ); |
| if( null == group ) { |
| synchronized( groups ) { |
| group = new FoldingGroup( tg ); |
| groupMap.put( tg.getName(), group ); |
| groups.add( group ); |
| Collections.sort( groups ); |
| } |
| } |
| List<Task> tasksInGroup = grouppedTasksMap.get( group ); |
| if( null == tasksInGroup ) { |
| tasksInGroup = new LinkedList<Task>(); |
| grouppedTasksMap.put( group, tasksInGroup ); |
| } |
| tasksInGroup.add( t ); |
| } |
| return grouppedTasksMap; |
| } |
| |
| @Override |
| public void tasksAdded( List<? extends Task> tasks ) { |
| if( tasks.isEmpty() ) |
| return; |
| Map<FoldingGroup,List<Task>> grouppedTasksMap = divideByGroup( tasks ); |
| for( FoldingGroup fg : grouppedTasksMap.keySet() ) { |
| List<Task> tasksInGroup = grouppedTasksMap.get( fg ); |
| fg.add( tasksInGroup ); |
| } |
| sortTaskList(); |
| } |
| |
| @Override |
| public void tasksRemoved( List<? extends Task> tasks ) { |
| if( tasks.isEmpty() ) |
| return; |
| Map<FoldingGroup,List<Task>> grouppedTasksMap = divideByGroup( tasks ); |
| for( FoldingGroup fg : grouppedTasksMap.keySet() ) { |
| List<Task> tasksInGroup = grouppedTasksMap.get( fg ); |
| fg.remove( tasksInGroup ); |
| } |
| } |
| |
| @Override |
| public void cleared() { |
| synchronized( groups ) { |
| for( FoldingGroup fg : groups ) { |
| fg.clear(); |
| } |
| } |
| } |
| |
| public boolean isGroupRow( int row ) { |
| return null != getGroupAtRow( row ); |
| } |
| |
| public void toggleGroupExpanded( int row ) { |
| FoldingGroup fg = getGroupAtRow( row ); |
| if( null != fg ) |
| fg.toggleExpanded(); |
| } |
| |
| private int getFoldingGroupStartingRow( FoldingGroup fg ) { |
| if( fg.isEmpty() ) |
| return -1; |
| int startingRow = 0; |
| synchronized( groups ) { |
| int groupIndex = groups.indexOf( fg ); |
| for( int i=0; i<groupIndex; i++ ) { |
| startingRow += groups.get( i ).getRowCount(); |
| } |
| } |
| return startingRow; |
| } |
| |
| @Override |
| protected void sortTaskList() { |
| Comparator<Task> comparator; |
| switch( sortingCol ) { |
| case COL_DESCRIPTION: |
| comparator = TaskComparator.getDescriptionComparator( ascending ); |
| break; |
| case COL_LOCATION: |
| comparator = TaskComparator.getLocationComparator( ascending ); |
| break; |
| case COL_FILE: |
| comparator = TaskComparator.getFileComparator( ascending ); |
| break; |
| default: |
| comparator = TaskComparator.getDefault(); |
| break; |
| } |
| if( null != groups ) { |
| synchronized( groups ) { |
| for( FoldingGroup fg : groups ) { |
| fg.setComparator( comparator ); |
| } |
| } |
| |
| Settings.getDefault().setSortingColumn( sortingCol ); |
| Settings.getDefault().setAscendingSort( ascending ); |
| } |
| |
| fireTableDataChanged(); |
| } |
| |
| class FoldingGroup implements Comparable<FoldingTaskListModel.FoldingGroup> { |
| private TaskGroup tg; |
| |
| private final Object TASK_LOCK = new Object(); |
| private TreeSet<Task> sortedTasks = new TreeSet<Task>( getComparator() ); |
| private ArrayList<Task> tasksList; |
| |
| private boolean isExpanded; |
| private Comparator<Task> comparator; |
| |
| public FoldingGroup( TaskGroup tg ) { |
| this.tg = tg; |
| isExpanded = Settings.getDefault().isGroupExpanded( tg.getName() ); |
| } |
| |
| public void add( List<Task> newTasks ) { |
| boolean wasEmpty = isEmpty(); |
| |
| synchronized( TASK_LOCK ) { |
| sortedTasks.addAll( newTasks ); |
| tasksList = null; |
| } |
| int startingRow = getFoldingGroupStartingRow( this ); |
| |
| if( wasEmpty ) { |
| fireTableRowsInserted( startingRow, startingRow+getRowCount() ); |
| } else { |
| if( isExpanded ) { |
| int firstRow = Integer.MAX_VALUE; |
| int lastRow = Integer.MIN_VALUE; |
| for( Task t : newTasks ) { |
| int index = getTasksList().indexOf( t ); |
| if( index < firstRow ) |
| firstRow = index; |
| if( index > lastRow ) |
| lastRow = index; |
| } |
| fireTableRowsInserted( firstRow+startingRow+1, lastRow+startingRow+1 ); |
| } |
| fireTableCellUpdated( startingRow, COL_DESCRIPTION ); |
| } |
| } |
| |
| public void remove( List<Task> removedTasks ) { |
| int firstRow = Integer.MAX_VALUE; |
| int lastRow = Integer.MIN_VALUE; |
| int rowCount = getRowCount(); |
| if( isExpanded ) { |
| for( Task t : removedTasks ) { |
| int index = getTasksList().indexOf( t ); |
| if( index < firstRow ) |
| firstRow = index; |
| if( index > lastRow ) |
| lastRow = index; |
| } |
| } |
| synchronized( TASK_LOCK ) { |
| sortedTasks.removeAll( removedTasks ); |
| tasksList = null; |
| } |
| int startingRow = getFoldingGroupStartingRow( this ); |
| if( isEmpty() ) { |
| fireTableRowsDeleted( startingRow, startingRow+rowCount ); |
| } else { |
| if( isExpanded ) { |
| fireTableRowsDeleted( firstRow+startingRow+1, lastRow+startingRow+1 ); |
| } |
| fireTableCellUpdated( startingRow, COL_DESCRIPTION ); |
| } |
| } |
| |
| public void clear() { |
| if( isEmpty() ) |
| return; |
| |
| final int rowCount = getRowCount(); |
| final int startingRow = getFoldingGroupStartingRow( this ); |
| synchronized( TASK_LOCK ) { |
| sortedTasks.clear(); |
| tasksList = null; |
| } |
| EventQueue.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| fireTableRowsDeleted( startingRow, startingRow+rowCount ); |
| } |
| }); |
| } |
| |
| public boolean isEmpty() { |
| synchronized( TASK_LOCK ) { |
| return sortedTasks.isEmpty(); |
| } |
| } |
| |
| public void setExpanded( boolean expand ) { |
| if( isExpanded == expand ) |
| return; |
| toggleExpanded(); |
| } |
| |
| public void toggleExpanded() { |
| this.isExpanded = !isExpanded; |
| |
| Settings.getDefault().setGroupExpanded( tg.getName(), isExpanded ); |
| |
| int firstRow = 0; |
| synchronized( groups ) { |
| int groupIndex = groups.indexOf( this ); |
| for( int i=0; i<groupIndex; i++ ) { |
| firstRow += groups.get( i ).getRowCount(); |
| } |
| } |
| int lastRow = firstRow + getTaskCount(); |
| firstRow += 1; |
| |
| if( isExpanded ) |
| fireTableRowsInserted( firstRow, lastRow ); |
| else |
| fireTableRowsDeleted( firstRow, lastRow ); |
| fireTableCellUpdated( firstRow-1, COL_GROUP ); |
| } |
| |
| public int getRowCount() { |
| synchronized( TASK_LOCK ) { |
| return isEmpty() ? 0 : (isExpanded ? 1+sortedTasks.size() : 1); |
| } |
| } |
| |
| public int getTaskCount() { |
| synchronized( TASK_LOCK ) { |
| return sortedTasks.size(); |
| } |
| } |
| |
| public Task getTaskAt( int index ) { |
| synchronized( TASK_LOCK ) { |
| return getTasksList().get( index ); |
| } |
| } |
| |
| @Override |
| public int compareTo(org.netbeans.modules.tasklist.ui.FoldingTaskListModel.FoldingGroup other) { |
| List<? extends TaskGroup> groupList = TaskGroup.getGroups(); |
| int myIndex = groupList.indexOf( tg ); |
| int otherIndex = groupList.indexOf( other.tg ); |
| return myIndex - otherIndex; |
| } |
| |
| public boolean isExpanded() { |
| return isExpanded; |
| } |
| |
| public TaskGroup getGroup() { |
| return tg; |
| } |
| |
| private List<Task> getTasksList() { |
| synchronized ( TASK_LOCK ) { |
| if(tasksList == null) { |
| tasksList = new ArrayList<Task>(sortedTasks); |
| } |
| return tasksList; |
| } |
| } |
| |
| private Comparator<Task> getComparator() { |
| if( null == comparator ) |
| comparator = TaskComparator.getDefault(); |
| return comparator; |
| } |
| |
| private void setComparator( Comparator<Task> newComparator ) { |
| if( getComparator().equals( newComparator ) ) |
| return; |
| comparator = newComparator; |
| synchronized( TASK_LOCK ) { |
| if( !sortedTasks.isEmpty() ) { |
| |
| TreeSet<Task> s = sortedTasks; |
| sortedTasks = new TreeSet<Task>(comparator); |
| sortedTasks.addAll(s); |
| tasksList = null; |
| |
| if( isExpanded() ) { |
| int firstRow = 0; |
| synchronized( groups ) { |
| int groupIndex = groups.indexOf( this ); |
| for( int i=0; i<groupIndex; i++ ) { |
| firstRow += groups.get( i ).getRowCount(); |
| } |
| } |
| int lastRow = firstRow + getTaskCount(); |
| firstRow += 1; |
| |
| fireTableRowsUpdated( firstRow, lastRow ); |
| } |
| } |
| } |
| } |
| } |
| } |