Java tutorial
/* * Copyright 2012, Red Hat, Inc. and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.zanata.webtrans.client.ui; import java.util.List; import org.zanata.webtrans.client.presenter.PreviewState; import org.zanata.webtrans.client.presenter.TransUnitReplaceInfo; import org.zanata.webtrans.client.resources.WebTransMessages; import org.zanata.webtrans.client.util.ContentStateToStyleUtil; import org.zanata.webtrans.shared.util.StringNotEmptyPredicate; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.gwt.cell.client.AbstractCell; import com.google.gwt.cell.client.ActionCell; import com.google.gwt.cell.client.ActionCell.Delegate; import com.google.gwt.cell.client.Cell.Context; import com.google.gwt.cell.client.CheckboxCell; import com.google.gwt.cell.client.ValueUpdater; import com.google.gwt.core.shared.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.Header; import com.google.gwt.user.cellview.client.TextColumn; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.HasValue; import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.ImageResourceRenderer; import com.google.gwt.view.client.CellPreviewEvent; import com.google.gwt.view.client.DefaultSelectionEventManager; import com.google.gwt.view.client.DefaultSelectionEventManager.SelectAction; import com.google.gwt.view.client.DefaultSelectionEventManager.WhitelistEventTranslator; import com.google.gwt.view.client.SelectionModel; /** * Displays search results for a single document. * * @author David Mason, <a * href="mailto:damason@redhat.com">damason@redhat.com</a> * */ public class SearchResultsDocumentTable extends CellTable<TransUnitReplaceInfo> { private static final int CHECKBOX_COLUMN_INDEX = 0; private static CellTableResources cellTableResources; private WebTransMessages messages; private static SafeHtml spinner; private static DefaultSelectionEventManager<TransUnitReplaceInfo> selectionManager = null; private static String highlightString = null; private static boolean requirePreview = true; private CheckboxHeader checkboxColumnHeader; public static void setHighlightString(String highlightString) { SearchResultsDocumentTable.highlightString = highlightString; } public static void setRequirePreview(boolean required) { requirePreview = required; } /** * Create a standard result document table with no action buttons. * * Clicks on any cells will toggle selection. * * @param selectionModel * @param selectAllHandler * handler for events for the selection column header checkbox * @param messages * @param goToEditorDelegate */ public SearchResultsDocumentTable(final SelectionModel<TransUnitReplaceInfo> selectionModel, ValueChangeHandler<Boolean> selectAllHandler, final WebTransMessages messages, Delegate<TransUnitReplaceInfo> goToEditorDelegate) { super(15, getCellTableResources()); this.messages = messages; setWidth("100%", true); setSelectionModel(selectionModel, getSelectionManager()); addStyleName("projectWideSearchResultsDocumentBody"); CheckColumn checkboxColumn = new CheckColumn(selectionModel); checkboxColumnHeader = new CheckboxHeader(); checkboxColumnHeader.addValueChangeHandler(selectAllHandler); TextColumn<TransUnitReplaceInfo> rowIndexColumn = buildRowIndexColumn(); Column<TransUnitReplaceInfo, List<String>> sourceColumn = buildSourceColumn(); Column<TransUnitReplaceInfo, TransUnitReplaceInfo> targetColumn = buildTargetColumn(); final SafeHtml goToIcon = new SafeHtmlBuilder().appendHtmlConstant("<span class='icon-edit pointer' />") .toSafeHtml(); ActionColumn goToEditorColumn = new ActionColumn( new ActionCell<TransUnitReplaceInfo>(goToIcon, goToEditorDelegate) { @Override public void render(Context context, TransUnitReplaceInfo value, SafeHtmlBuilder sb) { sb.append(goToIcon); } }); addColumn(checkboxColumn, checkboxColumnHeader); addColumn(rowIndexColumn, messages.rowIndex()); addColumn(sourceColumn, messages.source()); addColumn(targetColumn, messages.target()); addColumn(goToEditorColumn); setColumnWidth(checkboxColumn, 50.0, Unit.PX); setColumnWidth(rowIndexColumn, 70.0, Unit.PX); setColumnWidth(sourceColumn, 50.0, Unit.PCT); setColumnWidth(targetColumn, 50.0, Unit.PCT); setColumnWidth(goToEditorColumn, 50.0, Unit.PX); sourceColumn.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP); targetColumn.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP); } /** * Create a result document table with action buttons for each row. * * Action button display is based on * {@link TransUnitReplaceInfo#getPreviewState()} and * {@link TransUnitReplaceInfo#getReplaceState()}, but Click and Enter-Key * events will be generated regardless of display, so delegates must check * for appropriate states. * * Clicks on action button cells will not change selection. Clicks on other * cells will toggle selection. * * @param previewDelegate * handles clicks of 'preview' button * @param replaceDelegate * handles clicks of 'replace' button * @param undoDelegate * handles clicks of 'undo' button * @param selectionModel * @param selectAllHandler * handles events for the selection column header checkbox * @param messages * @param resources * * @see * #SearchResultsDocumentTable(com.google.gwt.view.client.SelectionModel */ public SearchResultsDocumentTable(Delegate<TransUnitReplaceInfo> previewDelegate, Delegate<TransUnitReplaceInfo> replaceDelegate, Delegate<TransUnitReplaceInfo> undoDelegate, Delegate<TransUnitReplaceInfo> goToEditorDelegate, final SelectionModel<TransUnitReplaceInfo> selectionModel, ValueChangeHandler<Boolean> selectAllHandler, final WebTransMessages messages, org.zanata.webtrans.client.resources.Resources resources) { this(selectionModel, selectAllHandler, messages, goToEditorDelegate); if (spinner == null) { spinner = new ImageResourceRenderer().render(resources.spinner()); } ActionColumn previewButtonColumn = new ActionColumn(new PreviewActionCell(previewDelegate)); ActionColumn replaceButtonColumn = new ActionColumn(new ReplaceActionCell(replaceDelegate, undoDelegate)); // preview header refers to both these columns addColumn(previewButtonColumn, messages.actions()); addColumn(replaceButtonColumn); setColumnWidth(previewButtonColumn, 85.0, Unit.PX); setColumnWidth(replaceButtonColumn, 85.0, Unit.PX); } // TODO add focus tracking field to allow current-document type interactions // listeners may need to be attached in view /** * @return a column that displays the 1-based index of the text flow */ private static TextColumn<TransUnitReplaceInfo> buildRowIndexColumn() { return new TextColumn<TransUnitReplaceInfo>() { @Override public String getValue(TransUnitReplaceInfo tu) { return Integer.toString(tu.getTransUnit().getRowIndex() + 1); } }; } /** * @return a column that displays the source contents for the text flow */ private static Column<TransUnitReplaceInfo, List<String>> buildSourceColumn() { return new Column<TransUnitReplaceInfo, List<String>>(new AbstractCell<List<String>>() { @Override public void render(Context context, List<String> contents, SafeHtmlBuilder sb) { Iterable<String> notEmptyContents = Iterables.filter(contents, StringNotEmptyPredicate.INSTANCE); SafeHtml safeHtml = TextContentsDisplay .asSyntaxHighlightAndSearch(notEmptyContents, highlightString).toSafeHtml(); sb.appendHtmlConstant(safeHtml.asString()); } }) { @Override public List<String> getValue(TransUnitReplaceInfo info) { return info.getTransUnit().getSources(); } }; } /** * @return a column that displays the target contents for the text flow */ private static Column<TransUnitReplaceInfo, TransUnitReplaceInfo> buildTargetColumn() { return new Column<TransUnitReplaceInfo, TransUnitReplaceInfo>(new AbstractCell<TransUnitReplaceInfo>() { @Override public void render(Context context, TransUnitReplaceInfo info, SafeHtmlBuilder sb) { List<String> contents = info.getTransUnit().getTargets(); if (info.getPreviewState() == PreviewState.Show) { SafeHtml safeHtml = TextContentsDisplay.asDiff(contents, info.getPreview().getContents()) .toSafeHtml(); sb.appendHtmlConstant(safeHtml.asString()); } else { SafeHtml safeHtml = TextContentsDisplay.asSyntaxHighlightAndSearch(contents, highlightString) .toSafeHtml(); sb.appendHtmlConstant(safeHtml.asString()); } } }) { @Override public TransUnitReplaceInfo getValue(TransUnitReplaceInfo info) { return info; } @Override public String getCellStyleNames(Context context, TransUnitReplaceInfo info) { String styleNames = Strings.nullToEmpty(super.getCellStyleNames(context, info)); return ContentStateToStyleUtil.stateToStyle(info.getTransUnit().getStatus(), styleNames); } }; } private static class CheckColumn extends Column<TransUnitReplaceInfo, Boolean> { private SelectionModel<TransUnitReplaceInfo> selectionModel; public CheckColumn(SelectionModel<TransUnitReplaceInfo> selectionModel) { super(new CheckboxCell(true, false)); this.selectionModel = selectionModel; } @Override public Boolean getValue(TransUnitReplaceInfo info) { return selectionModel.isSelected(info); } } private static class CheckboxHeader extends Header<Boolean> implements HasValue<Boolean> { private boolean checked; private HandlerManager handlerManager; public CheckboxHeader() { super(new CheckboxCell()); checked = false; } // This method is invoked to pass the value to the CheckboxCell's render // method @Override public Boolean getValue() { return checked; } @Override public void onBrowserEvent(Context context, Element elem, NativeEvent nativeEvent) { int eventType = Event.as(nativeEvent).getTypeInt(); if (eventType == Event.ONCHANGE) { nativeEvent.preventDefault(); // use value setter to easily fire change event to handlers setValue(!checked, true); } } @Override public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Boolean> handler) { return ensureHandlerManager().addHandler(ValueChangeEvent.getType(), handler); } @Override public void fireEvent(GwtEvent<?> event) { ensureHandlerManager().fireEvent(event); } @Override public void setValue(Boolean value) { checked = value; } @Override public void setValue(Boolean value, boolean fireEvents) { checked = value; if (fireEvents) { ValueChangeEvent.fire(this, value); } } private HandlerManager ensureHandlerManager() { if (handlerManager == null) { handlerManager = new HandlerManager(this); } return handlerManager; } } private static class ActionColumn extends Column<TransUnitReplaceInfo, TransUnitReplaceInfo> { public ActionColumn(ActionCell<TransUnitReplaceInfo> actionCell) { super(actionCell); this.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); this.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); this.setCellStyleNames("projectWideSearchResultsActionColumn"); } @Override public TransUnitReplaceInfo getValue(TransUnitReplaceInfo info) { return info; } } private class ReplaceActionCell extends ActionCell<TransUnitReplaceInfo> { private Delegate<TransUnitReplaceInfo> undoDelegate; private final SafeHtml disabledReplaceHtml; private final SafeHtml replacingHtml; private final SafeHtml undoHtml; private final SafeHtml undoingHtml; public ReplaceActionCell(Delegate<TransUnitReplaceInfo> replaceDelegate, Delegate<TransUnitReplaceInfo> undoDelegate) { super(SafeHtmlUtils.fromString(messages.replace()), replaceDelegate); this.undoDelegate = undoDelegate; disabledReplaceHtml = buildButtonHtml("", messages.replace(), messages.previewRequiredBeforeReplace(), false); replacingHtml = buildProcessingIndicator(messages.replacing()); undoingHtml = buildProcessingIndicator(messages.undoInProgress()); undoHtml = buildButtonHtml(messages.replaced(), messages.undo()); } @Override public void render(com.google.gwt.cell.client.Cell.Context context, TransUnitReplaceInfo value, SafeHtmlBuilder sb) { switch (value.getReplaceState()) { case NotReplaced: if (!requirePreview || value.getPreviewState() == PreviewState.Show || value.getPreviewState() == PreviewState.Hide) { super.render(context, value, sb); } else { sb.append(disabledReplaceHtml); } break; case Replacing: sb.append(replacingHtml); break; case Replaced: sb.append(undoHtml); break; case Undoing: sb.append(undoingHtml); break; default: break; } } @Override protected void onEnterKeyDown(Context context, Element parent, TransUnitReplaceInfo value, NativeEvent event, ValueUpdater<TransUnitReplaceInfo> valueUpdater) { switch (value.getReplaceState()) { case NotReplaced: if (!requirePreview || value.getPreviewState() == PreviewState.Show || value.getPreviewState() == PreviewState.Hide) { super.onEnterKeyDown(context, parent, value, event, valueUpdater); } break; case Replaced: undoDelegate.execute(value); break; default: break; } // else ignore (is processing) } } private class PreviewActionCell extends ActionCell<TransUnitReplaceInfo> { private Delegate<TransUnitReplaceInfo> previewDelegate; private final SafeHtml fetchingPreviewHtml; private final SafeHtml hidePreviewHtml; public PreviewActionCell(Delegate<TransUnitReplaceInfo> previewDelegate) { super(SafeHtmlUtils.fromString(messages.fetchPreview()), previewDelegate); this.previewDelegate = previewDelegate; fetchingPreviewHtml = buildProcessingIndicator(messages.fetchingPreview()); hidePreviewHtml = buildButtonHtml("", messages.hidePreview()); } @Override public void render(com.google.gwt.cell.client.Cell.Context context, TransUnitReplaceInfo value, SafeHtmlBuilder sb) { switch (value.getPreviewState()) { case NotFetched: case Hide: super.render(context, value, sb); break; case Fetching: sb.append(fetchingPreviewHtml); break; case Show: sb.append(hidePreviewHtml); break; case NotAllowed: // empty cell break; } } @Override protected void onEnterKeyDown(Context context, Element parent, TransUnitReplaceInfo value, NativeEvent event, ValueUpdater<TransUnitReplaceInfo> valueUpdater) { // delegate is responsible for taking appropriate action previewDelegate.execute(value); } } private static SafeHtml buildButtonHtml(String message, String buttonLabel) { return buildButtonHtml(message, buttonLabel, "", true); } private static SafeHtml buildButtonHtml(String message, String buttonLabel, String title, boolean enabled) { SafeHtmlBuilder sb = new SafeHtmlBuilder().appendHtmlConstant(message) .appendHtmlConstant("<button type=\"button\" tabindex=\"-1\""); if (!enabled) { sb.appendHtmlConstant(" title=\"").appendEscaped(title).appendHtmlConstant("\" disabled=\"disabled\""); } return sb.appendHtmlConstant(">").appendEscaped(buttonLabel).appendHtmlConstant("</button>").toSafeHtml(); } private static SafeHtml buildProcessingIndicator(String message) { return new SafeHtmlBuilder().append(spinner).appendHtmlConstant("<br/>").appendHtmlConstant(message) .toSafeHtml(); } private static DefaultSelectionEventManager<TransUnitReplaceInfo> getSelectionManager() { if (selectionManager == null) { selectionManager = buildSelectionManager(); } return selectionManager; } /** * build a selection manager that toggles selection when the row is clicked, * but suppresses selection when clicks are in the action button column to * prevent undesired selections when using action buttons * * @return the selection manager */ private static DefaultSelectionEventManager<TransUnitReplaceInfo> buildSelectionManager() { WhitelistEventTranslator<TransUnitReplaceInfo> selectionEventTranslator = new WhitelistEventTranslator<TransUnitReplaceInfo>() { @Override public SelectAction translateSelectionEvent(CellPreviewEvent<TransUnitReplaceInfo> event) { return isColumnWhitelisted(event.getColumn()) ? SelectAction.TOGGLE : SelectAction.IGNORE; } }; selectionEventTranslator.setColumnWhitelisted(CHECKBOX_COLUMN_INDEX, true); return DefaultSelectionEventManager.<TransUnitReplaceInfo>createCustomManager(selectionEventTranslator); } private static CellTableResources getCellTableResources() { if (cellTableResources == null) { cellTableResources = GWT.create(CellTableResources.class); } return cellTableResources; } public HasValue<Boolean> getCheckbox() { return checkboxColumnHeader; } }