Package org.zanata.webtrans.client.presenter

Source Code of org.zanata.webtrans.client.presenter.SearchResultsPresenter

/*
* 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.presenter;

import static org.zanata.webtrans.client.events.NotificationEvent.Severity.Info;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import net.customware.gwt.presenter.client.widget.WidgetPresenter;

import org.zanata.webtrans.client.events.KeyShortcutEvent;
import org.zanata.webtrans.client.events.KeyShortcutEventHandler;
import org.zanata.webtrans.client.events.NotificationEvent;
import org.zanata.webtrans.client.events.NotificationEvent.Severity;
import org.zanata.webtrans.client.events.TransUnitUpdatedEvent;
import org.zanata.webtrans.client.events.TransUnitUpdatedEventHandler;
import org.zanata.webtrans.client.events.WorkspaceContextUpdateEvent;
import org.zanata.webtrans.client.events.WorkspaceContextUpdateEventHandler;
import org.zanata.webtrans.client.history.History;
import org.zanata.webtrans.client.history.HistoryToken;
import org.zanata.webtrans.client.history.Window.Location;
import org.zanata.webtrans.client.keys.KeyShortcut;
import org.zanata.webtrans.client.keys.Keys;
import org.zanata.webtrans.client.keys.ShortcutContext;
import org.zanata.webtrans.client.resources.WebTransMessages;
import org.zanata.webtrans.client.rpc.CachingDispatchAsync;
import org.zanata.webtrans.client.service.GetTransUnitActionContextHolder;
import org.zanata.webtrans.client.ui.UndoLink;
import org.zanata.webtrans.shared.model.TransUnit;
import org.zanata.webtrans.shared.model.TransUnitId;
import org.zanata.webtrans.shared.model.TransUnitUpdateInfo;
import org.zanata.webtrans.shared.model.TransUnitUpdatePreview;
import org.zanata.webtrans.shared.model.UserWorkspaceContext;
import org.zanata.webtrans.shared.rpc.GetProjectTransUnitLists;
import org.zanata.webtrans.shared.rpc.GetProjectTransUnitListsResult;
import org.zanata.webtrans.shared.rpc.PreviewReplaceText;
import org.zanata.webtrans.shared.rpc.PreviewReplaceTextResult;
import org.zanata.webtrans.shared.rpc.ReplaceText;
import org.zanata.webtrans.shared.rpc.RevertTransUnitUpdates;
import org.zanata.webtrans.shared.rpc.UpdateTransUnitResult;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.cell.client.ActionCell.Delegate;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent.Handler;
import com.google.inject.Inject;
import com.google.inject.Provider;

/**
* View for project-wide search and replace within textflow targets
*
* @author David Mason, <a
*         href="mailto:damason@redhat.com">damason@redhat.com</a>
*/
public class SearchResultsPresenter extends
        WidgetPresenter<SearchResultsPresenter.Display> {

    private static final int TRUNCATED_TARGET_LENGTH = 24;

    public interface Display extends WidgetDisplay {
        HasText getSearchResponseLabel();

        /**
         * Set the string that will be highlighted in target content. Set to
         * null or empty string to disable highlight
         *
         * @param highlightString
         */
        void setHighlightString(String highlightString);

        HasValue<String> getFilterTextBox();

        void focusFilterTextBox();

        HasClickHandlers getSearchButton();

        HasValue<String> getReplacementTextBox();

        void focusReplacementTextBox();

        HasValue<Boolean> getCaseSensitiveChk();

        HasValue<Boolean> getSelectAllChk();

        HasChangeHandlers getSearchFieldSelector();

        static String SEARCH_FIELD_TARGET = "target";
        static String SEARCH_FIELD_SOURCE = "source";
        static String SEARCH_FIELD_BOTH = "both";

        String getSelectedSearchField();

        void setSearching(boolean searching);

        HasClickHandlers getReplaceAllButton();

        void setReplaceAllButtonEnabled(boolean enabled);

        void setReplaceAllButtonVisible(boolean visible);

        HasValue<Boolean> getRequirePreviewChk();

        void setRequirePreview(boolean required);

        void clearAll();

        /**
         * Add a document header and table to the display, with no row action
         * buttons.
         *
         * @param docName
         *            document title to show
         * @param viewDocClickHandler
         *            handler for 'view in editor' link
         * @param searchDocClickHandler
         *            handler for 'search in editor' link
         * @param selectionModel
         * @param selectAllHandler
         * @param goToEditorDelegate
         * @return the created table
         *
         * @see SearchResultsDocumentTable#(com.google.gwt.view.client.
         *      SelectionModel
         */
        ListDataProvider<TransUnitReplaceInfo> addDocument(String docName,
                ClickHandler viewDocClickHandler,
                ClickHandler searchDocClickHandler,
                ClickHandler infoClickHandler,
                MultiSelectionModel<TransUnitReplaceInfo> selectionModel,
                ValueChangeHandler<Boolean> selectAllHandler,
                Delegate<TransUnitReplaceInfo> goToEditorDelegate);

        /**
         * Add a document header and table to the display, with action buttons
         * per row.
         *
         * @return the created table
         *
         * @see Display#addDocument(String,
         *      com.google.gwt.event.dom.client.ClickHandler,
         *      com.google.gwt.event.dom.client.ClickHandler,
         *      com.google.gwt.event.dom.client.ClickHandler,
         *      com.google.gwt.view.client.MultiSelectionModel,
         *      com.google.gwt.event.logical.shared.ValueChangeHandler,
         *      com.google.gwt.cell.client.ActionCell.Delegate)
         * @see SearchResultsDocumentTable#(Delegate, Delegate, Delegate,
         *      SelectionModel, ValueChangeHandler, WebTransMessages,
         *      org.zanata.webtrans.client.resources.Resources)
         */
        ListDataProvider<TransUnitReplaceInfo> addDocument(String docName,
                ClickHandler viewDocClickHandler,
                ClickHandler searchDocClickHandler,
                ClickHandler infoClickHandler,
                MultiSelectionModel<TransUnitReplaceInfo> selectionModel,
                ValueChangeHandler<Boolean> selectAllHandler,
                Delegate<TransUnitReplaceInfo> previewDelegate,
                Delegate<TransUnitReplaceInfo> replaceDelegate,
                Delegate<TransUnitReplaceInfo> undoDelegate,
                Delegate<TransUnitReplaceInfo> goToEditorDelegate);

        /**
         * Required to avoid instantiating a component that calls client-only
         * code in the presenter
         *
         * @return a new selection model for use with table
         */
        MultiSelectionModel<TransUnitReplaceInfo> createMultiSelectionModel();

        HasValue<Boolean> getSelectAllCheckbox();

        void addSearchFieldsSelect(String item, String value);

        void showDiffLegend();
    }

    private final WebTransMessages messages;
    private final CachingDispatchAsync dispatcher;
    private final KeyShortcutPresenter keyShortcutPresenter;
    private final Location windowLocation;
    private final GetTransUnitActionContextHolder contextHolder;
    private final History history;
    private final Provider<UndoLink> undoLinkProvider;
    private final UserWorkspaceContext userWorkspaceContext;

    private AsyncCallback<GetProjectTransUnitListsResult> projectSearchCallback;
    private Delegate<TransUnitReplaceInfo> previewButtonDelegate;
    private Delegate<TransUnitReplaceInfo> replaceButtonDelegate;
    private Delegate<TransUnitReplaceInfo> undoButtonDelegate;
    private Delegate<TransUnitReplaceInfo> goToEditorDelegate;

    private Handler selectionChangeHandler;

    private Map<Long, HasValue<Boolean>> selectAllDocList;

    /**
     * Model objects for tables in display. Changes to these are reflected in
     * the view.
     */
    private Map<Long, ListDataProvider<TransUnitReplaceInfo>> documentDataProviders;

    /**
     * Selection model objects for tables in display. Used to determine which
     * transunits are selected
     */
    private Map<Long, MultiSelectionModel<TransUnitReplaceInfo>> documentSelectionModels;

    private Map<TransUnitId, TransUnitReplaceInfo> allReplaceInfos;

    private Map<Long, String> docPaths;

    private boolean autoPreview = true;
    private boolean showRowActionButtons = false;

    @Inject
    public SearchResultsPresenter(Display display, EventBus eventBus,
            CachingDispatchAsync dispatcher, History history,
            final WebTransMessages webTransMessages,
            final UserWorkspaceContext userWorkspaceContext,
            final KeyShortcutPresenter keyShortcutPresenter,
            final Provider<UndoLink> undoLinkProvider, Location windowLocation,
            GetTransUnitActionContextHolder contextHolder) {
        super(display, eventBus);
        messages = webTransMessages;
        this.history = history;
        this.dispatcher = dispatcher;
        this.userWorkspaceContext = userWorkspaceContext;
        this.keyShortcutPresenter = keyShortcutPresenter;
        this.undoLinkProvider = undoLinkProvider;
        this.windowLocation = windowLocation;
        this.contextHolder = contextHolder;
    }

    @Override
    protected void onBind() {
        projectSearchCallback = buildProjectSearchCallback();
        selectionChangeHandler = buildSelectionChangeHandler();
        documentDataProviders =
                new HashMap<Long, ListDataProvider<TransUnitReplaceInfo>>();
        documentSelectionModels =
                new HashMap<Long, MultiSelectionModel<TransUnitReplaceInfo>>();
        allReplaceInfos = new HashMap<TransUnitId, TransUnitReplaceInfo>();
        docPaths = new HashMap<Long, String>();
        selectAllDocList = new HashMap<Long, HasValue<Boolean>>();
        setUiForNothingSelected();
        display.setReplaceAllButtonVisible(userWorkspaceContext
                .hasEditTranslationAccess());

        display.addSearchFieldsSelect("search both", "both");
        display.addSearchFieldsSelect("search target", "target");
        display.addSearchFieldsSelect("search source", "source");

        registerHandler(display.getSearchButton().addClickHandler(
                new ClickHandler() {
                    @Override
                    public void onClick(ClickEvent event) {
                        updateSearch();
                    }
                }));

        registerHandler(display.getReplacementTextBox().addValueChangeHandler(
                new ValueChangeHandler<String>() {

                    @Override
                    public void onValueChange(ValueChangeEvent<String> event) {
                        HistoryToken token = history.getHistoryToken();
                        if (!event.getValue().equals(
                                token.getProjectSearchReplacement())) {
                            token.setProjectSearchReplacement(event.getValue());
                            history.newItem(token);
                        }
                    }
                }));

        registerHandler(display.getSelectAllChk().addValueChangeHandler(
                new ValueChangeHandler<Boolean>() {

                    @Override
                    public void onValueChange(ValueChangeEvent<Boolean> event) {
                        selectAllTextFlows(event.getValue());
                    }
                }));

        registerHandler(display.getRequirePreviewChk().addValueChangeHandler(
                new ValueChangeHandler<Boolean>() {

                    @Override
                    public void onValueChange(ValueChangeEvent<Boolean> event) {
                        display.setRequirePreview(event.getValue());
                        for (ListDataProvider<TransUnitReplaceInfo> provider : documentDataProviders
                                .values()) {
                            provider.refresh();
                        }
                        refreshReplaceAllButton();
                    }
                }));

        registerHandler(display.getReplaceAllButton().addClickHandler(
                new ClickHandler() {

                    @Override
                    public void onClick(ClickEvent event) {
                        replaceSelected();
                    }
                }));

        registerHandler(eventBus.addHandler(TransUnitUpdatedEvent.getType(),
                new TransUnitUpdatedEventHandler() {

                    @Override
                    public void onTransUnitUpdated(
                            final TransUnitUpdatedEvent event) {
                        TransUnitUpdateInfo updateInfo = event.getUpdateInfo();
                        TransUnitReplaceInfo replaceInfo =
                                allReplaceInfos.get(updateInfo.getTransUnit()
                                        .getId());
                        if (replaceInfo == null) {
                            Log.debug("no matching TU in document for TU update, id: "
                                    + updateInfo.getTransUnit().getId().getId());
                            return;
                        }
                        Log.debug("found matching TU for TU update, id: "
                                + updateInfo.getTransUnit().getId().getId());

                        if (replaceInfo.getReplaceState() == ReplacementState.Replaced
                                && !(replaceInfo.getTransUnit().getVerNum()
                                        .equals(updateInfo.getTransUnit()
                                                .getVerNum()))) {
                            // can't undo after additional update
                            setReplaceState(replaceInfo,
                                    ReplacementState.NotReplaced);
                            replaceInfo.setReplaceInfo(null);
                            replaceInfo
                                    .setPreviewState(PreviewState.NotFetched);
                            replaceInfo.setPreview(null);

                            MultiSelectionModel<TransUnitReplaceInfo> selectionModel =
                                    documentSelectionModels.get(updateInfo
                                            .getDocumentId().getId());
                            if (selectionModel == null) {
                                Log.error("missing selection model for document, id: "
                                        + updateInfo.getDocumentId().getId());
                            } else {
                                // clear selection to avoid accidental inclusion
                                // in batch
                                // operations
                                selectionModel.setSelected(replaceInfo, false);
                            }
                        }
                        replaceInfo.setTransUnit(updateInfo.getTransUnit());
                        refreshInfoDisplay(replaceInfo);
                    }
                }));

        registerHandler(eventBus.addHandler(
                WorkspaceContextUpdateEvent.getType(),
                new WorkspaceContextUpdateEventHandler() {
                    @Override
                    public void onWorkspaceContextUpdated(
                            WorkspaceContextUpdateEvent event) {
                        userWorkspaceContext.setProjectActive(event
                                .isProjectActive());
                        userWorkspaceContext.getWorkspaceContext()
                                .getWorkspaceId().getProjectIterationId()
                                .setProjectType(event.getProjectType());

                        display.setReplaceAllButtonVisible(userWorkspaceContext
                                .hasEditTranslationAccess());

                        for (TransUnitReplaceInfo info : allReplaceInfos
                                .values()) {
                            if (userWorkspaceContext.hasReadOnlyAccess()) {
                                setReplaceState(info,
                                        ReplacementState.NotAllowed);
                            } else if (info.getReplaceInfo() == null) {
                                setReplaceState(info,
                                        ReplacementState.NotReplaced);
                            } else {
                                setReplaceState(info, ReplacementState.Replaced);
                            }
                            refreshInfoDisplay(info);
                        }
                    }
                }));

        keyShortcutPresenter.register(KeyShortcut.Builder.builder()
                .addKey(new Keys(Keys.SHIFT_ALT_KEYS, 'A'))
                .setContext(ShortcutContext.ProjectWideSearch)
                .setDescription(messages.selectAllTextFlowsKeyShortcut())
                .setHandler(new KeyShortcutEventHandler() {
                    @Override
                    public void onKeyShortcut(KeyShortcutEvent event) {
                        display.getSelectAllChk().setValue(
                                !display.getSelectAllChk().getValue(), true);
                    }
                }).build());

        keyShortcutPresenter.register(KeyShortcut.Builder.builder()
                .addKey(new Keys(Keys.ALT_KEY, 'P'))
                .setContext(ShortcutContext.ProjectWideSearch)
                .setDescription(messages.focusSearchPhraseKeyShortcut())
                .setHandler(new KeyShortcutEventHandler() {
                    @Override
                    public void onKeyShortcut(KeyShortcutEvent event) {
                        display.focusFilterTextBox();
                    }
                }).build());

        keyShortcutPresenter.register(KeyShortcut.Builder.builder()
                .addKey(new Keys(Keys.ALT_KEY, 'C'))
                .setContext(ShortcutContext.ProjectWideSearch)
                .setDescription(messages.focusReplacementPhraseKeyShortcut())
                .setHandler(new KeyShortcutEventHandler() {
                    @Override
                    public void onKeyShortcut(KeyShortcutEvent event) {
                        display.focusReplacementTextBox();
                    }
                }).build());

        keyShortcutPresenter.register(KeyShortcut.Builder.builder()
                .addKey(new Keys(Keys.ALT_KEY, 'R'))
                .setContext(ShortcutContext.ProjectWideSearch)
                .setDescription(messages.replaceSelectedKeyShortcut())
                .setHandler(new KeyShortcutEventHandler() {
                    @Override
                    public void onKeyShortcut(KeyShortcutEvent event) {
                        replaceSelected();
                    }
                }).build());

        keyShortcutPresenter.register(KeyShortcut.Builder.builder()
                .addKey(new Keys(Keys.ALT_KEY, 'W'))
                .setContext(ShortcutContext.ProjectWideSearch)
                .setDescription(messages.toggleRowActionButtons())
                .setHandler(new KeyShortcutEventHandler() {
                    @Override
                    public void onKeyShortcut(KeyShortcutEvent event) {
                        showRowActionButtons = !showRowActionButtons;
                    }
                }).build());

        // TODO register key shortcuts:
        // Alt+Z undo last operation

        // detect currently focused document (if any)
        // Alt+A select current doc
        // Alt+V view current doc in editor
        // Shift+Alt+V search current doc in editor
    }

    private void showDocInEditor(String doc, boolean runSearch) {
        contextHolder.updateContext(null); // this will ensure editor reload
                                           // (prevent multiple cursors in code
                                           // mirror)
        HistoryToken token = HistoryToken.fromTokenString(history.getToken());
        token.setDocumentPath(doc);
        token.clearEditorFilterAndSearch();
        token.setView(MainView.Editor);
        if (runSearch) {
            token.setEditorTextSearch(token.getProjectSearchText());
        } else {
            token.setEditorTextSearch("");
        }
        history.newItem(token);
    }

    @Override
    protected void onUnbind() {
    }

    @Override
    public void onRevealDisplay() {
        keyShortcutPresenter.setContextActive(
                ShortcutContext.ProjectWideSearch, true);
        display.focusFilterTextBox();
    }

    public void concealDisplay() {
        keyShortcutPresenter.setContextActive(
                ShortcutContext.ProjectWideSearch, false);
    }

    private AsyncCallback<GetProjectTransUnitListsResult>
            buildProjectSearchCallback() {
        return new AsyncCallback<GetProjectTransUnitListsResult>() {

            @Override
            public void onFailure(Throwable caught) {
                Log.error("[SearchResultsPresenter] failed project-wide search request: "
                        + caught.getMessage());
                eventBus.fireEvent(new NotificationEvent(Severity.Error,
                        messages.searchFailed()));
                display.clearAll();
                display.getSearchResponseLabel().setText(
                        messages.searchFailed());
            }

            @Override
            public void onSuccess(GetProjectTransUnitListsResult result) {
                int textFlowCount = displaySearchResults(result);
                if (result.getDocumentIds().size() == 0) {
                    // TODO add case sensitivity and scope
                    display.getSearchResponseLabel().setText(
                            messages.searchForPhraseReturnedNoResults(result
                                    .getSearchAction().getSearchString()));
                } else {
                    // TODO add case sensitivity and scope
                    display.getSearchResponseLabel().setText(
                            messages.showingResultsForProjectWideSearch(result
                                    .getSearchAction().getSearchString(),
                                    textFlowCount, result.getDocumentIds()
                                            .size()));
                }
                display.setSearching(false);
            }

        };
    }

    private Delegate<TransUnitReplaceInfo> ensureReplaceButtonDelegate() {
        if (replaceButtonDelegate == null) {
            replaceButtonDelegate = buildReplaceButtonDelegate();
        }
        return replaceButtonDelegate;
    }

    private Delegate<TransUnitReplaceInfo> buildReplaceButtonDelegate() {
        return new Delegate<TransUnitReplaceInfo>() {

            @Override
            public void execute(TransUnitReplaceInfo info) {
                fireReplaceTextEvent(Collections.singletonList(info));
            }

        };
    }

    private Delegate<TransUnitReplaceInfo> ensureUndoButtonDelegate() {
        if (undoButtonDelegate == null) {
            undoButtonDelegate = buildUndoButtonDelegate();
        }
        return undoButtonDelegate;
    }

    private Delegate<TransUnitReplaceInfo> buildUndoButtonDelegate() {
        return new Delegate<TransUnitReplaceInfo>() {
            @Override
            public void execute(final TransUnitReplaceInfo info) {
                fireUndoEvent(Collections.singletonList(info.getReplaceInfo()));
            }
        };
    }

    private Delegate<TransUnitReplaceInfo> ensurePreviewButtonDelegate() {
        if (previewButtonDelegate == null) {
            previewButtonDelegate = buildPreviewButtonDelegate();
        }
        return previewButtonDelegate;
    }

    private Delegate<TransUnitReplaceInfo> buildPreviewButtonDelegate() {
        return new Delegate<TransUnitReplaceInfo>() {

            @Override
            public void execute(TransUnitReplaceInfo info) {
                switch (info.getPreviewState()) {
                case NotAllowed:
                    break;
                case Show:
                    info.setPreviewState(PreviewState.Hide);
                    refreshInfoDisplay(info);
                    break;
                default:
                    firePreviewEvent(Collections.singletonList(info));
                    break;
                }
            }

        };
    }

    private Delegate<TransUnitReplaceInfo> ensureGoToEditorDelegate() {
        if (goToEditorDelegate == null) {
            goToEditorDelegate = new Delegate<TransUnitReplaceInfo>() {
                @Override
                public void execute(TransUnitReplaceInfo info) {
                    // in the case of editor still in filter mode or search
                    // result,
                    // requested text flow may not appear in result. We want to
                    // make
                    // sure it reloads everything for this document.

                    HistoryToken token = history.getHistoryToken();
                    contextHolder.updateContext(null); // this will ensure
                                                       // HistoryEventHandlerService
                                                       // fire InitEditorEvent
                    token.clearEditorFilterAndSearch();
                    token.setView(MainView.Editor);
                    token.setDocumentPath(docPaths.get(info.getDocId()));
                    token.setTextFlowId(info.getTransUnit().getId().toString());
                    history.newItem(token);
                }
            };
        }
        return goToEditorDelegate;
    }

    /**
     * @return
     */
    private Handler buildSelectionChangeHandler() {
        return new Handler() {

            @Override
            public void onSelectionChange(SelectionChangeEvent event) {
                int selectedFlows = countSelectedFlows();

                if (autoPreview) {
                    previewSelected(true, true);
                }

                if (selectedFlows == 0) {
                    setUiForNothingSelected();
                } else {
                    refreshReplaceAllButton();
                }
            }
        };
    }

    /**
     * Build selection change handler to uncheck 'Select All' in document list
     * if any child is unchecked
     *
     * @param docId
     * @param selectionModel
     * @param dataProvider
     * @return
     */
    private Handler buildSelectionChangeDeselectHandler(final Long docId,
            final MultiSelectionModel<TransUnitReplaceInfo> selectionModel,
            final ListDataProvider<TransUnitReplaceInfo> dataProvider) {
        return new Handler() {
            @Override
            public void onSelectionChange(SelectionChangeEvent event) {
                boolean isAllSelected = true;
                for (TransUnitReplaceInfo data : dataProvider.getList()) {
                    if (!selectionModel.isSelected(data)) {
                        isAllSelected = false;
                        break;
                    }
                }

                if (selectAllDocList.containsKey(docId)) {
                    selectAllDocList.get(docId).setValue(isAllSelected);
                }

                isAllSelected = true;
                for (HasValue<Boolean> docSelectAll : selectAllDocList.values()) {
                    if (!docSelectAll.getValue().booleanValue()) {
                        isAllSelected = false;
                        break;
                    }
                }
                display.getSelectAllChk().setValue(isAllSelected);
            }
        };
    }

    /**
     *
     * @param skipEmptyNotification
     *            true to silently ignore the request when no rows are selected
     */
    private void previewSelected(boolean skipEmptyNotification,
            boolean hideNonSelectedPreviews) {
        List<TransUnitReplaceInfo> selected = getAllSelected();
        if (!skipEmptyNotification || !selected.isEmpty()) {
            firePreviewEvent(selected);
        }

        if (hideNonSelectedPreviews) {
            for (TransUnitReplaceInfo info : allReplaceInfos.values()) {
                if (info.getPreviewState() == PreviewState.Show
                        && !selected.contains(info)) {
                    info.setPreviewState(PreviewState.Hide);
                }
            }
        }
    }

    /**
     * Fire a {@link PreviewReplaceText} event for the given {@link TransUnit}s
     * using parameters from the current history state. This will also update
     * the state and refresh the table to show 'previewing' indicator.
     *
     * If toPreview is empty, this is a no-op.
     *
     * @param toPreview
     */
    private void firePreviewEvent(List<TransUnitReplaceInfo> toPreview) {
        if (toPreview.isEmpty()) {
            eventBus.fireEvent(new NotificationEvent(Severity.Warning, messages
                    .noTextFlowsSelected()));
            return;
        }
        final String replacement = display.getReplacementTextBox().getValue();
        // prevent failed requests for empty replacement
        if (replacement.isEmpty()) {
            eventBus.fireEvent(new NotificationEvent(Severity.Warning, messages
                    .noReplacementPhraseEntered()));
            return;
        }

        List<TransUnit> transUnits = new ArrayList<TransUnit>();
        for (TransUnitReplaceInfo replaceInfo : toPreview) {
            switch (replaceInfo.getPreviewState()) {
            case NotFetched:
                transUnits.add(replaceInfo.getTransUnit());
                replaceInfo.setPreviewState(PreviewState.Fetching);
                refreshInfoDisplay(replaceInfo);
                break;
            case Hide:
                replaceInfo.setPreviewState(PreviewState.Show);
                refreshInfoDisplay(replaceInfo);
                break;
            }
        }

        if (transUnits.isEmpty()) {
            // could notify user, doesn't seem worthwhile
            return;
        }

        final String searchText = display.getFilterTextBox().getValue();
        boolean caseSensitive = display.getCaseSensitiveChk().getValue();
        ReplaceText action =
                new ReplaceText(transUnits, searchText, replacement,
                        caseSensitive);
        PreviewReplaceText previewAction = new PreviewReplaceText(action);
        dispatcher.execute(previewAction,
                new AsyncCallback<PreviewReplaceTextResult>() {

                    @Override
                    public void onFailure(Throwable e) {
                        Log.error(
                                "[SearchResultsPresenter] Preview replace text failure "
                                        + e, e);
                        eventBus.fireEvent(new NotificationEvent(
                                Severity.Error, messages.previewFailed()));
                        // may want to change TU state from 'previewing'
                        // (possibly error
                        // state)
                    }

                    @Override
                    public void
                            onSuccess(final PreviewReplaceTextResult result) {
                        for (TransUnitUpdatePreview preview : result
                                .getPreviews()) {
                            TransUnitReplaceInfo replaceInfo =
                                    allReplaceInfos.get(preview.getId());
                            if (replaceInfo == null) {
                                Log.error("no replace info found for previewed text flow");
                            } else {
                                Log.debug("setting preview state for preview id: "
                                        + preview.getId());
                                replaceInfo.setPreview(preview);
                                replaceInfo.setPreviewState(PreviewState.Show);
                                refreshInfoDisplay(replaceInfo);
                            }
                        }
                        refreshReplaceAllButton();
                        eventBus.fireEvent(new NotificationEvent(Severity.Info,
                                messages.fetchedPreview()));
                    }

                });
    }

    private void replaceSelected() {
        if (replaceSelectedAllowed()) {
            fireReplaceTextEvent(getAllSelected());
        } else {
            eventBus.fireEvent(new NotificationEvent(Severity.Warning, messages
                    .previewRequiredBeforeReplace()));
        }
    }

    private List<TransUnitReplaceInfo> getAllSelected() {
        List<TransUnitReplaceInfo> selected =
                new ArrayList<TransUnitReplaceInfo>();
        for (MultiSelectionModel<TransUnitReplaceInfo> sel : documentSelectionModels
                .values()) {
            selected.addAll(sel.getSelectedSet());
        }
        return selected;
    }

    /**
     * Fire a {@link ReplaceText} event for the given {@link TransUnit}s using
     * parameters from the current history state. This will also update the
     * state and refresh the table to show 'replacing' indicator.
     *
     * An event will not be fired if toReplace is empty or contains no text
     * flows that are eligible for a replace operation.
     *
     * @param toReplace
     *            list of TransUnits to replace
     */
    private void fireReplaceTextEvent(List<TransUnitReplaceInfo> toReplace) {
        if (!userWorkspaceContext.hasEditTranslationAccess()) {
            eventBus.fireEvent(new NotificationEvent(Severity.Warning, messages
                    .youAreNotAllowedToModifyTranslations()));
            return;
        }

        if (toReplace.isEmpty()) {
            eventBus.fireEvent(new NotificationEvent(Severity.Warning, messages
                    .noTextFlowsSelected()));
            return;
        }
        List<TransUnit> transUnits = new ArrayList<TransUnit>();
        for (TransUnitReplaceInfo info : toReplace) {
            switch (info.getReplaceState()) {
            case NotReplaced:
                transUnits.add(info.getTransUnit());
                setReplaceState(info, ReplacementState.Replacing);
                info.setPreviewState(PreviewState.Hide);
                refreshInfoDisplay(info);
                break;
            }
        }

        if (transUnits.isEmpty()) {
            eventBus.fireEvent(new NotificationEvent(Severity.Warning, messages
                    .noReplacementsToMake()));
            return;
        }

        final String searchText = display.getFilterTextBox().getValue();
        final String replacement = display.getReplacementTextBox().getValue();
        boolean caseSensitive = display.getCaseSensitiveChk().getValue();
        ReplaceText action =
                new ReplaceText(transUnits, searchText, replacement,
                        caseSensitive);
        dispatcher.execute(action, new AsyncCallback<UpdateTransUnitResult>() {

            @Override
            public void onFailure(Throwable e) {
                Log.error("[SearchResultsPresenter] Replace text failure " + e,
                        e);
                eventBus.fireEvent(new NotificationEvent(Severity.Error,
                        messages.replaceTextFailure()));
                // may want to change state from 'replacing' (possibly add error
                // state)
            }

            @Override
            public void onSuccess(final UpdateTransUnitResult result) {
                final List<TransUnitUpdateInfo> updateInfoList =
                        processSuccessfulReplacements(result
                                .getUpdateInfoList());

                if (updateInfoList.isEmpty()) {
                    eventBus.fireEvent(new NotificationEvent(Info, messages
                            .noReplacementsToMake()));
                    return;
                }

                String message;
                if (updateInfoList.size() == 1) {
                    TransUnitUpdateInfo info = updateInfoList.get(0);
                    String text = info.getTransUnit().getTargets().get(0);
                    String truncatedText =
                            text.substring(
                                    0,
                                    (text.length() <= TRUNCATED_TARGET_LENGTH ? text
                                            .length() : TRUNCATED_TARGET_LENGTH));
                    int oneBasedRowIndex =
                            info.getTransUnit().getRowIndex() + 1;
                    String docName = docPaths.get(info.getDocumentId().getId());
                    message =
                            messages.replacedTextInOneTextFlow(searchText,
                                    replacement, docName, oneBasedRowIndex,
                                    truncatedText);
                } else {
                    message =
                            messages.replacedTextInMultipleTextFlows(
                                    searchText, replacement,
                                    updateInfoList.size());
                }

                final UndoLink undoLink = undoLinkProvider.get();
                undoLink.prepareUndoFor(result);
                undoLink.setUndoCallback(new UndoLink.UndoCallback() {
                    @Override
                    public void preUndo() {
                        executePreUndo(updateInfoList);
                    }

                    @Override
                    public void postUndoSuccess() {
                        executePostSucess(result);
                    }
                });

                NotificationEvent event =
                        new NotificationEvent(Info, message, undoLink);
                eventBus.fireEvent(event);
            }
        });
    }

    /**
     * Fire a {@link RevertTransUnitUpdates} event to request undoing of the
     * given updates.
     *
     * @param updateInfoList
     *            updates that are to be reverted
     */
    private void fireUndoEvent(List<TransUnitUpdateInfo> updateInfoList) {
        // TODO only fire undo for flows that are undoable?
        // rpc method should cope with this anyway, so no big deal

        eventBus.fireEvent(new NotificationEvent(Severity.Info, messages
                .undoInProgress()));
        RevertTransUnitUpdates action = new RevertTransUnitUpdates();
        executePreUndo(updateInfoList);
        dispatcher.execute(action, new AsyncCallback<UpdateTransUnitResult>() {
            @Override
            public void onFailure(Throwable caught) {
                eventBus.fireEvent(new NotificationEvent(Severity.Error,
                        messages.undoReplacementFailure()));
            }

            @Override
            public void onSuccess(UpdateTransUnitResult result) {
                executePostSucess(result);
            }
        });
    }

    private void executePreUndo(List<TransUnitUpdateInfo> updateInfoList) {
        for (TransUnitUpdateInfo updateInfo : updateInfoList) {
            TransUnitReplaceInfo replaceInfo =
                    allReplaceInfos.get(updateInfo.getTransUnit().getId());
            // may be null if another search has been performed since the
            // replacement
            if (replaceInfo != null) {
                setReplaceState(replaceInfo, ReplacementState.Undoing);
                refreshInfoDisplay(replaceInfo);
            }
        }
    }

    private void executePostSucess(UpdateTransUnitResult result) {
        for (TransUnitUpdateInfo info : result.getUpdateInfoList()) {
            TransUnitReplaceInfo replaceInfo =
                    allReplaceInfos.get(info.getTransUnit().getId());
            setReplaceState(replaceInfo, ReplacementState.NotReplaced);
            if (replaceInfo.getPreview() == null) {
                replaceInfo.setPreviewState(PreviewState.NotFetched);
            } else {
                MultiSelectionModel<TransUnitReplaceInfo> selectionModel =
                        documentSelectionModels.get(replaceInfo.getDocId());
                if (selectionModel != null
                        && selectionModel.isSelected(replaceInfo)) {
                    replaceInfo.setPreviewState(PreviewState.Show);
                } else {
                    replaceInfo.setPreviewState(PreviewState.Hide);
                }
            }
            refreshInfoDisplay(replaceInfo);
        }
        refreshReplaceAllButton();
    }

    /**
     * Update data providers and refresh display for successful replacements.
     *
     * @param updateInfoList
     *            info on replacements. If any of these are not successful, they
     *            are ignored.
     * @return the number of updates that are
     *         {@link TransUnitUpdateInfo#isSuccess()}
     */
    private List<TransUnitUpdateInfo> processSuccessfulReplacements(
            final List<TransUnitUpdateInfo> updateInfoList) {
        List<TransUnitUpdateInfo> successfulReplacements =
                new ArrayList<TransUnitUpdateInfo>();
        for (TransUnitUpdateInfo updateInfo : updateInfoList) {
            if (updateInfo.isSuccess()) {
                TransUnitReplaceInfo replaceInfo =
                        allReplaceInfos.get(updateInfo.getTransUnit().getId());
                if (replaceInfo != null) {
                    if (updateInfo.isTargetChanged()) {
                        successfulReplacements.add(updateInfo);
                    }
                    replaceInfo.setReplaceInfo(updateInfo);
                    ReplacementState replaceState = ReplacementState.Replaced;
                    setReplaceState(replaceInfo, replaceState);
                    // this should be done when the TU update event comes in
                    // anyway may want to remove this
                    replaceInfo.setTransUnit(updateInfo.getTransUnit());
                    refreshInfoDisplay(replaceInfo);
                }
            } else {
                eventBus.fireEvent(new NotificationEvent(Severity.Error,
                        messages.replaceTextFailureWithMessage(updateInfo
                                .getTransUnit().getId().toString(),
                                updateInfo.getErrorMessage())));
            }
        }
        return successfulReplacements;
    }

    /**
     * Sets the info item to its current index in the containing data provider
     * to force the provider to recognize that it has changed.
     *
     * @param info
     */
    private void refreshInfoDisplay(TransUnitReplaceInfo info) {
        ListDataProvider<TransUnitReplaceInfo> dataProvider =
                documentDataProviders.get(info.getDocId());
        if (dataProvider != null) {
            List<TransUnitReplaceInfo> list = dataProvider.getList();
            try {
                list.set(list.indexOf(info), info);
            } catch (IndexOutOfBoundsException e) {
                Log.error("failed to re-set info object in its dataprovider", e);
            }
        }
    }

    /**
     * Show search results as documents in the display. This will replace any
     * existing results being displayed.
     *
     * @param result
     *            results to display
     * @return the number of text flows that were displayed
     */
    private int displaySearchResults(GetProjectTransUnitListsResult result) {
        clearAllExistingData();
        int totalTransUnits = 0;
        for (Long docId : result.getDocumentIds()) {
            docPaths.put(docId, result.getDocPath(docId));
            List<TransUnit> transUnits = result.getUnits(docId);
            totalTransUnits += transUnits.size();
            displayDocumentResults(docId, result.getDocPath(docId), transUnits);
        }
        return totalTransUnits;
    }

    /**
     * Display header and all results for a single document.
     *
     * @param docId
     * @param docPathName
     * @param transUnits
     */
    private void displayDocumentResults(final Long docId,
            final String docPathName, List<TransUnit> transUnits) {
        final ListDataProvider<TransUnitReplaceInfo> dataProvider;
        final MultiSelectionModel<TransUnitReplaceInfo> selectionModel =
                display.createMultiSelectionModel();

        documentSelectionModels.put(docId, selectionModel);
        ClickHandler showDocHandler = showDocClickHandler(docPathName, false);
        ClickHandler searchDocHandler = showDocClickHandler(docPathName, true);
        ClickHandler infoClickHandler = new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                display.showDiffLegend();
            }
        };

        ValueChangeHandler<Boolean> selectDocHandler =
                selectAllHandler(docId, selectionModel);

        if (showRowActionButtons) {
            dataProvider =
                    display.addDocument(docPathName, showDocHandler,
                            searchDocHandler, infoClickHandler, selectionModel,
                            selectDocHandler, ensurePreviewButtonDelegate(),
                            ensureReplaceButtonDelegate(),
                            ensureUndoButtonDelegate(),
                            ensureGoToEditorDelegate());
        } else {
            dataProvider =
                    display.addDocument(docPathName, showDocHandler,
                            searchDocHandler, infoClickHandler, selectionModel,
                            selectDocHandler, ensureGoToEditorDelegate());
        }

        selectAllDocList.put(docId, display.getSelectAllCheckbox());
        documentDataProviders.put(docId, dataProvider);

        selectionModel.addSelectionChangeHandler(selectionChangeHandler);

        List<TransUnitReplaceInfo> data = dataProvider.getList();
        for (TransUnit tu : transUnits) {
            TransUnitReplaceInfo info = new TransUnitReplaceInfo(docId, tu);
            // default state is NotReplaced, this call triggers read-only check
            setReplaceState(info, ReplacementState.NotReplaced);
            data.add(info);
            allReplaceInfos.put(tu.getId(), info);
        }

        selectionModel
                .addSelectionChangeHandler(buildSelectionChangeDeselectHandler(
                        docId, selectionModel, dataProvider));

        Collections.sort(data, TransUnitReplaceInfo.getRowComparator());
    }

    /**
     * Build a click handler to show a document in the editor.
     *
     * @see #showDocInEditor(String, boolean)
     */
    private ClickHandler showDocClickHandler(final String docPathName,
            final boolean runSearch) {
        ClickHandler showDocClickHandler = new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                showDocInEditor(docPathName, runSearch);
            }
        };
        return showDocClickHandler;
    }

    /**
     * Build a handler to select and de-select all text flows in a document
     *
     * @param docId
     * @param selectionModel
     * @return the new handler
     */
    private ValueChangeHandler<Boolean> selectAllHandler(final Long docId,
            final MultiSelectionModel<TransUnitReplaceInfo> selectionModel) {
        return new ValueChangeHandler<Boolean>() {
            @Override
            public void onValueChange(ValueChangeEvent<Boolean> event) {
                if (event.getValue()) {
                    ListDataProvider<TransUnitReplaceInfo> dataProvider =
                            documentDataProviders.get(docId);
                    if (dataProvider != null) {
                        for (TransUnitReplaceInfo info : dataProvider.getList()) {
                            selectionModel.setSelected(info, true);
                        }
                    }
                } else {
                    selectionModel.clear();
                }
            }
        };
    }

    public void updateViewAndRun(String searchText, boolean caseSensitive,
            boolean searchInSource, boolean searchInTarget) {
        display.setHighlightString(searchText);
        display.getFilterTextBox().setValue(searchText, false);
        display.getCaseSensitiveChk().setValue(caseSensitive, false);

        clearAllExistingData();

        if (!searchText.isEmpty()) {
            display.setSearching(true);
            GetProjectTransUnitLists action =
                    new GetProjectTransUnitLists(searchText, searchInSource,
                            searchInTarget, caseSensitive,
                            windowLocation.getQueryDocuments());
            dispatcher.execute(action, projectSearchCallback);
        }
    }

    public void updateReplacementText(String replacement) {
        display.getReplacementTextBox().setValue(replacement, true);
        for (TransUnitReplaceInfo info : allReplaceInfos.values()) {
            info.setPreview(null);
            info.setPreviewState(PreviewState.NotFetched);
            refreshInfoDisplay(info);
        }
        refreshReplaceAllButton();

        if (autoPreview) {
            previewSelected(true, false);
        }
    }

    /**
     * Clear all data providers, selection models, replace infos, and removes
     * all documents from the display
     */
    private void clearAllExistingData() {
        documentDataProviders.clear();
        documentSelectionModels.clear();
        display.getSelectAllChk().setValue(false, false);
        allReplaceInfos.clear();
        display.clearAll();
        selectAllDocList.clear();
        setUiForNothingSelected();
    }

    private void setUiForNothingSelected() {
        display.setReplaceAllButtonEnabled(false);
    }

    private void refreshReplaceAllButton() {
        display.setReplaceAllButtonEnabled(replaceSelectedAllowed());
    }

    /**
     * Checks that something is selected and that if previews are required, all
     * selected rows have previews
     *
     * @return true if conditions are met to replace selected
     */
    private boolean replaceSelectedAllowed() {
        boolean requirePreview = display.getRequirePreviewChk().getValue();
        boolean canReplace =
                countSelectedFlows() != 0
                        && (!requirePreview || allSelectedHavePreview());
        return canReplace;
    }

    /**
     * @return false if any selected text flows do not have an available
     *         preview. true if no text flows are selected or all have previews.
     */
    private boolean allSelectedHavePreview() {
        for (MultiSelectionModel<TransUnitReplaceInfo> model : documentSelectionModels
                .values()) {
            for (TransUnitReplaceInfo info : model.getSelectedSet()) {
                switch (info.getPreviewState()) {
                case NotFetched:
                case Fetching:
                    return false;
                }
            }
        }
        return true;
    }

    private int countSelectedFlows() {
        int selectedFlows = 0;
        for (MultiSelectionModel<TransUnitReplaceInfo> model : documentSelectionModels
                .values()) {
            selectedFlows += model.getSelectedSet().size();
        }
        return selectedFlows;
    }

    /**
     * Set the replace state for a {@link TransUnitReplaceInfo}, adjusting to
     * {@link ReplacementState#NotAllowed} if the workspace is read-only.
     *
     * @param replaceInfo
     * @param replaceState
     *            to set, ignored if workspace is read-only
     */
    private void setReplaceState(TransUnitReplaceInfo replaceInfo,
            ReplacementState replaceState) {
        if (!userWorkspaceContext.hasEditTranslationAccess()) {
            replaceInfo.setReplaceState(ReplacementState.NotAllowed);
        } else {
            replaceInfo.setReplaceState(replaceState);
        }
    }

    private void selectAllTextFlows(boolean selected) {
        for (HasValue<Boolean> docSelectAll : selectAllDocList.values()) {
            docSelectAll.setValue(selected, true);
        }
    }

    private void updateSearch() {
        boolean changed = false;
        HistoryToken token = history.getHistoryToken();

        Boolean caseSensitive = display.getCaseSensitiveChk().getValue();
        if (caseSensitive != token.getProjectSearchCaseSensitive()) {
            token.setProjectSearchCaseSensitive(caseSensitive);
            changed = true;
        }

        String searchPhrase = display.getFilterTextBox().getValue();
        if (!searchPhrase.equals(token.getProjectSearchText())) {
            token.setProjectSearchText(searchPhrase);
            changed = true;
        }

        String selected = display.getSelectedSearchField();
        boolean searchSource =
                selected.equals(Display.SEARCH_FIELD_SOURCE)
                        || selected.equals(Display.SEARCH_FIELD_BOTH);
        boolean searchTarget =
                selected.equals(Display.SEARCH_FIELD_TARGET)
                        || selected.equals(Display.SEARCH_FIELD_BOTH);
        if (searchSource != token.isProjectSearchInSource()) {
            token.setProjectSearchInSource(searchSource);
            changed = true;
        }
        if (searchTarget != token.isProjectSearchInTarget()) {
            token.setProjectSearchInTarget(searchTarget);
            changed = true;
        }

        if (changed) {
            history.newItem(token);
        }
    }
}
TOP

Related Classes of org.zanata.webtrans.client.presenter.SearchResultsPresenter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.