package eu.jucy.gui.search;
import helpers.GH;
import helpers.IObservable;
import helpers.SizeEnum;
import helpers.StatusObject;
import helpers.Observable.IObserver;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import logger.LoggerFactory;
import org.apache.log4j.Logger;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposalListener2;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import eu.jucy.gui.ApplicationWorkbenchWindowAdvisor;
import eu.jucy.gui.GUIPI;
import eu.jucy.gui.IImageKeys;
import eu.jucy.gui.ISearchableEditor;
import eu.jucy.gui.Lang;
import eu.jucy.gui.UCEditor;
import eu.jucy.gui.DownloadableColumn.ExactSize;
import eu.jucy.gui.DownloadableColumn.FileColumn;
import eu.jucy.gui.DownloadableColumn.Path;
import eu.jucy.gui.DownloadableColumn.SlotsSearchColumn;
import eu.jucy.gui.DownloadableColumn.TTHRoot;
import eu.jucy.gui.DownloadableColumn.Type;
import eu.jucy.gui.DownloadableColumn.UserWrapper;
import eu.jucy.gui.UserColumns.Connection;
import eu.jucy.gui.UserColumns.HubName;
import eu.jucy.gui.UserColumns.IPColumn;
import eu.jucy.gui.itemhandler.DownloadableHandlers.DownloadHandler;
import eu.jucy.gui.Application;
import uc.FavHub;
import uc.IHub;
import uc.crypto.HashValue;
import uc.files.IDownloadable;
import uc.files.MultiUserAbstractDownloadable;
import uc.files.search.ComparisonEnum;
import uc.files.search.FileSearch;
import uc.files.search.ISearchResult;
import uc.files.search.SearchType;
import uc.protocols.hub.Hub;
import uihelpers.ComboBoxViewer;
import uihelpers.DelayedTreeUpdater;
import uihelpers.SUIJob;
import uihelpers.TableViewerAdministrator;
/**
* An Editor providing the possibility to the user to search and
* view the corresponding search results
*
* @author Quicksilver
*
*/
public class SearchEditor extends UCEditor implements IObserver<StatusObject> , ISearchableEditor {
private static final Logger logger = LoggerFactory.make();
public static final String searchMenuID = "eu.jucy.gui.search";
static final List<SearchInfo> PAST_SEARCHES = new ArrayList<SearchInfo>();
public static final String ID = "eu.jucy.Search";
private TreeViewer treeViewer;
private volatile FileSearch current;
private Image showLeftIcon;
private Tree searchResultTable;
private Table hubsToSearch;
private ComboBoxViewer<SearchType> fileTypeComboViewer;
private ComboBoxViewer<SizeEnum> unitComboViewer;
private Text sizeText;
private ComboBoxViewer<ComparisonEnum> minmaxsizeComboViewer;
private Text searchForText;
private TableViewerAdministrator<IDownloadable> tva;
private DelayedTreeUpdater<IDownloadable> update;
private Button onlyWhereIAmOpButton;
private Button onlyUsersWithFreeSlotsButton;
private Button invertButton;
private Label itemCountLabel;
private Label fileCountLabel;
private boolean useCustomCancelImage = false;
private Image image = null;
private ToolItem item;
private boolean proposalClosed = true;
@Override
public void createPartControl(Composite parent) {
final GridLayout gridLayout_1 = new GridLayout();
parent.setLayout(gridLayout_1);
final SashForm sashForm = new SashForm(parent, SWT.NONE);
final Composite sideBarComposite = new Composite(sashForm, SWT.BORDER);
final GridLayout gridLayout_2 = new GridLayout();
gridLayout_2.numColumns = 1;
sideBarComposite.setLayout(gridLayout_2);
Group comp = new Group(sideBarComposite,SWT.NONE);
comp.setText(Lang.SearchFor);
comp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
final GridLayout gridLayout_search = new GridLayout();
gridLayout_search.numColumns = 2;
comp.setLayout(gridLayout_search);
searchForText = new Text(comp, SWT.SEARCH | SWT.ICON_CANCEL | SWT.ICON_SEARCH | SWT.BORDER);
searchForText.setMessage(String.format("%-25s", Lang.EnterSearch));
useCustomCancelImage = (searchForText.getStyle() & SWT.ICON_CANCEL) == 0; //if cancel style does not work on text..
if (useCustomCancelImage ) {
image = AbstractUIPlugin.imageDescriptorFromPlugin(Application.PLUGIN_ID, IImageKeys.STOPICON).createImage();
ToolBar toolBar = new ToolBar (comp, SWT.FLAT);
item = new ToolItem (toolBar, SWT.PUSH);
item.setImage(image);
item.setEnabled(false);
item.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
cancel();
}
});
toolBar.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
}
searchForText.addKeyListener(new KeyAdapter() {
public void keyPressed(final KeyEvent e) {
if ((e.keyCode == SWT.KEYPAD_CR ||e.keyCode == SWT.CR) && proposalClosed) {
String search = searchForText.getText().trim();
e.doit = false;
if (search.length() > 2) {
startSearch( search );
}
}
}
});
searchForText.addSelectionListener(new SelectionAdapter() {
public void widgetDefaultSelected(SelectionEvent e) {
if (e.detail == SWT.ICON_CANCEL) {
cancel();
}
}
});
SearchInfoProvider sip = new SearchInfoProvider();
ContentProposalAdapter cpa = new ContentProposalAdapter(
searchForText,sip,sip,
KeyStroke.getInstance(KeyStroke.NO_KEY,SWT.ARROW_DOWN ),null);
cpa.setPopupSize(new Point(500, 400));
cpa.addContentProposalListener(new IContentProposalListener2() {
public void proposalPopupClosed(ContentProposalAdapter adapter) {
new SUIJob() { //done in UIjob so it is postponed until the key listener is called (Enter Key that was used for closing the proposal)
@Override
public void run() {
proposalClosed = true;
}
}.schedule();
logger.debug("proposal popup closed");
}
public void proposalPopupOpened(ContentProposalAdapter adapter) {
proposalClosed = false;
logger.debug("proposal popup opened");
}
});
GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false, useCustomCancelImage? 1 : 2 , 1);
searchForText.setLayoutData(gd);
final Label distanceholder = new Label(sideBarComposite, SWT.NONE);
distanceholder.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
Group sizeComp = new Group(sideBarComposite,SWT.NONE);
sizeComp.setText(Lang.Size);
sizeComp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
final GridLayout gridLayout_size = new GridLayout();
gridLayout_size.numColumns = 3;
sizeComp.setLayout(gridLayout_size);
Combo minmaxsizeCombo = new Combo(sizeComp, SWT.READ_ONLY);
minmaxsizeCombo.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
minmaxsizeComboViewer = new ComboBoxViewer<ComparisonEnum>(minmaxsizeCombo,ComparisonEnum.values());
minmaxsizeComboViewer.select(ComparisonEnum.ATLEAST);
sizeText = new Text(sizeComp, SWT.BORDER);
sizeText.setTextLimit(15);
sizeText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
Combo unitCombo = new Combo(sizeComp, SWT.READ_ONLY);
unitComboViewer = new ComboBoxViewer<SizeEnum>(unitCombo,SizeEnum.values());
unitComboViewer.select(SizeEnum.B);
unitCombo.setVisibleItemCount(SizeEnum.values().length);
unitCombo.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
final Label distanceholder2 = new Label(sideBarComposite, SWT.NONE);
distanceholder2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
Group fileType = new Group(sideBarComposite,SWT.NONE);
fileType.setText(Lang.FileType);
final GridLayout gridLayout_fileType= new GridLayout();
gridLayout_fileType.numColumns = 1;
fileType.setLayout(gridLayout_fileType);
fileType.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
//create combo for choosing the filetype of the search and fill it with the matching Enums
Combo fileTypeCombo = new Combo(fileType, SWT.READ_ONLY);
fileTypeCombo.setVisibleItemCount(SearchType.values().length);
fileTypeCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
fileTypeComboViewer = new ComboBoxViewer<SearchType>(fileTypeCombo,SearchType.getNMDCSearchTypes());
fileTypeComboViewer.select(SearchType.ANY);
final Label distanceholder3 = new Label(sideBarComposite, SWT.NONE);
distanceholder3.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
Group searchOptions = new Group(sideBarComposite,SWT.NONE);
final GridLayout gridLayout_options = new GridLayout();
gridLayout_options.numColumns = 1;
searchOptions.setLayout(gridLayout_options);
searchOptions.setText(Lang.SearchOptions);
searchOptions.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
onlyUsersWithFreeSlotsButton = new Button(searchOptions, SWT.CHECK);
onlyUsersWithFreeSlotsButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
onlyUsersWithFreeSlotsButton.setText(Lang.OnlyUsersWithFreeSlots);
onlyWhereIAmOpButton = new Button(searchOptions, SWT.CHECK);
onlyWhereIAmOpButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
onlyWhereIAmOpButton.setText(Lang.OnlyWhereIAmOp);
invertButton = new Button(searchOptions, SWT.PUSH);
invertButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
invertButton.setText(Lang.InvertSelection);
invertButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
for (TableItem ti:hubsToSearch.getItems()) {
ti.setChecked(!ti.getChecked());
}
}
});
Composite comptable= new Composite(sideBarComposite,SWT.NONE);
comptable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
comptable.setLayout(new FillLayout());
hubsToSearch = new Table(comptable, SWT.V_SCROLL | SWT.BORDER | SWT.CHECK);
TableColumn tc = new TableColumn(hubsToSearch,SWT.NONE);
Map<FavHub,Hub> hubs = ApplicationWorkbenchWindowAdvisor.get().getHubs();
List<FavHub> fHubs = new ArrayList<FavHub>(hubs.keySet());
Collections.sort(fHubs);
for (FavHub e: fHubs ) {
IHub hub = hubs.get(e);
if (hub != null && !hub.getFavHub().isChatOnly()) {
TableItem ti = new TableItem(hubsToSearch,SWT.LEAD);
ti.setData(hub);
String name = hub.getName();
if (GH.isNullOrEmpty(name)) {
name = hub.getFavHub().getSimpleHubaddy();
}
ti.setText(name);
ti.setChecked(true);
}
}
tc.pack();
hubsToSearch.pack();
comptable.pack();
treeViewer = new TreeViewer(sashForm, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER);
searchResultTable = treeViewer.getTree();
searchResultTable.setHeaderVisible(true);
treeViewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
executeCommand(DownloadHandler.ID);
}
});
tva = new TableViewerAdministrator<IDownloadable>(treeViewer,
Arrays.asList(new FileColumn(),UserWrapper.createUserColumn(), new Type(),
new ExactSize(),new SlotsSearchColumn(),new Path(),
new UserWrapper(new Connection()),new UserWrapper(new HubName()),
new UserWrapper(new IPColumn()),new TTHRoot())
,GUIPI.searchEditorTable,TableViewerAdministrator.NoSorting);
tva.apply();
onlyUsersWithFreeSlotsButton.addSelectionListener(new SelectionAdapter() {
boolean onlyUsersWithFreeSlots = false;
ViewerFilter filter = new ViewerFilter() {
@Override
public boolean select(Viewer viewer, Object parentElement,Object element) {
if (onlyUsersWithFreeSlots) {
if (element instanceof ISearchResult) {
ISearchResult sr = (ISearchResult)element;
return sr.getAvailabelSlots() > 0;
}
}
return true;
}
};
{
treeViewer.addFilter(filter);
}
@Override
public void widgetSelected(SelectionEvent e) {
onlyUsersWithFreeSlots = onlyUsersWithFreeSlotsButton.getSelection();
treeViewer.refresh();
}
});
SearchTreeProvider stp = new SearchTreeProvider();
treeViewer.setContentProvider(stp);
sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
sashForm.setWeights(new int[] {150, 350 });
final Composite composite = new Composite(parent, SWT.BORDER);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
final GridLayout gridLayout = new GridLayout();
gridLayout.verticalSpacing = 0;
gridLayout.marginWidth = 1;
gridLayout.marginHeight = 1;
gridLayout.horizontalSpacing = 2;
gridLayout.numColumns = 3;
composite.setLayout(gridLayout);
// final Button showSearchOptions = new Button(composite, SWT.CHECK);
// showSearchOptions.setSelection(true);
// showSearchOptions.addSelectionListener(new SelectionAdapter() {
// private int[] weights = new int[] {130, 430 };
// private boolean enable = true;
// public void widgetSelected(final SelectionEvent e) {
// if (enable != showSearchOptions.getSelection()) {
// enable = !enable;
// if (showSearchOptions.getSelection()) {
// sashForm.setWeights(weights);
// } else {
// weights = sashForm.getWeights();
// sashForm.setWeights(new int[]{0,500});
// }
// }
// }
// });
//
// showSearchOptions.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
// showSearchOptions.setText(Lang.ShowSidebar);
showLeftIcon = AbstractUIPlugin.imageDescriptorFromPlugin(
Application.PLUGIN_ID, IImageKeys.SHOW_LEFT_SIDEVIEW).createImage();
ToolBar toolBar = new ToolBar (composite, SWT.HORIZONTAL | SWT.SHADOW_OUT | SWT.FLAT);
final ToolItem showOptions = new ToolItem (toolBar, SWT.CHECK);
showOptions.setImage(showLeftIcon);
showOptions.addSelectionListener(new SelectionAdapter() {
private int[] weights = null;
public void widgetSelected(final SelectionEvent e) {
if (!showOptions.getSelection()) {
sashForm.setWeights(weights);
} else {
weights = sashForm.getWeights();
sashForm.setWeights(new int[]{0,500});
}
}
});
toolBar.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
itemCountLabel = new Label(composite, SWT.BORDER);
final GridData gridData = new GridData(SWT.LEFT, SWT.FILL, false, false);
gridData.widthHint = 120;
itemCountLabel.setLayoutData(gridData);
fileCountLabel = new Label(composite, SWT.BORDER);
final GridData gridData_1 = new GridData(SWT.LEFT, SWT.FILL, false, false);
gridData_1.widthHint = 100;
fileCountLabel.setLayoutData(gridData_1);
//
update = new DelayedTreeUpdater<IDownloadable>(treeViewer) {
@Override
protected void updateDone() {
updateLabels();
}
};
// makeActions();
getSite().setSelectionProvider(treeViewer);
createContextPopup(searchMenuID, treeViewer);
if (getInitialSearch() != null) {
//set TTH as type..
fileTypeComboViewer.select(SearchType.TTH);
String s = getInitialSearch().toString();
searchForText.setText(s);
startSearch(s);
} else if (alternateInitialSearch() != null) {
fileTypeComboViewer.select(SearchType.ANY);
String s = alternateInitialSearch();
searchForText.setText(s);
startSearch(s);
}
hubsToSearch.pack();
sideBarComposite.pack();
sashForm.pack();
setControlsForFontAndColour(treeViewer.getControl(),searchForText,
sizeText,unitCombo,minmaxsizeCombo,hubsToSearch);
}
private String lastSearch;
private int searchIndex = -1;
private List<ISearchResult> found;
public void next() {
if (found != null && !found.isEmpty()) {
searchIndex++;
ISearchResult current = found.get(searchIndex % found.size());
treeViewer.setSelection(new StructuredSelection(current), true);
}
}
public void search(String searchstring) {
if (!searchstring.equals(lastSearch)) {
lastSearch = searchstring;
searchIndex = -1;
if (current != null) {
found = current.searchSubset(lastSearch);
}
}
next();
}
private HashValue getInitialSearch() {
return ((SearchEditorInput)getEditorInput()).getInitialsearch();
}
private String alternateInitialSearch() {
return ((SearchEditorInput)getEditorInput()).getAlternate();
}
/**
* checks if the user has entered a max or minsize ..
* returns -1 if nothing is set in sizeText or set value is not an integer
*
* @return the max or minsize set
*/
private long getSearchSize(){
long size = -1;
//check if a max size is set
String sizestring = sizeText.getText().trim();
if (sizestring.length() > 0) {
try {
size = new Long(sizestring);
if( size < 0 ) {
size = -1;
} else {
size = unitComboViewer.getSelected().getInBytes(size);
}
} catch(NumberFormatException nfe){
size = -1; //no legal number...
}
}
return size;
}
/**
* reads in the values from the widgets and then sends a search to the hub
* @param searchString a searchstring the user provided
*/
private void startSearch(String searchstring) {
//unregister last search
cancel();
//create new search and register.. with the DCClient then register this viewer with the search
current = new FileSearch(searchstring,fileTypeComboViewer.getSelected()
,minmaxsizeComboViewer.getSelected(),getSearchSize());
ApplicationWorkbenchWindowAdvisor.get().register(current);
current.addObserver(this);
//add the search as input to the input provider..
treeViewer.setInput(current);
//search in the marked hubs
Set<IHub> usedHubs = new HashSet<IHub>();
boolean onlyHubsWhereOp = onlyWhereIAmOpButton.getSelection();
for (TableItem ti : hubsToSearch.getItems()) {
Object o = ti.getData();
if (ti.getChecked() && o instanceof IHub ) {
logger.debug("sending seach to hub: "+ ((IHub)o).getName());
IHub hub =(IHub)o;
if ( (!onlyHubsWhereOp || hub.getSelf().isOp()) && !hub.getFavHub().isChatOnly() ) {
usedHubs.add(hub);
}
}
}
ApplicationWorkbenchWindowAdvisor.get().search(current, usedHubs);
setPartName(Lang.Search + " - "+searchstring);
fireTopicChangedListeners();
updateCancelImageIfNeeded();
}
private void cancel() {
if (current != null) { // cancels current search and clears view
PAST_SEARCHES.add(0,
new SearchInfo(
current.getSearchString(),
current.getNrOfResults(),
current.getNrOfFiles(),
current.getSearchType()));
ApplicationWorkbenchWindowAdvisor.get().unregister(current);
current.deleteObserver(this);
current = null;
update.clear();
if (treeViewer.getContentProvider() != null) { //might not be set..
treeViewer.setInput(new Object());
}
updateCancelImageIfNeeded();
updateLabels();
}
}
private void updateCancelImageIfNeeded() {
if (useCustomCancelImage && !item.isDisposed()) {
if (current == null) {
item.setEnabled(false);
} else {
item.setEnabled(true);
item.setImage(image);
}
}
}
private void updateLabels() {
if (!itemCountLabel.isDisposed() && !fileCountLabel.isDisposed()) {
if (current == null) {
itemCountLabel.setText("");
fileCountLabel.setText("");
} else {
itemCountLabel.setText( String.format(Lang.ReceivedResults, current.getNrOfResults()));
fileCountLabel.setText( String.format(Lang.DifferentFiles , current.getNrOfFiles()));
}
}
}
public void update(IObservable<StatusObject> o, StatusObject arg) {
update.put(arg.getType(), (ISearchResult)arg.getValue(), arg.getDetailObject());
}
@Override
public void setFocus() {
searchForText.setFocus();
}
/**
* on disposing unregister any current search
*
*/
public void dispose() {
cancel();
super.dispose();
if (image != null) {
image.dispose();
}
if (showLeftIcon != null) {
showLeftIcon.dispose();
}
}
@Override
public String getTopic() {
String text = getPartName();
if (current != null) {
text += " - "+ current.getSearchString();
}
return text;
}
public static class SearchTreeProvider implements ITreeContentProvider {
public Object[] getElements(Object inputElement) {
if(inputElement instanceof FileSearch){
FileSearch sr = (FileSearch)inputElement;
return sr.getResults().toArray();
}
return new Object[0];
}
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof MultiUserAbstractDownloadable) {
return ((MultiUserAbstractDownloadable)parentElement).getFiles().toArray();
}
return new Object[0];
}
public Object getParent(Object element) {
return null;
}
public boolean hasChildren(Object element) {
return ((IDownloadable)element).nrOfUsers() > 1;
}
public void dispose() {}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {}
}
}