view src/econnect/wp3_3/client/widgets/table/DynamicStiTable.java @ 30:1e95995ddbb2

re-added the ability to refine the dataset by a (very simple) text search
author Sebastian Kruse <skruse@mpiwg-berlin.mpg.de>
date Wed, 28 Nov 2012 17:01:34 +0100
parents 3184e8faa5c0
children 20eb7596d466
line wrap: on
line source

package econnect.wp3_3.client.widgets.table;

import java.util.ArrayList;

import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Hidden;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler; 
import com.google.gwt.event.dom.client.MouseOutHandler; 
import com.google.gwt.http.client.URL;

import econnect.wp3_3.client.core.ApplicationConstants;
import econnect.wp3_3.client.core.DataObject;
import econnect.wp3_3.client.core.DataSet;
import econnect.wp3_3.client.core.StiConstants;
import econnect.wp3_3.client.core.StiCore;
import econnect.wp3_3.client.services.ExportWriterInterface;
import econnect.wp3_3.client.services.ExportWriterInterfaceAsync;

/**
 * Implementation of a dynamic table for one dataset
*/
public class DynamicStiTable extends Grid {

	/**
     * The maximum number of columns of the table 
    */
	private int maxCols = 4;

	/**
     * The maximum number of rows of the table 
    */
	private int maxRows = 10;
	
	/**
     * The sti core component 
    */
	private StiCore core;

	/**
     * The dataset of this dynamic table 
    */
	private DataSet dataSet;
	
	/**
     * The index of the dataset 
    */
	private int index;
	
	/**
     * The dataobjects, which are presented at the actual page 
    */
	private DataObject[][] displayedObjects;

	/**
     * The visual representations of the actual displayed objects 
    */
	private Grid elements;
	
	/**
     * The number of chunks (pages) for the table 
    */
	private int chunkCount;

	/**
     * The actual selected chunk (page) 
    */
	private int selectedChunk = 0;

	/**
     * The grid for the page links 
    */
	private Grid pages;

	/**
     * The number-of-results label 
    */
	private Label results;

	/**
     * The image for the previous page 
    */
	private Image previous;

	/**
     * The image for the next page 
    */
	private Image next;
	
	/**
     * The image for show selected elements 
    */
	private Image showSelected;
	
	/**
     * The image for show all elements 
    */
	private Image showAll;

	/**
     * This value tells the view status: 1 for showAll, -1 for showSelected, 0 otherwise 
    */
	private int viewAll = 1;
	
	/**
     * ArrayList that holds all elements that can be shown 
    */	
	private ArrayList<DataObject> actualObjectSet;
	
	/**
     * Anchor that holds the URL for the KML download
    */	
	private Anchor aDownloadKML;
	
	/**
     * RPC Interface for writing the KML file
    */	
	private ExportWriterInterfaceAsync exportWriter;
	
