/*
* 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: ComputersList.java 5495 2008-10-24 04:59:13Z harry $
*/
package de.dal33t.powerfolder.ui.computers;
import java.util.*;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.*;
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.Member;
import de.dal33t.powerfolder.ui.PFUIComponent;
import de.dal33t.powerfolder.ui.event.ExpansionEvent;
import de.dal33t.powerfolder.ui.event.ExpansionListener;
import de.dal33t.powerfolder.event.NodeManagerModelListener;
import de.dal33t.powerfolder.ui.model.NodeManagerModel;
import de.dal33t.powerfolder.ui.widget.GradientPanel;
import de.dal33t.powerfolder.ui.util.Icons;
import de.dal33t.powerfolder.ui.util.DelayedUpdater;
import de.dal33t.powerfolder.util.Translation;
import de.dal33t.powerfolder.util.compare.MemberComparator;
public class ComputersList extends PFUIComponent {
private JPanel uiComponent;
private JPanel computerListPanel;
private final NodeManagerModel nodeManagerModel;
private final List<ExpandableComputerView> viewList;
private ExpansionListener expansionListener;
private ComputersTab computersTab;
private volatile boolean populated;
private volatile boolean multiGroup;
// Only access these when synchronized on viewList.
private final Set<Member> previousMyComputers;
private final Set<Member> previousFriends;
private final Set<Member> previousConnectedLans;
private boolean collapseMyComputers;
private boolean collapseFriends;
private boolean collapseConnectedLans;
private JLabel myComputersLabel;
private JLabel myComputersIcon;
private JLabel friendsLabel;
private JLabel friendsIcon;
private JLabel connectedLansLabel;
private JLabel connectedLansIcon;
private DelayedUpdater forcedUpdater;
private DelayedUpdater lightUpdater;
/**
* Constructor
*
* @param controller
*/
public ComputersList(Controller controller, ComputersTab computersTab) {
super(controller);
forcedUpdater = new DelayedUpdater(controller, 500L);
lightUpdater = new DelayedUpdater(controller, 500L);
this.computersTab = computersTab;
expansionListener = new MyExpansionListener();
nodeManagerModel = getUIController().getApplicationModel()
.getNodeManagerModel();
viewList = new CopyOnWriteArrayList<ExpandableComputerView>();
previousConnectedLans = new TreeSet<Member>(MemberComparator.NICK);
previousFriends = new TreeSet<Member>(MemberComparator.NICK);
previousMyComputers = new TreeSet<Member>(MemberComparator.NICK);
myComputersLabel = new JLabel(
Translation.getTranslation("computers_list.my_computers"));
myComputersIcon = new JLabel(Icons.getIconById(Icons.EXPAND));
myComputersLabel.addMouseListener(new MyComputersListener());
myComputersIcon.addMouseListener(new MyComputersListener());
friendsLabel = new JLabel(
Translation.getTranslation("computers_list.friends"));
friendsIcon = new JLabel(Icons.getIconById(Icons.COLLAPSE));
friendsLabel.addMouseListener(new FriendsListener());
friendsIcon.addMouseListener(new FriendsListener());
connectedLansLabel = new JLabel(
Translation.getTranslation("computers_list.lan"));
connectedLansIcon = new JLabel(Icons.getIconById(Icons.EXPAND));
connectedLansLabel.addMouseListener(new ConnectedLansListener());
connectedLansIcon.addMouseListener(new ConnectedLansListener());
}
/**
* Get the UI component
*
* @return
*/
public JPanel getUIComponent() {
if (uiComponent == null) {
buildUI();
}
return uiComponent;
}
/**
* Build the UI
*/
private void buildUI() {
initComponents();
// Build ui
FormLayout layout = new FormLayout("pref:grow", "pref, pref:grow");
PanelBuilder builder = new PanelBuilder(layout);
CellConstraints cc = new CellConstraints();
builder.add(computerListPanel, cc.xy(1, 1));
uiComponent = builder.getPanel();
}
/**
* Initialize the componets
*/
private void initComponents() {
computerListPanel = new JPanel();
computerListPanel.setLayout(new BoxLayout(computerListPanel,
BoxLayout.PAGE_AXIS));
getUIController().getApplicationModel().getNodeManagerModel()
.addNodeManagerModelListener(new MyNodeManagerModelListener());
rebuild(false);
}
private void rebuild(boolean expCol) {
if (expCol) {
forcedUpdater.schedule(new Runnable() {
public void run() {
rebuild0(true);
}
});
} else {
lightUpdater.schedule(new Runnable() {
public void run() {
rebuild0(false);
}
});
}
}
/**
* Rebuild the whole list, if there is a significant change. This detects
* things like Ln nodes becoming friends, etc.
*
* @param expCol
* true if expand or collapse change - MUST redisplay, even if
* previous are all the same.
*/
private void rebuild0(boolean expCol) {
// Do nothing until populate command is called.
if (!populated) {
return;
}
Map<NodeManagerModel.Type, Set<Member>> map = nodeManagerModel
.getNodesMap();
// Split nodes into three groups:
// 1) My Computers,
// 2) Friends and
// 3) Connected LAN
// Use maps to sort by name.
Map<Member, Member> myComputersMap = new TreeMap<Member, Member>(
MemberComparator.NICK);
Map<Member, Member> friendsMap = new TreeMap<Member, Member>(
MemberComparator.NICK);
Map<Member, Member> connectedLansMap = new TreeMap<Member, Member>(
MemberComparator.NICK);
Set<Member> myComputersSet = map
.get(NodeManagerModel.Type.MY_COMPUTERS_INDEX);
for (Member member : myComputersSet) {
myComputersMap.put(member, member);
}
Set<Member> friendsSet = map.get(NodeManagerModel.Type.FRIENDS_INDEX);
for (Member member : friendsSet) {
friendsMap.put(member, member);
}
Set<Member> connectedLanSet = map
.get(NodeManagerModel.Type.CONNECTED_LAN);
for (Member member : connectedLanSet) {
connectedLansMap.put(member, member);
}
synchronized (viewList) {
// Are the nodes same as current views?
boolean different = expCol;
if (previousConnectedLans.size() == connectedLansMap.size()
&& previousFriends.size() == connectedLansMap.size()
&& previousMyComputers.size() == myComputersMap.size())
{
for (Member member : myComputersMap.values()) {
if (!previousMyComputers.contains(member)) {
different = true;
break;
}
}
if (!different) {
for (Member member : connectedLansMap.values()) {
if (!previousConnectedLans.contains(member)) {
different = true;
break;
}
}
if (!different) {
for (Member member : friendsMap.values()) {
if (!previousFriends.contains(member)) {
different = true;
break;
}
}
}
}
} else {
different = true;
}
if (!different) {
return;
}
// Update for next time.
previousConnectedLans.clear();
previousFriends.clear();
previousMyComputers.clear();
previousConnectedLans.addAll(connectedLansMap.values());
previousMyComputers.addAll(myComputersMap.values());
previousFriends.addAll(friendsMap.values());
// Clear view listeners
Member expandedNode = null;
Member focussedNode = null;
for (ExpandableComputerView view : viewList) {
if (view.isExpanded()) {
expandedNode = view.getNode();
}
if (view.hasFocus()) {
focussedNode = view.getNode();
}
view.removeExpansionListener(expansionListener);
view.removeCoreListeners();
}
viewList.clear();
computerListPanel.removeAll();
// If there is only one group, do not bother with separators
multiGroup = (myComputersMap.isEmpty() ? 0 : 1)
+ (connectedLansMap.isEmpty() ? 0 : 1)
+ (friendsMap.isEmpty() ? 0 : 1) > 1;
// First show my computers.
boolean firstMyComputer = true;
for (Member node : myComputersMap.values()) {
if (firstMyComputer && multiGroup) {
firstMyComputer = false;
addSeparator(collapseMyComputers, myComputersIcon,
myComputersLabel);
}
if (!multiGroup || !collapseMyComputers) {
addView(node, expandedNode, focussedNode);
}
}
// Then others (connected on LAN).
boolean firstLan = true;
for (Member node : connectedLansMap.values()) {
if (firstLan && multiGroup) {
firstLan = false;
addSeparator(collapseConnectedLans, connectedLansIcon,
connectedLansLabel);
}
if (!multiGroup || !collapseConnectedLans) {
addView(node, expandedNode, focussedNode);
}
}
// Then friends.
boolean firstFriend = true;
for (Member node : friendsMap.values()) {
if (firstFriend && multiGroup) {
firstFriend = false;
addSeparator(collapseFriends, friendsIcon, friendsLabel);
}
if (!multiGroup || !collapseFriends) {
addView(node, expandedNode, focussedNode);
}
}
computersTab.updateEmptyLabel();
getUIComponent().revalidate();
}
}
private void addSeparator(boolean collapsed, JLabel icon, JLabel label) {
FormLayout layout = new FormLayout(
"3dlu, pref, 3dlu, pref, 3dlu, pref:grow, 3dlu", "pref, 4dlu");
PanelBuilder builder = new PanelBuilder(layout);
CellConstraints cc = new CellConstraints();
icon.setIcon(collapsed ? Icons.getIconById(Icons.EXPAND) : Icons
.getIconById(Icons.COLLAPSE));
icon.setToolTipText(collapsed ? Translation
.getTranslation("computers_list.expand_hint") : Translation
.getTranslation("computers_list.collapse_hint"));
label.setToolTipText(collapsed ? Translation
.getTranslation("computers_list.expand_hint") : Translation
.getTranslation("computers_list.collapse_hint"));
builder.add(icon, cc.xy(2, 1));
builder.add(label, cc.xy(4, 1));
builder.add(new JSeparator(), cc.xy(6, 1));
JPanel panel = builder.getPanel();
panel.setOpaque(false);
panel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
computerListPanel.add(panel);
}
private void addView(Member node, Member expandedNode, Member focussedNode)
{
ExpandableComputerView view = new ExpandableComputerView(
getController(), node);
computerListPanel.add(view.getUIComponent());
viewList.add(view);
if (expandedNode != null && node.equals(expandedNode)) {
// Maintain expanded state of view on rebuild.
view.expand();
}
if (focussedNode != null && node.equals(focussedNode)) {
// Maintain focussed state of view on rebuild.
view.setFocus(true);
}
view.addExpansionListener(expansionListener);
}
public boolean isEmpty() {
// If multigroup, always show, even if all collapsed.
return viewList.isEmpty() && !multiGroup;
}
/**
* Enable the view processing methods 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;
rebuild(false);
}
// ////////////////
// Inner Classes //
// ////////////////
/**
* Node Manager Model listener.
*/
private class MyNodeManagerModelListener implements
NodeManagerModelListener
{
public void changed() {
rebuild(false);
}
}
/**
* Expansion listener to collapse views.
*/
private class MyExpansionListener implements ExpansionListener {
public void resetAllButSource(ExpansionEvent e) {
synchronized (viewList) {
for (ExpandableComputerView view : viewList) {
if (!view.equals(e.getSource())) {
// Not source, so collapse.
view.collapse();
view.setFocus(false);
}
}
}
}
public boolean fireInEventDispatchThread() {
return true;
}
}
private class MyComputersListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
collapseMyComputers = !collapseMyComputers;
rebuild(true);
}
}
private class FriendsListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
collapseFriends = !collapseFriends;
rebuild(true);
}
}
private class ConnectedLansListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
collapseConnectedLans = !collapseConnectedLans;
rebuild(true);
}
}
}