/*******************************************************************************
* Copyright 2013 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.common.ui.widgets;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Link;
/**
* Widget that displays a number of numeric links which represent pages, with
* previous and next buttons on either side. Can be used to support changing
* pages on a paged result view.
* <p/>
* The number of page links shown depends on the page count and the available
* width.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class PageLinks extends Composite
{
public final static int UNKNOWN_PAGE_COUNT = -1;
private int pageCount = UNKNOWN_PAGE_COUNT;
private int selectedPage = 0;
private final List<PageListener> listeners = new ArrayList<PageListener>();
private Control selectedPageControl;
public PageLinks(Composite parent, int style)
{
super(parent, style);
PageLinksLayout layout = new PageLinksLayout(SWT.HORIZONTAL);
layout.pack = true;
layout.justify = true;
layout.spacing = 5;
layout.wrap = false;
layout.center = true;
layout.fill = false;
layout.marginBottom = layout.marginLeft = layout.marginRight = layout.marginTop = 0;
setLayout(layout);
addControlListener(new ControlAdapter()
{
@Override
public void controlResized(ControlEvent e)
{
setupLinks();
}
});
addPaintListener(new PaintListener()
{
@Override
public void paintControl(PaintEvent e)
{
if (selectedPageControl == null || selectedPageControl.isDisposed())
{
return;
}
GC gc = e.gc;
Color color = e.display.getSystemColor(SWT.COLOR_WIDGET_BORDER);
gc.setForeground(color);
Rectangle rect = selectedPageControl.getBounds();
gc.drawRectangle(rect.x - 3, rect.y - 2, rect.width + 4, rect.height + 2);
}
});
setupLinks();
}
/**
* Add a listener for page changes.
*
* @param listener
* Listener to add
*/
public void addPageListener(PageListener listener)
{
listeners.add(listener);
}
/**
* Remove the given page listener from the list of page listeners.
*
* @param listener
* Listener to remove
*/
public void removePageListener(PageListener listener)
{
listeners.remove(listener);
}
/**
* @return Number of pages that can be shown by this component.
*/
public int getPageCount()
{
return pageCount;
}
/**
* Set the number of pages that can be shown by this component.
*
* @param pageCount
*/
public void setPageCount(int pageCount)
{
this.pageCount = pageCount;
asyncSetupLinks();
}
/**
* @return The selected page.
*/
public int getSelectedPage()
{
return selectedPage;
}
/**
* Set the selected page.
* <p/>
* Note that listeners are not notified of this page change.
*
* @param selectedPage
*/
public void setSelectedPage(int selectedPage)
{
setSelectedPage(selectedPage, false);
}
protected void setSelectedPage(int selectedPage, boolean notifyListeners)
{
if (this.selectedPage != selectedPage)
{
this.selectedPage = selectedPage;
asyncSetupLinks();
if (notifyListeners)
{
for (int i = listeners.size() - 1; i >= 0; i--)
{
listeners.get(i).pageChanged(selectedPage);
}
}
}
}
/**
* @return The minimum spacing between page links.
*/
public int getSpacing()
{
return ((PageLinksLayout) getLayout()).spacing;
}
/**
* Set the minimum spacing between page links.
*
* @param spacing
*/
public void setSpacing(int spacing)
{
((PageLinksLayout) getLayout()).spacing = spacing;
asyncSetupLinks();
}
@Override
public void dispose()
{
disposeChildren();
super.dispose();
}
protected void disposeChildren()
{
Control[] children = getChildren();
for (Control child : children)
{
child.dispose();
}
}
/**
* Calls {@link #setupLinks()} on the UI thread.
*/
protected void asyncSetupLinks()
{
getDisplay().asyncExec(new Runnable()
{
@Override
public void run()
{
setupLinks();
}
});
}
/**
* Create the child page link widgets.
*/
protected void setupLinks()
{
if (isDisposed())
{
return;
}
disposeChildren();
int spacing = getSpacing();
int availableWidth = getBounds().width - spacing;
Button previous = new Button(this, SWT.FLAT);
previous.setText("<"); //$NON-NLS-1$
previous.pack();
previous.addSelectionListener(new SelectionAdapter()
{
@Override
public void widgetSelected(SelectionEvent e)
{
setSelectedPage(selectedPage - 1, true);
}
});
previous.setEnabled(selectedPage > 0);
//reduce the available width by the width of the previous/next buttons (assume the next button has the same width)
availableWidth -= previous.getBounds().width;
availableWidth -= previous.getBounds().width;
Link link = new Link(this, SWT.NONE);
List<Integer> pages = new ArrayList<Integer>();
int pageCount = this.pageCount == UNKNOWN_PAGE_COUNT ? Integer.MAX_VALUE : this.pageCount;
int page = Math.min(pageCount - 1, this.selectedPage);
while (availableWidth >= 0)
{
//has the first page been added to the array (don't add any before):
boolean addedFirst = !pages.isEmpty() && pages.get(0) == 0;
//has the last page been added to the array (don't add any after):
boolean addedLast = !pages.isEmpty() && pages.get(pages.size() - 1) == pageCount - 1;
//if both the first and the last have been added, we don't need to add any more
if (addedFirst && addedLast)
{
break;
}
//calculate the width of the page link and subtract it from the available width
link.setText(String.valueOf(page + 1));
link.pack();
int width = link.getBounds().width + spacing;
if (width > availableWidth)
{
break;
}
availableWidth -= width;
//should the new page be added at the start or end of the array?
boolean before = !addedFirst && (addedLast || pages.size() % 2 == 0) && page != pageCount - 1;
//add the page to the array:
pages.add(before ? 0 : pages.size(), page);
//increment or decrement the page:
page += addedFirst ? 1 : addedLast ? -1 : pages.size() * (before ? 1 : -1);
}
link.dispose();
selectedPageControl = null;
int extraWidthPerPage = availableWidth / Math.max(pages.size(), 1) + 1;
for (final Integer p : pages)
{
link = new Link(this, SWT.NONE);
String text = String.valueOf(p + 1);
if (selectedPage != p)
{
text = "<a>" + text + "</a>"; //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
selectedPageControl = link;
}
link.setText(text);
link.pack();
link.addSelectionListener(new SelectionAdapter()
{
@Override
public void widgetSelected(SelectionEvent e)
{
setSelectedPage(p, true);
}
});
//distribute the extra available width equally to the non selected link controls
//(but not the selected page so it doesn't affect the border)
if (selectedPage != p)
{
int extraWidth = Math.min(extraWidthPerPage, availableWidth);
availableWidth -= extraWidth;
//link.setLayoutData(new RowData(link.getBounds().width + extraWidth, SWT.DEFAULT));
}
}
Button next = new Button(this, SWT.FLAT);
next.setText(">"); //$NON-NLS-1$
next.pack();
next.addSelectionListener(new SelectionAdapter()
{
@Override
public void widgetSelected(SelectionEvent e)
{
setSelectedPage(selectedPage + 1, true);
}
});
next.setEnabled(selectedPage < pageCount - 1);
layout();
redraw(); //repaint the selected page border
}
}