/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander 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; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.ui.quicklist.item;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mucommander.ui.icon.IconManager;
import com.mucommander.ui.main.WindowManager;
import com.mucommander.ui.main.table.CellLabel;
import com.mucommander.ui.quicklist.QuickListWithDataList;
import com.mucommander.ui.quicksearch.QuickSearch;
import com.mucommander.ui.theme.ColorChangedEvent;
import com.mucommander.ui.theme.FontChangedEvent;
import com.mucommander.ui.theme.ThemeCache;
import com.mucommander.ui.theme.ThemeData;
import com.mucommander.ui.theme.ThemeListener;
import com.mucommander.ui.theme.ThemeManager;
/**
* This class represent a data list for FileTablePopupWithDataList.
*
* @author Arik Hadas
*/
public class QuickListDataList<T> extends JList {
private static final Logger LOGGER = LoggerFactory.getLogger(QuickListDataList.class);
private final static int VISIBLE_ROWS_COUNT = 10;
private QuickSearch<T> quickSearch = new QuickListQuickSearch();
private Component nextFocusableComponent;
public QuickListDataList(Component nextFocusableComponent){
this.nextFocusableComponent = nextFocusableComponent;
setFocusTraversalKeysEnabled(false);
addMouseListenerToList();
DataListItemRenderer itemRenderer = getItemRenderer();
ThemeManager.addCurrentThemeListener(itemRenderer);
setCellRenderer(itemRenderer);
}
public QuickListDataList(T[] data) {
this(new Component() {});
setListData(data);
}
protected DataListItemRenderer getItemRenderer() {
return new DataListItemRenderer();
}
public QuickSearch<T> getQuickSearch() {
return quickSearch;
}
public String getItemAsString(T item) {
return ""+item;
}
/**
* This function is called before showing TablePopupWithDataList.
* It does the required steps before the popup is shown.
*/
@Override
public void setListData(Object[] data) {
super.setListData(data);
int numOfRowsInList = getModel().getSize();
if (numOfRowsInList > 0) {
setVisibleRowCount(Math.min(numOfRowsInList, VISIBLE_ROWS_COUNT));
setSelectedIndex(0);
ensureIndexIsVisible(0);
}
}
protected T getListItem(int index) {
if (index > getModel().getSize() || index < 0)
return null;
return (T) getModel().getElementAt(index);
}
protected void addMouseListenerToList() {
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
// If there was double click on item of the popup's list,
// select it, and update the text component.
if (e.getClickCount() == 2) {
int index = locationToIndex(e.getPoint());
setSelectedIndex(index);
((QuickListWithDataList)(getParent().getParent().getParent())).itemSelected(getSelectedValue());
}
}
});
}
public void setForegroundColors(Color foreground, Color selectedForeground) {
DataListItemRenderer cellRenderer = (DataListItemRenderer) getCellRenderer();
cellRenderer.setItemForeground(foreground);
cellRenderer.setSelectedItemForeground(selectedForeground);
}
public void setBackgroundColors(Color background, Color selectedBackground) {
DataListItemRenderer cellRenderer = (DataListItemRenderer) getCellRenderer();
cellRenderer.setItemBackgound(background);
cellRenderer.setSelectedItemBackgound(selectedBackground);
}
/**
*
*/
protected class DataListItemRenderer extends DefaultListCellRenderer implements ThemeListener {
private Color selectedItemBackgound = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR);
private Color selectedItemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR);
private Color itemBackgound = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR);
private Color itemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR);
private Font itemFont = ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_ITEM_FONT);
protected DataListItemRenderer() { }
@Override
public Component getListCellRendererComponent(JList list, Object value, int rowIndex, boolean isSelected, boolean cellHasFocus) {
// Let superclass deal with most of it...
super.getListCellRendererComponent(list, value, rowIndex, isSelected, cellHasFocus);
T item = getListItem(rowIndex);
// Sanity check.
if(item==null) {
LOGGER.debug("tableModel.getCachedFileAtRow("+ rowIndex +") RETURNED NULL !");
return null;
}
QuickSearch<T> search = QuickListDataList.this.getQuickSearch();
boolean matches = search.isActive() ? search.matches(getItemAsString(item)) : true;
CellLabel label = new CellLabel();
label.setFont(itemFont);
label.setText(getItemAsString(item));
//label.setToolTipText(""+item);
// Set background color depending on whether the row is selected or not, and whether the table has focus or not
if (isSelected) {
label.setBackground(selectedItemBackgound);
label.setForeground(selectedItemForeground);
}
else {
label.setBackground(matches ? itemBackgound : ThemeCache.unmatchedBackground);
label.setForeground(matches ? itemForeground : ThemeCache.unmatchedForeground);
}
return label;
}
public void setSelectedItemBackgound(Color selectedItemBackgound) {
this.selectedItemBackgound = selectedItemBackgound;
}
public void setSelectedItemForeground(Color selectedItemForeground) {
this.selectedItemForeground = selectedItemForeground;
}
public void setItemBackgound(Color itemBackgound) {
this.itemBackgound = itemBackgound;
}
public void setItemForeground(Color itemForeground) {
this.itemForeground = itemForeground;
}
//////////////////////////////////
// ThemeListener implementation //
//////////////////////////////////
public void colorChanged(ColorChangedEvent event) {
if (event.getColorId() == ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR)
itemBackgound = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR);
else if (event.getColorId() == ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR)
itemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR);
else if (event.getColorId() == ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR)
selectedItemBackgound= ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR);
else if (event.getColorId() == ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR)
selectedItemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR);
}
public void fontChanged(FontChangedEvent event) {
itemFont = ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_ITEM_FONT);
}
}
/**
*
*/
public class QuickListQuickSearch extends QuickSearch<T> {
public QuickListQuickSearch() {
super(QuickListDataList.this);
}
@Override
protected void searchStarted() {
}
@Override
protected void searchStopped() {
WindowManager.getCurrentMainFrame().getStatusBar().updateSelectedFilesInfo();
QuickListDataList.this.repaint();
}
@Override
protected int getNumOfItems() {
return QuickListDataList.this.getModel().getSize();
}
@Override
protected String getItemString(int index) {
return getItemAsString(getListItem(index));
}
@Override
protected void searchStringBecameEmpty(String searchString) {
WindowManager.getCurrentMainFrame().getStatusBar().setStatusInfo(searchString); // TODO: is needed?
}
@Override
protected void matchFound(int row, String searchString) {
if(row!=getSelectedIndex()) {
setSelectedIndex(row);
ensureIndexIsVisible(row);
}
// Display the new search string in the status bar
// that indicates that the search has yielded a match
WindowManager.getCurrentMainFrame().getStatusBar().setStatusInfo(searchString, IconManager.getIcon(IconManager.STATUS_BAR_ICON_SET, QUICK_SEARCH_OK_ICON), false);
}
@Override
protected void matchNotFound(String searchString) {
// No file matching the search string, display the new search string with an icon
// that indicates that the search has failed
WindowManager.getCurrentMainFrame().getStatusBar().setStatusInfo(searchString, IconManager.getIcon(IconManager.STATUS_BAR_ICON_SET, QUICK_SEARCH_KO_ICON), false);
}
@Override
public synchronized void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
// If quick search is not active...
if (!isActive()) {
// Return (do not start quick search) if the key is not a valid quick search input
if(!isValidQuickSearchInput(e)) {
if (keyCode == KeyEvent.VK_ESCAPE || keyCode == KeyEvent.VK_ENTER)
tryToTransferFocusToTheNextComponent();
if (keyCode == KeyEvent.VK_ENTER)
((QuickListWithDataList)(getParent().getParent().getParent())).itemSelected(getSelectedValue());
return;
}
// Start the quick search and continue to process the current key event
start();
}
// At this point, quick search is active
boolean keyHasModifiers = (e.getModifiersEx()&(KeyEvent.SHIFT_DOWN_MASK|KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK|KeyEvent.META_DOWN_MASK))!=0;
// Backspace removes the last character of the search string
if(keyCode==KeyEvent.VK_BACK_SPACE && !keyHasModifiers) {
// Search string is empty already
if(isSearchStringEmpty())
return;
removeLastCharacterFromSearchString();
// Find the row that best matches the new search string and select it
findMatch(0, true, true);
}
// Escape immediately cancels the quick search
else if(keyCode==KeyEvent.VK_ESCAPE && !keyHasModifiers) {
stop();
}
// Up/Down jumps to previous/next match
// Shift+Up/Shift+Down marks currently selected file and jumps to previous/next match
else if((keyCode==KeyEvent.VK_UP || keyCode==KeyEvent.VK_DOWN) && !keyHasModifiers) {
// Find the first row before/after the current row that matches the search string
boolean down = keyCode==KeyEvent.VK_DOWN;
findMatch(getSelectedIndex() + (down ? 1 : -1), down, false);
}
// If no modifier other than Shift is pressed and the typed character is not a control character (space is ok)
// and a valid Unicode character, add it to the current search string
else if(isValidQuickSearchInput(e)) {
appendCharacterToSearchString(e.getKeyChar());
// Find the row that best matches the new search string and select it
findMatch(0, true, true);
}
else {
switch(e.getKeyCode()) {
case KeyEvent.VK_UP:
{
int numOfItems = getModel().getSize();
if (numOfItems > 0 && getSelectedIndex() == 0) {
setSelectedIndex(numOfItems - 1);
ensureIndexIsVisible(numOfItems - 1);
e.consume();
}
}
break;
case KeyEvent.VK_DOWN:
{
int numOfItems = getModel().getSize();
if (numOfItems > 0 && getSelectedIndex() == numOfItems - 1) {
setSelectedIndex(0);
ensureIndexIsVisible(0);
e.consume();
}
}
break;
case KeyEvent.VK_ENTER:
tryToTransferFocusToTheNextComponent();
((QuickListWithDataList)(getParent().getParent().getParent())).itemSelected(getSelectedValue());
stop();
break;
case KeyEvent.VK_TAB:
tryToTransferFocusToTheNextComponent();
stop();
break;
}
// Do not update last search string's change timestamp
return;
}
// Update last search string's change timestamp
setLastSearchStringChange(System.currentTimeMillis());
e.consume();
}
private void tryToTransferFocusToTheNextComponent() {
if (nextFocusableComponent != null)
nextFocusableComponent.requestFocus();
}
}
}