/* CustomSortWindowCtrl.java
{{IS_NOTE
Purpose:
Description:
History:
Aug 17, 2010 6:34:44 PM , Created by Sam
}}IS_NOTE
Copyright (C) 2009 Potix Corporation. All Rights Reserved.
*/
package org.zkoss.zss.app;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.zkoss.lang.Objects;
import org.zkoss.poi.ss.usermodel.Cell;
import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Components;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.ForwardEvent;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zss.app.zul.Dialog;
import org.zkoss.zss.model.Worksheet;
import org.zkoss.zss.ui.Rect;
import org.zkoss.zss.ui.Spreadsheet;
import org.zkoss.zss.ui.impl.Utils;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Comboitem;
import org.zkoss.zul.Label;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.ListitemRenderer;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Window;
/**
* @author Sam
*
*/
public class CustomSortWindowCtrl extends GenericForwardComposer {
private Dialog _customSortDialog;
/**
* Sort by column
*/
private final static boolean SORT_TOP_TO_BOTTOM = false;
/**
* Sort by row
*/
private final static boolean SORT_LEFT_TO_RIGHT = true;
private final static String ORIENTATION_COLUMN_KEY = "sort.orientation.column";// orientation
/**
* Indicate the sort orientation
* <p> Default: false, sort from top to bottom, means sort by column
* <p> true, sort from left to right, means sort by row
*/
private boolean sortOrientation;
/**
* Selected sort level target by user
*/
private ListModelList sortLevelModel;
/**
* Depends on sortOrientation
* If sort by rows, available sort target include top row to bottom row
* If sort by columns, available sort target include left column to right column
*
* The value build from spreadsheet's selection range.
*/
private List<String> availableSortIndex = new ArrayList<String>();
private ListModelList sortIndexModel = new ListModelList();
private Listbox sortLevel;
private Checkbox caseSensitive;
private Checkbox hasHeader;
private Combobox sortOrientationCombo;
private Button addBtn;
private Button delBtn;
private Button upBtn;
private Button downBtn;
private Button okBtn;
//TODO: remove Spreadsheet object, use WorkbookCtrl instead
private Spreadsheet ss;
private final static String KEY_ARG_SPREADSGEET = "org.zkoss.zss.app.customSortWindowCtrl.spreadsheetArg";
public static Map newArg(Spreadsheet spreadsheet) {
HashMap<String, Object> arg = new HashMap<String, Object>();
arg.put(KEY_ARG_SPREADSGEET, spreadsheet);
return arg;
}
public void onOpen$_customSortDialog(ForwardEvent event) {
Rect selection = (Rect) event.getOrigin().getData();
ss.setSelection(selection);
init();
_customSortDialog.setMode(Window.MODAL);
}
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
ss = (Spreadsheet)Executions.getCurrent().getArg().get(KEY_ARG_SPREADSGEET);
}
private void init () {
sortOrientationCombo.setSelectedIndex(0);
List<SortLevel> ary = new ArrayList<SortLevel>();
ary.add(new SortLevel());
setAvailableSortTarget(availableSortIndex);
sortLevelModel = new ListModelList(ary);
sortLevel.setModel(sortLevelModel);
sortLevel.setItemRenderer(sortLevelRenderer);
}
/**
* Setup the available sort target
* <p> Sort target is column if sort direction is from top to bottom
* <p> Sort target is row if sort direction is from left to right
*/
private void setAvailableSortTarget (List<String> list) {
list.clear();
list.add(new String(""));
Rect rect = ss.getSelection();
if (sortOrientation == SORT_TOP_TO_BOTTOM) {
for (int i = rect.getLeft(); i <= rect.getRight(); i++)
list.add(new String("Column " + ss.getColumntitle(i)));
} else {
for (int i = rect.getTop(); i <= rect.getBottom(); i++)
list.add(new String("Row " + ss.getRowtitle(i)));
}
sortIndexModel.clear();
sortIndexModel.addAll(list);
}
public void onSelect$sortLevel () {
setClickableMoveButtons();
}
public void onInitRender$sortLevel () {
setClickableMoveButtons();
}
private void setClickableMoveButtons() {
int idx = sortLevel.getSelectedIndex();
upBtn.setDisabled(idx == 0 ? true : false);
downBtn.setDisabled(idx < ( sortLevel.getItemCount() - 1) ? false : true);
}
public void onClick$addBtn () {
sortLevelModel.add(new SortLevel());
}
public void onClick$delBtn () {
if (sortLevel.getSelectedIndex() < 0)
return;
sortLevelModel.remove(sortLevel.getSelectedIndex());
}
public void onClick$upBtn () {
if (sortLevel.getSelectedIndex() < 0)
return;
int idx = sortLevel.getSelectedIndex();
int forward = idx - 1;
if (forward >= 0) {
swapSortLevel(sortLevelModel, idx, forward);
sortLevel.setSelectedIndex(forward);
}
}
public void onClick$downBtn () {
if (sortLevel.getSelectedIndex() < 0)
return;
int idx = sortLevel.getSelectedIndex();
int back = idx + 1;
if (back < sortLevelModel.getSize()) {
swapSortLevel(sortLevelModel, idx, back);
sortLevel.setSelectedIndex(back);
}
}
public void onClick$okBtn () {
if (hasEmptyArgs(sortLevelModel.getInnerList())) {
Messagebox.show(getLabel("sort.err.hasEmptyField"));
return;
}
String dupTarget = checkDuplicateSortIndex(sortLevelModel.getInnerList());
if (dupTarget != null) {
Messagebox.show(dupTarget + " " + getLabel("sort.err.duplicateField"));
return;
}
int count = 0;
int size = sortLevelModel.size() <= 3 ? sortLevelModel.size() : 3;
int[] index = new int[size];
int[] dataOption = new int[size];
boolean[] algorithm = new boolean[size];
for (Iterator iter = sortLevelModel.iterator(); iter.hasNext() && count < index.length; count++) {
SortLevel l = (SortLevel)iter.next();
index[count] = l.sortIndex;
algorithm[count] = l.order;
dataOption[count] = l.dataOption;
}
//call utils
Utils.sort(ss.getSelectedSheet(), ss.getSelection(), index, algorithm, dataOption, hasHeader.isChecked(), caseSensitive.isChecked(), sortOrientation);
_customSortDialog.fireOnClose(null);
}
/**
* Returns true if there is a
* @return
*/
private static boolean hasEmptyArgs (List<SortLevel> list) {
for (SortLevel s : list) {
if (s.sortIndex == -1)
return true;
}
return false;
}
/**
* Returns the duplicate sort title
* @param list
* @return string if there is a duplicate, or null when there is no duplicate.
*/
private String checkDuplicateSortIndex(List<SortLevel> list) {
HashMap<Integer, Boolean> map = new HashMap<Integer, Boolean>();
for (SortLevel s : list) {
if ( map.containsKey( Integer.valueOf(s.sortIndex) ) ) {
String title = null;
if (sortOrientation)
title = "Row " + ss.getRowtitle(s.sortIndex);
else
title = "Column " + ss.getColumntitle(s.sortIndex);
return title;
} else
map.put(Integer.valueOf(s.sortIndex), Boolean.valueOf(true));
}
return null;
}
private static void swapSortLevel(ListModelList model, int sourceIdx, int destIdx) {
SortLevel srcSort = (SortLevel)model.get(sourceIdx);
SortLevel dstSort = (SortLevel)model.get(destIdx);
model.set(destIdx, srcSort);
model.set(sourceIdx, dstSort);
}
public void onSelect$sortOrientationCombo () {
boolean orientation;
Comboitem seld = sortOrientationCombo.getSelectedItem();
orientation = getLabel(ORIENTATION_COLUMN_KEY).equals(seld.getLabel()) ?
SORT_TOP_TO_BOTTOM : SORT_LEFT_TO_RIGHT;
if (sortOrientation != orientation) {
sortOrientation = orientation;
setAvailableSortTarget(availableSortIndex);
//init sort level
int i = 0;
List<SortLevel> ary = sortLevelModel.getInnerList();
for (SortLevel l : ary) {
l.sortIndex = -1;
sortLevelModel.set(i++, l);
}
}
}
private ListitemRenderer sortLevelRenderer = new ListitemRenderer() {
public void render(Listitem item, Object obj) throws Exception {
Listcell cell = new Listcell();
SortLevel sort = (SortLevel)obj;
SortLevel first = (SortLevel)sortLevelModel.get(0);
cell.appendChild(sort.equals(first) ?
(Component)new Label(getLabel("sort.sortBy")) : (Component)new Label(getLabel("sort.thenBy")));
item.appendChild(cell);
cell = new Listcell();
SortIndexSelector idxSel = new SortIndexSelector(sort);
cell.appendChild(idxSel);
item.appendChild(cell);
cell = new Listcell();
SortAlgorithm sortMethod = new SortAlgorithm(sort);
cell.appendChild(sortMethod);
idxSel.setAttribute(SortAlgorithm.class.getCanonicalName(), sortMethod);
item.appendChild(cell);
}
@Override
public void render(Listitem item, Object data, int index)
throws Exception {
render(item, data);
}
};
public class SortIndexSelector extends Combobox {
SortLevel sort;
public SortIndexSelector (SortLevel sort) {
setWidth("100%");
setReadonly(true);
setMold("rounded");
this.sort = sort;
setModel(sortIndexModel);
}
public void onAfterRender() {
if (sort.sortIndex >= 0 && getItemCount() > 0) {
int idx = getSpreadsheetIndexOffset(ss, sort.sortIndex, sortOrientation);
setSelectedIndex(idx >= 0 ? idx : 0);
} else
setSelectedIndex(-1);
}
public void onSelect () {
String seldLabel = getSelectedItem().getLabel();
if (seldLabel != null && seldLabel.length() > 0) {
int blankIdx = seldLabel.lastIndexOf(" ");
if (blankIdx >= 0) {
String title = seldLabel.substring( seldLabel.lastIndexOf(" ") + " ".length() );
int idx = getSpreadsheetIndexBy(ss, title, sortOrientation);
if (idx != -1) {
sort.sortIndex = idx;
SortAlgorithm alg = (SortAlgorithm)getAttribute(SortAlgorithm.class.getCanonicalName());
alg.refresh();
}
}
}
}
}
public class SortAlgorithm extends Combobox {
private SortLevel sort;
/**
* Default display the String
*/
private final static String STR_ASCENDING_KEY = "sort.str.ascending";
private final static String STR_DESCENDING_KEY = "sort.str.descending";
private final static String NUM_ASCENDING_KEY = "sort.num.ascending";
private final static String NUM_DESCENDING_KEY = "sort.num.descending";
/**
* Sort index has number
* <p> default false means sort target is string
*/
private boolean sortNumber;
public SortAlgorithm(SortLevel sort) {
this.sort = sort;
setWidth("100%");
setReadonly(true);
setMold("rounded");
appendComboitemsBySortIndex(sort.sortIndex);
setSelectedIndex(sort.order == SortLevel.ASCENDING ? 0 : 1);
}
public void refresh() {
setSelectedIndex(-1);
Components.removeAllChildren(this);
appendComboitemsBySortIndex(sort.sortIndex);
setSelectedIndex(0);
}
private void appendComboitemsBySortIndex(int index) {
if (index < 0) { //default sort target is string
sortNumber = false;
appendChild(new Comboitem(getLabel(STR_ASCENDING_KEY)));
appendChild(new Comboitem(getLabel(STR_DESCENDING_KEY)));
} else {
if (isAllCellNumberType(index)) {
sortNumber = true;
appendChild(new Comboitem(getLabel(NUM_ASCENDING_KEY)));
appendChild(new Comboitem(getLabel(NUM_DESCENDING_KEY)));
} else {
sortNumber = false;
appendChild(new Comboitem(getLabel(STR_ASCENDING_KEY)));
appendChild(new Comboitem(getLabel(STR_DESCENDING_KEY)));
}
}
}
/**
* Returns true indicate spreadsheet index column/row is number
* @return
*/
private boolean isAllCellNumberType(int idx) {
Worksheet sheet = ss.getSelectedSheet();
Rect rect = ss.getSelection();
int top = sortOrientation ? idx : rect.getTop();
int left = sortOrientation ? rect.getLeft() : idx;
int bottom = sortOrientation ? idx : rect.getBottom();
int right = sortOrientation ? rect.getRight() : idx;
for (int row = top; row <= bottom; row++) {
for (int col = left; col <= right; col++) {
Cell c = Utils.getCell(sheet, row, col);
if (c != null) {
int type = c.getCellType() != Cell.CELL_TYPE_FORMULA ?
c.getCellType() : c.getCachedFormulaResultType();
if (type != Cell.CELL_TYPE_NUMERIC)
return false;
}
}
}
return true;
}
public void onSelect (SelectEvent evt) {
Iterator<Comboitem> iter = evt.getSelectedItems().iterator();
if (iter.hasNext()) {
Comboitem item = iter.next();
if (!sortNumber) { //sort string
sort.order = getLabel(STR_ASCENDING_KEY).equals(item.getLabel()) ?
SortLevel.ASCENDING : SortLevel.DESCENDING;
} else { //sort number
sort.order = getLabel(NUM_ASCENDING_KEY).equals(item.getLabel()) ?
SortLevel.ASCENDING : SortLevel.DESCENDING;
}
}
}
}
/**
* Returns the string from i-18n, if not found, return empty string
* @return
*/
private static String getLabel(String key) {
String val = Labels.getLabel(key);
return val != null ? val : "";
}
private static int getSpreadsheetIndexOffset (Spreadsheet spreadsheet, int index, boolean sortAlgorithm) {
Rect rect = spreadsheet.getSelection();
int baseIdx = sortAlgorithm == SORT_TOP_TO_BOTTOM ? rect.getLeft() : rect.getTop();
int idx = index - baseIdx + 1;
return idx >= 0 ? idx : -1;
}
/**
* Returns the index of column or row by title on selection range of spreadsheet
* @param spreadsheet
* @param title
* @param sortAlgorithm
* @return
*/
private static int getSpreadsheetIndexBy (Spreadsheet spreadsheet, String title, boolean sortAlgorithm) {
Rect rect = spreadsheet.getSelection();
if (sortAlgorithm == SORT_TOP_TO_BOTTOM) {
for (int i = rect.getLeft(); i <= rect.getRight(); i++) {
String t = spreadsheet.getColumntitle(i);
if (Objects.equals(title, t))
return i;
}
} else {
for (int i = rect.getTop(); i <= rect.getBottom(); i++) {
String t = spreadsheet.getRowtitle(i);
if (Objects.equals(title, t))
return i;
}
}
return -1;
}
}