	/**
     * Constructor for initialization of the dynamic table
     * 
     * @param core object to allow interaction with all javascript components
     * @param id id of the dataset
    */
	public DynamicStiTable( StiCore stiCore, final int id ){
		
		final ApplicationConstants constants = (ApplicationConstants) GWT.create(ApplicationConstants.class);
		final StiConstants textConstants = (StiConstants) GWT.create(StiConstants.class);
		
		this.resize(2,1);
		this.elements = new Grid();
		this.addStyleName("dataTable");
		this.addStyleName("center");
		
		this.index = id;
		this.core = stiCore;
		this.dataSet = core.getDataSets().get(index);
		setActualObjectSet();
		
		previous = new Image( "images/prev.png" );
		previous.setStyleName("arrow");
		next = new Image( "images/next.png" );
		next.setStyleName("arrow");		

		pages = new Grid(0,0);
		organizePages();
		
		previous.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				selectedChunk--;
				organizePages();
				checkHide();
				fillTable();
			}
		});
		previous.setVisible(false);
		next.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				selectedChunk++;
				organizePages();
				checkHide();
				fillTable();
			}
		});
		if( chunkCount == 1 ){
			next.setVisible(false);
		}		
		
		showAll =  new Image("images/viewAll"+(core.getColorId(index))+".png");
		showAll.setTitle(textConstants.showAll());
		showSelected =  new Image("images/viewSelected"+(core.getColorId(index))+".png");
		showSelected.setTitle(textConstants.showSelected());
		showAll.addStyleName("selectedView");
		showAll.addStyleName("view");
		showSelected.addStyleName("view");
		showAll.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				if( viewAll != 1 ){
					showSelected.removeStyleName("selectedView");
					showAll.addStyleName("selectedView");
					viewAll = 1;
					setActualObjectSet();
					organizePages();
					checkHide();
					fillTable();
				}
			}
		});
		showSelected.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				if( viewAll != -1 ){
					viewAll = -1;
					if( setActualObjectSet() ){
						showAll.removeStyleName("selectedView");
						showSelected.addStyleName("selectedView");
						organizePages();
						checkHide();
						fillTable();
					}
					else {
						viewAll = 1;
					}
				}
			}
		});
		
		Image storeSelected = new Image("images/storeSelected"+(core.getColorId(index))+".png");
		storeSelected.setTitle(textConstants.storeSelected());
		storeSelected.addStyleName("view");		
		storeSelected.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				core.storeSelected(id);
			}
		});
		
		Grid showOptions = new Grid(1,3);
		showOptions.setWidget(0,0,showAll);
		showOptions.setWidget(0,1,showSelected);
		showOptions.setWidget(0,2,storeSelected);
		
		final TextBox textualSearch = new TextBox();
		final Button search = new Button("go");
		final Image cancelImage = new Image(constants.cancelImage());
		final Image refineImage = new Image(constants.refineImage());
		final Grid buttons = new Grid(1,2);
		buttons.setWidget(0, 0, refineImage);
		buttons.setWidget(0, 1, cancelImage);
		final Grid refine = new Grid(1,3);
		refine.setWidget(0,0,textualSearch);
		refine.setWidget(0,1,search);
		search.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				if( !textualSearch.getText().equals("") ){
					refineByText(textualSearch.getText());
					textualSearch.setEnabled(false);
					refine.setWidget(0, 1, buttons);
				}
			}
		});
		refineImage.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {				
				core.refine();
			}
		});
		cancelImage.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				textualSearch.setEnabled(true);
				core.reset();
				
				//TODO: das gibt es nun mehrfach im Code, sollte also in eine Funktion
				//Ÿberfuhrt werden
				showSelected.removeStyleName("selectedView");
				showAll.addStyleName("selectedView");
				viewAll = 1;
				setActualObjectSet();
				organizePages();
				checkHide();
				fillTable();
				
				refine.setWidget(0, 1, search);
			}
		});
		
		this.results = new Label();	
		this.results.setStyleName("resultsLabel");
		
		Label page = new Label(textConstants.page()+":");
		page.setStyleName("pageLabel");
		
		Grid footer = new Grid(1,4);
		footer.setWidget(0, 0, page );
		footer.setWidget(0, 1, pages );
		footer.setWidget(0, 2, previous);
		footer.setWidget(0, 3, next);
		footer.addStyleName("center");
		
		
		//This anchor will hold the URL of the export-file
		//for download from the ExportServlet
		aDownloadKML = new Anchor("download KML");
		aDownloadKML.setVisible(false);
		aDownloadKML.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				aDownloadKML.setVisible(false);
			}
		});
		
		Image export = new Image(constants.exportImage());
		export.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				//create KML file
				createKMLFile();
			}
		});
		export.addStyleName("export");
		export.setTitle(textConstants.exportDataSet());

		Image delete = new Image(constants.deleteImage());
		delete.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				core.deleteDataSet(index);
			}
		});
		delete.addStyleName("delete");
		delete.setTitle(textConstants.deleteDataSet());
		
		Grid func = new Grid(1,3);

		func.setWidget(0, 0, aDownloadKML);
		func.setWidget(0, 1, export);
		func.setWidget(0, 2, delete);
		func.addStyleName("center");
		
		Grid tableControls = new Grid(1,5);
		tableControls.setWidget(0,0,showOptions);
		tableControls.setWidget(0,1,refine);
		tableControls.setWidget(0,2,this.results);
		tableControls.setWidget(0,3,footer);
		tableControls.setWidget(0,4,func);
		tableControls.setStyleName("tableControls");
		
		this.setWidget(0, 0, tableControls);

		tableControls.getCellFormatter().setHorizontalAlignment(0, 0, HasHorizontalAlignment.ALIGN_CENTER);

		this.getCellFormatter().setHorizontalAlignment(0, 0, HasHorizontalAlignment.ALIGN_CENTER);
		this.getCellFormatter().setHorizontalAlignment(1, 0, HasHorizontalAlignment.ALIGN_CENTER);
		this.getCellFormatter().setWidth(1, 0, "100%");
		
		fillTable();

	}
	
    /**
     * Sets the actual object set; depends on the showAll and showSelected selection
     * 
     * @return boolean value, if there are selected objects
    */
	private boolean setActualObjectSet(){
		if( viewAll == -1 ){
			ArrayList<DataObject> objects = new ArrayList<DataObject>();
			for( int i=0; i<this.dataSet.getObjects().length(); i++){
				DataObject object = this.dataSet.getObjects().get(i);
				if( object.isSelected() ){
					objects.add(object);
				}
			}
			if( objects.size() > 0 ){
				//TODO: Wenn die derzeitige Auswahl _keine_ Elemente
				//enthŠlt, werden _alle_ angezeigt. Ist das sinnvoll?
				actualObjectSet = objects;
			}
			else {
				return false;
			}
		}
		else if( viewAll == 1 ){
			actualObjectSet = new ArrayList<DataObject>();
			for( int i=0; i<this.dataSet.getObjects().length(); i++){
				actualObjectSet.add(this.dataSet.getObjects().get(i));	
			}
		}
		this.chunkCount = actualObjectSet.size() / ( maxCols * maxRows );
		if( actualObjectSet.size() % ( maxCols * maxRows ) != 0 ){
			this.chunkCount++;
		}
		return true;
	}
	
    /**
     * Organizes the visible links in the pages grid
    */
	private void organizePages(){
		ArrayList<Label> pageLabels = new ArrayList<Label>();
		for( int i=1; i<this.chunkCount+1; i++ ){
			final Label page = new Label(""+i);
			page.addClickHandler(new ClickHandler() {
				public void onClick(ClickEvent event) {					
					selectedChunk = Integer.parseInt(page.getText())-1;
					checkHide();
					fillTable();
					updateView(false);
					organizePages();
				}
			});
			pageLabels.add(page);
		}	
		int max = 10;
		if( this.chunkCount<10 ){
			max = this.chunkCount;
		}
		pages.resize(1,max);
		int start = selectedChunk - 4;
		int rest = max - 5;
		if( start < 0 ){
			rest += (-1)*start;
			start = 0;	
		}
		int end = selectedChunk + rest;
		if( end > this.chunkCount - 1 ){
			start -= end - this.chunkCount + 1;
			end = this.chunkCount - 1;
		}
		if( start < 0 ){
			start = 0;	
		}
		int j=0;
		for( int i=start; i<end+1; i++ ){
			Label page = (Label) pageLabels.get(i);
			if( selectedChunk == Integer.parseInt(page.getText())-1 ){
				page.setStyleName("selectedPage");
			}
			else {
				page.setStyleName("page");
			}
			pages.setWidget(0,j,page);
			j++;
		}
	}
	
    /**
     * Fills the table cells with content. It depends on the actual chosen chunk (page) and the objects in the actual object set.
    */
	private void fillTable(){				
		this.elements.resize(0,0);
		this.elements.resize(this.maxRows, this.maxCols);
		this.displayedObjects = new DataObject[this.maxRows][this.maxCols];
		this.setWidget(1, 0, this.elements);
		int start = this.selectedChunk * ( this.maxCols * this.maxRows );
		int end = start + ( this.maxCols * this.maxRows );
		if( end > this.actualObjectSet.size() ){
			end = this.actualObjectSet.size();
		}
		this.results.setText("Results "+(start+1)+" - "+end+" of "+this.actualObjectSet.size());
		int row = 0, col = 0;
		for( int i=start; i<end; i++){	
			DataObject object = (DataObject) this.actualObjectSet.get(i);
			String oName = object.getName();
			if( oName.length() > 60 ){
				oName = oName.substring(0, 57);
				oName += "...";
			}
			Label name = new Label(oName);
			Label place = new Label(object.getPlace());
			Label time = new Label(object.getTimeString());
			final Grid infoGrid = new Grid(3,1);
			infoGrid.setWidget(0, 0, name);
			infoGrid.setWidget(1, 0, place);
			infoGrid.setWidget(2, 0, time);
			if( !object.getDescription().equals("") ){
				infoGrid.resize(4,1);
				HTML description = new HTML(object.getDescription());
				description.addStyleName("description");
				infoGrid.setWidget(3, 0, description);
			}
			infoGrid.addStyleName("unselectedCellInner");
			name.addStyleName("nameLabel");
			place.addStyleName("dataLabel");
			time.addStyleName("dataLabel");
			HTML text = new HTML();
			text.setHTML(infoGrid.toString());
			final int r = row;
			final int c = col % this.maxCols;
			this.displayedObjects[r][c] = object;
			text.addMouseOverHandler(new MouseOverHandler(){
				public void onMouseOver(MouseOverEvent event) {
					core.undoHover();
					displayedObjects[r][c].setHover(true);
					updateCell(r,c);
					core.updateTimeAndMap();
				}
			});
			text.addMouseOutHandler(new MouseOutHandler(){
				public void onMouseOut(MouseOutEvent event) {
					displayedObjects[r][c].setHover(false);
					updateCell(r,c);
					core.updateTimeAndMap();
				}
			});
			text.addClickHandler(new ClickHandler(){
				public void onClick(ClickEvent event) {
					if( displayedObjects[r][c].getPercentage() == 1 ){
						displayedObjects[r][c].setPercentage(0);
					}
					else {
						displayedObjects[r][c].setPercentage(1);
					}
					updateCell(r,c);
					core.updateTimeAndMap();
				}
			});
			this.elements.setWidget(r,c,text);
			this.elements.getCellFormatter().getElement(r,c).setClassName("unselectedCellOuter");
			updateCell(r,c);			
			col++;
			if( col % this.maxCols == 0 ){
				row++;
			}
		}
	}
	
    /**
     * Creates a (preliminary) KML file from the data in the current data set  
    */
	private void createKMLFile(){
		String kmlContent = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><kml xmlns=\"http://www.opengis.net/kml/2.2\"><Document>";
		
		for( int i=0; i<this.dataSet.getObjects().length(); i++){
			DataObject currentDataObject = this.dataSet.getObjects().get(i);
			
			String name = currentDataObject.getName();
			String description = currentDataObject.getDescription();
			String place = currentDataObject.getPlace();
			String lat = currentDataObject.getLat();
			String lon = currentDataObject.getLon();
			String timeStamp = currentDataObject.getTimeStamp();
			String timeSpanStart = currentDataObject.getTimeSpanStart();
			String timeSpanEnd = currentDataObject.getTimeSpanEnd();
			
			String kmlEntry = "<Placemark>";

			kmlEntry += "<name><![CDATA[" + name + "]]></name>";
			kmlEntry += "<address><![CDATA[" + place + "]]></address>";
			kmlEntry += "<description><![CDATA[" + description + "]]></description>";
			kmlEntry += "<Point><coordinates>" + lon + "," + lat + "</coordinates></Point>";
			
			if ( (timeStamp != null) && (timeStamp != "undefined") ) {
				kmlEntry += "<TimeStamp><when>" + timeStamp + "</when></TimeStamp>";
			} else {
				kmlEntry += "<TimeSpan><begin>" + timeSpanStart + "</begin><end>" + timeSpanEnd + "</end></TimeSpan>";				
			}

			kmlEntry += "</Placemark>";
			
			kmlContent += kmlEntry;
		}
		
		kmlContent += "</Document></kml>";

        exportWriter = GWT.create(ExportWriterInterface.class);
        
		exportWriter.writeKMLFile(kmlContent, 
									new AsyncCallback<String>() {
            							public void onFailure(Throwable caught) {
            								//TODO: error message in layover?
            								//there needs to be an error-window for KML parser failures anyway
            								//this one should be used
            								int i = 1;
            							}
            							public void onSuccess(String exportFileName) {

            				                String url = GWT.getModuleBaseURL();
            				                url = url + "ExportServlet?ExportedFilename=" + URL.encode(exportFileName);
            				                
            				                aDownloadKML.setHref(url);
            				                aDownloadKML.setVisible(true);
            							}
									}
            					);
	}
	
    /**
     * Refines the currently selected dataset (table) by the search term entered.
     * 
    */
	private void refineByText(String searchString){
		
		String lowerCaseSearchString = searchString.toLowerCase();
		
		for( int i=0; i<this.dataSet.getObjects().length(); i++){
			DataObject currentDataObject = this.dataSet.getObjects().get(i);
			
			String name = currentDataObject.getName();
			String description = currentDataObject.getDescription();
			String place = currentDataObject.getPlace();
			
			if (	name.toLowerCase().contains(lowerCaseSearchString) || 
					description.toLowerCase().contains(lowerCaseSearchString) ||
					place.toLowerCase().contains(lowerCaseSearchString) )
				currentDataObject.setPercentage(1);
			else 
				currentDataObject.setPercentage(0);
		}
		
		//TODO: das gibt es nun mehrfach im Code, sollte also in eine Funktion
		//Ÿberfuhrt werden
		viewAll = -1;
		setActualObjectSet();
		showAll.removeStyleName("selectedView");
		showSelected.addStyleName("selectedView");
		organizePages();
		checkHide();
		fillTable();
	}
	
	/**
     * Checks, if the previous or next button has to be hidden
    */
	private void checkHide(){
		if( this.selectedChunk == 0 ){
			previous.setVisible(false);
		}
		else {
			previous.setVisible(true);
		}
		if( this.selectedChunk+1 >= this.chunkCount ){
			next.setVisible(false);
		}
		else {
			next.setVisible(true);
		}
	}
	
    /**
     * Updates a cell at the position (row,col). Depends on the selection status of the corresponding dataobject.
     * 
     * @param row the row of the cell
     * @param col the col of the cell
    */
	private void updateCell( int row, int col ){
		boolean selected = this.displayedObjects[row][col].isSelected();
		double percentage = (new Double(this.displayedObjects[row][col].getPercentage()).doubleValue());
		String borderColor = StiCore.getBorderColor(this.index,selected);
		String cellColor = StiCore.getCellColor(this.index,percentage);
		this.elements.getCellFormatter().getElement(row, col).getStyle().setBorderColor(borderColor);
		this.elements.getCellFormatter().getElement(row, col).getStyle().setBackgroundColor(cellColor);
	}
	
    /**
     * Updates all cells of the actual page
     * 
     * @param hover if there was a hover selection which caused to trigger this function
    */
	public void updateView(boolean hover){
		if( !hover && this.viewAll == -1 ){
			showSelected.removeStyleName("selectedView");
			this.viewAll = 0;		
		}
		for( int i=0; i<this.displayedObjects.length; i++ ){
			for( int j=0; j<this.displayedObjects[i].length; j++ ){
				if( this.displayedObjects[i][j] != null && this.displayedObjects[i][j].getStatus() ){
					updateCell(i,j);
				}
			}
		}
	}

	public String getTermIdentifier() {
		return this.dataSet.getTermIdentifier();
	}
	
}