/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id: FoldersList.java 5495 2008-10-24 04:59:13Z harry $ */ package de.dal33t.powerfolder.ui.folders; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.*; import com.jgoodies.forms.builder.PanelBuilder; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.PreferencesEntry; import de.dal33t.powerfolder.clientserver.ServerClient; import de.dal33t.powerfolder.clientserver.ServerClientEvent; import de.dal33t.powerfolder.clientserver.ServerClientListener; import de.dal33t.powerfolder.disk.Folder; import de.dal33t.powerfolder.disk.FolderRepository; import de.dal33t.powerfolder.disk.problem.Problem; import de.dal33t.powerfolder.disk.problem.ProblemListener; import de.dal33t.powerfolder.event.FolderMembershipEvent; import de.dal33t.powerfolder.event.FolderMembershipListener; import de.dal33t.powerfolder.event.FolderRepositoryEvent; import de.dal33t.powerfolder.event.FolderRepositoryListener; import de.dal33t.powerfolder.event.TransferManagerAdapter; import de.dal33t.powerfolder.event.TransferManagerEvent; import de.dal33t.powerfolder.light.FileInfo; import de.dal33t.powerfolder.light.FolderInfo; import de.dal33t.powerfolder.security.FolderCreatePermission; import de.dal33t.powerfolder.ui.PFUIComponent; import de.dal33t.powerfolder.ui.event.ExpansionEvent; import de.dal33t.powerfolder.ui.event.ExpansionListener; import de.dal33t.powerfolder.ui.folders.ExpandableFolderModel.Type; import de.dal33t.powerfolder.ui.model.BoundPermission; import de.dal33t.powerfolder.ui.util.DelayedUpdater; import de.dal33t.powerfolder.util.IdGenerator; import de.dal33t.powerfolder.util.UserDirectories; /** * This class creates a list combining folder repository and server client * folders. */ public class FoldersList extends PFUIComponent { private final List<ExpandableFolderView> views; private JPanel uiComponent; private JPanel folderListPanel; private FolderRepository repository; private ServerClient client; private JScrollPane scrollPane; private ExpansionListener expansionListener; private FolderMembershipListener membershipListener; private FoldersTab foldersTab; private boolean empty; private volatile boolean populated; private boolean showTypical; private DelayedUpdater transfersUpdater; private DelayedUpdater foldersUpdater; @SuppressWarnings("unused") private BoundPermission folderCreatePermission; /** * Constructor * * @param controller */ public FoldersList(Controller controller, FoldersTab foldersTab) { super(controller); empty = true; showTypical = PreferencesEntry.SHOW_TYPICAL_FOLDERS .getValueBoolean(getController()); this.foldersTab = foldersTab; transfersUpdater = new DelayedUpdater(getController()); foldersUpdater = new DelayedUpdater(getController()); expansionListener = new MyExpansionListener(); membershipListener = new MyFolderMembershipListener(); views = new CopyOnWriteArrayList<ExpandableFolderView>(); buildUI(); getController().getTransferManager().addListener( new MyTransferManagerListener()); folderCreatePermission = new BoundPermission(getController(), FolderCreatePermission.INSTANCE) { @Override public void hasPermission(boolean hasPermission) { showTypical = hasPermission && PreferencesEntry.SHOW_TYPICAL_FOLDERS .getValueBoolean(getController()); updateFolders(); } }; } /** * Gets the UI component. * * @return */ public JPanel getUIComponent() { return uiComponent; } /** * Inits the components then builds UI. */ private void buildUI() { repository = getController().getFolderRepository(); client = getController().getOSClient(); folderListPanel = new JPanel(); folderListPanel.setLayout(new BoxLayout(folderListPanel, BoxLayout.PAGE_AXIS)); registerListeners(); // Build ui FormLayout layout = new FormLayout("pref:grow", "pref, pref:grow"); PanelBuilder builder = new PanelBuilder(layout); CellConstraints cc = new CellConstraints(); builder.add(folderListPanel, cc.xy(1, 1)); uiComponent = builder.getPanel(); } public boolean isEmpty() { return empty; } /** * Adds a listener for folder repository changes. */ private void registerListeners() { getController().getFolderRepository().addProblemListenerToAllFolders( new MyProblemListener()); getController().getFolderRepository().addFolderRepositoryListener( new MyFolderRepositoryListener()); for (Folder folder : getController().getFolderRepository().getFolders()) { folder.addMembershipListener(membershipListener); } getController().getOSClient().addListener(new MyServerClientListener()); } private void updateFolders() { foldersUpdater.schedule(new Runnable() { public void run() { updateFolders0(); } }); } /** * Makes sure that the folder views are correct for folder repository and * server client folders. */ private void updateFolders0() { // Do nothing until the populate command is received. if (!populated) { return; } if (getController().isShuttingDown()) { return; } // Get combined list of repo and account folders. List<ExpandableFolderModel> localFolders = new ArrayList<ExpandableFolderModel>(); for (Folder folder : repository.getFolders()) { FolderInfo folderInfo = folder.getInfo(); ExpandableFolderModel bean = new ExpandableFolderModel( Type.Local, folderInfo, folder, getController().getOSClient().joinedByCloud(folder)); localFolders.add(bean); } for (FolderInfo folderInfo : client.getAccountFolders()) { ExpandableFolderModel bean = new ExpandableFolderModel( Type.CloudOnly, folderInfo, null, true); if (localFolders.contains(bean)) { continue; } localFolders.add(bean); } if (showTypical) { boolean showAppData = PreferencesEntry.EXPERT_MODE .getValueBoolean(getController()); for (String name : UserDirectories.getUserDirectoriesFiltered( getController(), showAppData).keySet()) { FolderInfo folderInfo = new FolderInfo(name, '[' + IdGenerator.makeId() + ']'); ExpandableFolderModel bean = new ExpandableFolderModel( Type.Typical, folderInfo, null, false); if (!localFolders.contains(bean)) { localFolders.add(bean); } } } Collections.sort(localFolders, FolderBeanComparator.INSTANCE); empty = localFolders.isEmpty(); synchronized (views) { // Remember the expanded and focussed views. FolderInfo expandedFolderInfo = null; FolderInfo focussedFolderInfo = null; for (ExpandableFolderView view : views) { if (expandedFolderInfo == null && view.isExpanded()) { expandedFolderInfo = view.getFolderInfo(); } if (focussedFolderInfo == null && view.hasFocus()) { focussedFolderInfo = view.getFolderInfo(); } } // Remove all folder views, but keep a list to see if any can be // recycled in the new display. List<ExpandableFolderView> oldViews = new ArrayList<ExpandableFolderView>(); for (ExpandableFolderView view : views) { oldViews.add(view); views.remove(view); } // Add new folder views. for (ExpandableFolderModel folderBean : localFolders) { addView(folderBean, expandedFolderInfo, focussedFolderInfo, oldViews); } // Is anything left in the old list? Decommission. for (ExpandableFolderView oldView : oldViews) { views.remove(oldView); oldView.dispose(); oldView.removeExpansionListener(expansionListener); oldView.unregisterListeners(); folderListPanel.remove(oldView.getUIComponent()); } // Redraw UI with everything gone. folderListPanel.invalidate(); if (uiComponent != null) { uiComponent.invalidate(); } if (scrollPane != null) { scrollPane.repaint(); } } foldersTab.updateEmptyLabel(); } private void addView(ExpandableFolderModel folderBean, FolderInfo expandedFolderInfo, FolderInfo focussedFolderInfo, List<ExpandableFolderView> oldViews) { // See if we already have this view. ExpandableFolderView newView = null; Iterator<ExpandableFolderView> iter = oldViews.iterator(); while (iter.hasNext()) { ExpandableFolderView oldView = iter.next(); if (oldView.getFolderInfo().equals(folderBean.getFolderInfo())) { newView = oldView; // Remove from the list so it does not get decommissioned. iter.remove(); break; } } // Do not have it? Create a new one. boolean viewCreated = false; if (newView == null) { newView = new ExpandableFolderView(getController(), folderBean.getFolderInfo()); viewCreated = true; } // Update details. newView.configure(folderBean); // Add to views. folderListPanel.add(newView.getUIComponent()); views.add(newView); // Was view expanded before? if (expandedFolderInfo != null && folderBean.getFolderInfo().equals(expandedFolderInfo)) { newView.expand(); } // Was view focussed before? if (focussedFolderInfo != null && folderBean.getFolderInfo().equals(focussedFolderInfo)) { newView.setFocus(true); } if (viewCreated) { newView.addExpansionListener(expansionListener); } } /** * Required to make scroller repaint correctly. * * @param scrollPane */ public void setScroller(JScrollPane scrollPane) { this.scrollPane = scrollPane; } /** * Enable the updateFolders method so that views get processed. This is done * so views do not get added before Synthetica has set all the colors, else * views look different before and after. */ public void populate() { populated = true; updateFolders(); } /** * Update all views with its folder's problems. */ private void updateProblems() { for (ExpandableFolderView view : views) { view.updateIconAndOS(); } } public void folderCreated(final FolderRepositoryEvent e) { // New folder created; try to show it in the list. Thread t = new Thread() { public void run() { // At this point, the newly created folder does not exist in the views, // so do this later, when the folder has appeared. try { Thread.sleep(3000); } catch (InterruptedException e) { // Don't care. } // Run this in the AWT thread later. SwingUtilities.invokeLater(new Runnable() { public void run() { scrollToFolderInfo(e.getFolderInfo()); } }); } }; t.start(); } /** * This tries to find a view for a FolderInfo and if found, scrolls to it in the list. * * @param folderInfo */ private void scrollToFolderInfo(FolderInfo folderInfo) { // Sum view heights and calculate my position within that height. boolean found = false; int totalHeight = 0; int myPosition = 0; for (ExpandableFolderView view : views) { view.getUIComponent().getHeight(); int height = view.getUIComponent().getHeight(); totalHeight += height; if (view.getFolderInfo().equals(folderInfo)) { found = true; } if (!found) { myPosition += height; } } if (found) { int viewportHeight = scrollPane.getViewport().getHeight(); // Are all the views already on the screen? if (viewportHeight > totalHeight) { // All on screen with no vertical scrolling required. Nothing to do. return; } // Scroll it so mine is at top. scrollPane.getVerticalScrollBar().setValue(myPosition); } } // //////////////// // Inner classes // // //////////////// /** * Listener for changes to folder repository folder set. */ private class MyFolderRepositoryListener implements FolderRepositoryListener { public void folderRemoved(FolderRepositoryEvent e) { updateFolders(); e.getFolder().removeMembershipListener(membershipListener); } public void folderCreated(FolderRepositoryEvent e) { updateFolders(); e.getFolder().addMembershipListener(membershipListener); } public void maintenanceStarted(FolderRepositoryEvent e) { } public void maintenanceFinished(FolderRepositoryEvent e) { } public boolean fireInEventDispatchThread() { return true; } } /** * Listener for changes to server client folder set. */ private class MyServerClientListener implements ServerClientListener { private boolean lastLoginSuccess; public void login(ServerClientEvent event) { if (event.isLoginSuccess()) { updateFolders(); lastLoginSuccess = true; } else if (lastLoginSuccess) { updateFolders(); } } public void accountUpdated(ServerClientEvent event) { updateFolders(); } public void serverConnected(ServerClientEvent event) { } public void serverDisconnected(ServerClientEvent event) { if (event.getServerNode().hasJoinedAnyFolder()) { updateFolders(); } } public void nodeServerStatusChanged(ServerClientEvent event) { if (event.getServerNode().hasJoinedAnyFolder()) { updateFolders(); } } public boolean fireInEventDispatchThread() { return true; } } private class MyFolderMembershipListener implements FolderMembershipListener { public void memberJoined(FolderMembershipEvent folderEvent) { if (getController().getOSClient().isClusterServer( folderEvent.getMember())) { updateFolders(); } } public void memberLeft(FolderMembershipEvent folderEvent) { if (getController().getOSClient().isClusterServer( folderEvent.getMember())) { updateFolders(); } } public boolean fireInEventDispatchThread() { return true; } } /** * Expansion listener to collapse all other views. */ private class MyExpansionListener implements ExpansionListener { public void resetAllButSource(ExpansionEvent e) { synchronized (views) { for (ExpandableFolderView view : views) { if (!view.equals(e.getSource())) { // Not source, so collapse. view.collapse(); view.setFocus(false); } } } } public boolean fireInEventDispatchThread() { return true; } } private static class FolderBeanComparator implements Comparator<ExpandableFolderModel> { private static final FolderBeanComparator INSTANCE = new FolderBeanComparator(); public int compare(ExpandableFolderModel o1, ExpandableFolderModel o2) { return o1.getFolderInfo().name.compareToIgnoreCase(o2 .getFolderInfo().name); } } private class MyProblemListener implements ProblemListener { public void problemAdded(Problem problem) { updateProblems(); } public void problemRemoved(Problem problem) { updateProblems(); } public boolean fireInEventDispatchThread() { return true; } } private class MyTransferManagerListener extends TransferManagerAdapter { private void notifyView(TransferManagerEvent event) { final FileInfo fileInfo = event.getFile(); transfersUpdater.schedule(new Runnable() { public void run() { FolderInfo folderInfo = fileInfo.getFolderInfo(); synchronized (views) { for (ExpandableFolderView view : views) { if (view.getFolderInfo().equals(folderInfo)) { view.updateNameLabel(); break; } } } } }); } public void completedDownloadRemoved(TransferManagerEvent event) { notifyView(event); } public void downloadCompleted(TransferManagerEvent event) { notifyView(event); } public boolean fireInEventDispatchThread() { return true; } } }