/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard ([email protected])
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition 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 General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.web.wicket;

import java.text.NumberFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.HiddenField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.projectforge.common.DateHolder;
import org.projectforge.common.DatePrecision;
import org.projectforge.common.NumberHelper;
import org.projectforge.core.BaseDao;
import org.projectforge.core.BaseSearchFilter;
import org.projectforge.user.PFUserContext;
import org.projectforge.user.PFUserDO;
import org.projectforge.user.UserGroupCache;
import org.projectforge.web.user.UserSelectPanel;
import org.projectforge.web.wicket.bootstrap.GridBuilder;
import org.projectforge.web.wicket.bootstrap.GridSize;
import org.projectforge.web.wicket.components.DateTimePanel;
import org.projectforge.web.wicket.components.DateTimePanelSettings;
import org.projectforge.web.wicket.components.LabelValueChoiceRenderer;
import org.projectforge.web.wicket.components.SingleButtonPanel;
import org.projectforge.web.wicket.flowlayout.CheckBoxButton;
import org.projectforge.web.wicket.flowlayout.DivPanel;
import org.projectforge.web.wicket.flowlayout.FieldSetIconPosition;
import org.projectforge.web.wicket.flowlayout.FieldsetPanel;
import org.projectforge.web.wicket.flowlayout.HiddenInputPanel;
import org.projectforge.web.wicket.flowlayout.HtmlCommentPanel;
import org.projectforge.web.wicket.flowlayout.IconPanel;
import org.projectforge.web.wicket.flowlayout.IconType;
import org.projectforge.web.wicket.flowlayout.InputPanel;
import org.projectforge.web.wicket.flowlayout.MyComponentsRepeater;

public abstract class AbstractListForm<F extends BaseSearchFilter, P extends AbstractListPage< ? , ? , ? >> extends
AbstractSecuredForm<F, P>
{
  private static final long serialVersionUID = 1304394324524767035L;

  public static final String I18N_ONLY_DELETED = "onlyDeleted";

  public static final String I18N_ONLY_DELETED_TOOLTIP = "onlyDeleted.tooltip";

  protected F searchFilter;

  protected abstract F newSearchFilterInstance();

  protected Integer pageSize;

  protected GridBuilder gridBuilder;

  private DivPanel extendedFilter;

  private Label modifiedSearchExpressionLabel;

  private String modificationSince;

  protected DateTimePanel startDateTimePanel;

  protected DateTimePanel stopDateTimePanel;

  @SpringBean(name = "userGroupCache")
  protected UserGroupCache userGroupCache;

  private FieldsetPanel optionsFieldsetPanel;

  /**
   * List to create action buttons in the desired order before creating the RepeatingView.
   */
  protected MyComponentsRepeater<Component> actionButtons;

  private SingleButtonPanel cancelButtonPanel;

  protected SingleButtonPanel resetButtonPanel;

  protected SingleButtonPanel searchButtonPanel;

  private SingleButtonPanel nextButtonPanel;

  protected FieldsetPanel pageSizeFieldsetPanel;

  public static final int[] PAGE_SIZES = new int[] { 3, 5, 10, 25, 50, 100, 200, 500, 1000};

  public static DropDownChoice<Integer> getPageSizeDropDownChoice(final String id, final Locale locale, final IModel<Integer> model,
      final int minValue, final int maxValue)
      {
    final LabelValueChoiceRenderer<Integer> pageSizeChoiceRenderer = new LabelValueChoiceRenderer<Integer>();
    final NumberFormat nf = NumberFormat.getInstance(locale);
    for (final int size : PAGE_SIZES) {
      if (size >= minValue && size <= maxValue) {
        pageSizeChoiceRenderer.addValue(size, nf.format(size));
      }
    }
    final DropDownChoice<Integer> pageSizeChoice = new DropDownChoice<Integer>(id, model, pageSizeChoiceRenderer.getValues(),
        pageSizeChoiceRenderer);
    pageSizeChoice.setNullValid(false);
    return pageSizeChoice;
      }

  public AbstractListForm(final P parentPage)
  {
    super(parentPage);
    getSearchFilter();
  }

  @SuppressWarnings("serial")
  @Override
  protected void init()
  {
    super.init();
    final FeedbackPanel feedbackPanel = new FeedbackPanel("feedback");
    feedbackPanel.setOutputMarkupId(true);
    add(feedbackPanel);

    gridBuilder = newGridBuilder(this, "filter");
    if (isFilterVisible() == true) {
      {
        // Fieldset search filter
        final FieldsetPanel fs = gridBuilder.newFieldset(getString("searchFilter"));
        if (parentPage.getBaseDao().isHistorizable() == true) {
          IconPanel icon = new IconPanel(fs.newIconChildId(), IconType.PLUS_SIGN, getString("filter.extendedSearch"))
          .setOnClick("javascript:showExtendedFilter();");
          icon.setMarkupId("showExtendedFilter");
          fs.add(icon, FieldSetIconPosition.BOTTOM_LEFT);
          icon = new IconPanel(fs.newIconChildId(), IconType.MINUS_SIGN).setOnClick("javascript:hideExtendedFilter();");
          icon.setMarkupId("hideExtendedFilter");
          fs.add(icon, FieldSetIconPosition.BOTTOM_LEFT);
        }
        final TextField< ? > searchTextField = createSearchTextField();
        fs.add(searchTextField);
        fs.setLabelFor(searchTextField);
        final Model<String> modifiedSearchExpressionModel = new Model<String>() {
          @Override
          public String getObject()
          {
            return getModifiedSearchExpressionLabel(AbstractListForm.this, searchFilter.getSearchString());
          }
        };
        final DivPanel div = new DivPanel(fs.newChildId()) {
          /**
           * @see org.projectforge.web.wicket.flowlayout.DivPanel#isVisible()
           */
          @Override
          public boolean isVisible()
          {
            return StringUtils.isNotBlank(searchFilter.getSearchString()) == true;
          }
        };
        div.add(AttributeModifier.append("class", "modifiedSearchExpressionLabel"));
        fs.add(div);
        modifiedSearchExpressionLabel = new Label(div.newChildId(), modifiedSearchExpressionModel);
        modifiedSearchExpressionLabel.setEscapeModelStrings(false);
        div.add(modifiedSearchExpressionLabel);

        fs.addHelpIcon(getString("tooltip.lucene.link"), FieldSetIconPosition.TOP_RIGHT).setOnClickLocation(getRequestCycle(),
            WebConstants.DOC_LINK_HANDBUCH_LUCENE, true);
        final String helpKeyboardImageTooltip = getHelpKeyboardImageTooltip();
        if (helpKeyboardImageTooltip != null) {
          fs.addKeyboardHelpIcon(helpKeyboardImageTooltip);
        }
      }
    }
    if (parentPage.getBaseDao().isHistorizable() == true && isFilterVisible() == true) {
      addExtendedFilter();
    }
    if (showOptionsPanel() == true) {
      gridBuilder.newSplitPanel(GridSize.COL66);
      optionsFieldsetPanel = gridBuilder.newFieldset(getOptionsLabel()).suppressLabelForWarning();
      final DivPanel optionsCheckBoxesPanel = optionsFieldsetPanel.addNewCheckBoxButtonDiv();
      onOptionsPanelCreate(optionsFieldsetPanel, optionsCheckBoxesPanel);
      if (showHistorySearchAndDeleteCheckbox() == true) {
        optionsCheckBoxesPanel.add(createAutoRefreshCheckBoxButton(optionsCheckBoxesPanel.newChildId(),
            new PropertyModel<Boolean>(getSearchFilter(), "deleted"), getString(I18N_ONLY_DELETED), getString(I18N_ONLY_DELETED_TOOLTIP))
            .setWarning());
        optionsCheckBoxesPanel.add(createAutoRefreshCheckBoxButton(optionsCheckBoxesPanel.newChildId(), new PropertyModel<Boolean>(
            getSearchFilter(), "searchHistory"), getString("search.searchHistory"), getString("search.searchHistory.additional.tooltip")));
      }
      if (optionsCheckBoxesPanel.hasChilds() == true) {
        optionsFieldsetPanel.add(optionsCheckBoxesPanel);
      }
      gridBuilder.newSplitPanel(GridSize.COL33);
    } else {
      gridBuilder.newGridPanel();
    }
    // DropDownChoice page size
    pageSizeFieldsetPanel = gridBuilder.newFieldset(getString("label.pageSize"));
    pageSizeFieldsetPanel.add(getPageSizeDropDownChoice(pageSizeFieldsetPanel.getDropDownChoiceId(), getLocale(), new PropertyModel<Integer>(this, "pageSize"), 25, 1000));

    final WebMarkupContainer buttonCell = new WebMarkupContainer("buttonCell");
    add(buttonCell);
    actionButtons = new MyComponentsRepeater<Component>("actionButtons");
    buttonCell.add(actionButtons.getRepeatingView());

    final Button cancelButton = new Button("button", new Model<String>("cancel")) {
      @Override
      public final void onSubmit()
      {
        getParentPage().onCancelSubmit();
      }
    };
    cancelButton.setDefaultFormProcessing(false);
    cancelButtonPanel = new SingleButtonPanel(getNewActionButtonChildId(), cancelButton, getString("cancel"), SingleButtonPanel.CANCEL);
    addActionButton(cancelButtonPanel);

    final Button resetButton = new Button("button", new Model<String>("reset")) {
      @Override
      public final void onSubmit()
      {
        getParentPage().onResetSubmit();
      }
    };
    resetButton.setDefaultFormProcessing(false);
    resetButtonPanel = new SingleButtonPanel(getNewActionButtonChildId(), resetButton, getString("reset"), SingleButtonPanel.RESET);
    addActionButton(resetButtonPanel);

    final Button nextButton = new Button("button", new Model<String>("next")) {
      @Override
      public final void onSubmit()
      {
        getParentPage().onNextSubmit();
      }
    };
    nextButtonPanel = new SingleButtonPanel(getNewActionButtonChildId(), nextButton, getString("next"), SingleButtonPanel.DEFAULT_SUBMIT);
    addActionButton(nextButtonPanel);

    final Button searchButton = new Button("button", new Model<String>("search")) {
      @Override
      public final void onSubmit()
      {
        getParentPage().onSearchSubmit();
      }
    };
    searchButtonPanel = new SingleButtonPanel(getNewActionButtonChildId(), searchButton, getString("search"),
        SingleButtonPanel.DEFAULT_SUBMIT);
    addActionButton(searchButtonPanel);

    setComponentsVisibility();
  }

  protected String getOptionsLabel()
  {
    return getString("label.options");
  }

  @SuppressWarnings("serial")
  private void addExtendedFilter()
  {
    gridBuilder.newSplitPanel(GridSize.COL66);
    extendedFilter = gridBuilder.getRowPanel();
    extendedFilter.setMarkupId("extendedFilter");
    if (searchFilter.isUseModificationFilter() == false) {
      extendedFilter.add(AttributeModifier.append("style", "display: none;"));
    }
    {
      final FieldsetPanel fieldset = gridBuilder.newFieldset(getString("search.periodOfModification"));
      fieldset.add(new HiddenInputPanel(fieldset.newChildId(), new HiddenField<Boolean>(InputPanel.WICKET_ID, new PropertyModel<Boolean>(
          searchFilter, "useModificationFilter"))).setHtmlId("useModificationFilter"));

      startDateTimePanel = new DateTimePanel(fieldset.newChildId(), new PropertyModel<Date>(searchFilter, "startTimeOfModification"),
          (DateTimePanelSettings) DateTimePanelSettings.get().withSelectProperty("startDateOfModification").withSelectPeriodMode(true),
          DatePrecision.MINUTE);
      fieldset.add(startDateTimePanel);
      fieldset.setLabelFor(startDateTimePanel);
      stopDateTimePanel = new DateTimePanel(fieldset.newChildId(), new PropertyModel<Date>(searchFilter, "stopTimeOfModification"),
          (DateTimePanelSettings) DateTimePanelSettings.get().withSelectProperty("stopDateOfModification").withSelectPeriodMode(true),
          DatePrecision.MINUTE);
      stopDateTimePanel.setRequired(false);
      fieldset.add(stopDateTimePanel);
      final HtmlCommentPanel comment = new HtmlCommentPanel(fieldset.newChildId(), new DatesAsUTCModel() {
        @Override
        public Date getStartTime()
        {
          return searchFilter.getStartTimeOfModification();
        }

        @Override
        public Date getStopTime()
        {
          return searchFilter.getStopTimeOfModification();
        }
      });
      fieldset.add(comment);
      // DropDownChoice for convenient selection of time periods.
      final LabelValueChoiceRenderer<String> timePeriodChoiceRenderer = new LabelValueChoiceRenderer<String>();
      timePeriodChoiceRenderer.addValue("lastMinute", getString("search.lastMinute"));
      timePeriodChoiceRenderer.addValue("lastMinutes:10", PFUserContext.getLocalizedMessage("search.lastMinutes", 10));
      timePeriodChoiceRenderer.addValue("lastMinutes:30", PFUserContext.getLocalizedMessage("search.lastMinutes", 30));
      timePeriodChoiceRenderer.addValue("lastHour", getString("search.lastHour"));
      timePeriodChoiceRenderer.addValue("lastHours:4", PFUserContext.getLocalizedMessage("search.lastHours", 4));
      timePeriodChoiceRenderer.addValue("today", getString("search.today"));
      timePeriodChoiceRenderer.addValue("sinceYesterday", getString("search.sinceYesterday"));
      timePeriodChoiceRenderer.addValue("lastDays:3", PFUserContext.getLocalizedMessage("search.lastDays", 3));
      timePeriodChoiceRenderer.addValue("lastDays:7", PFUserContext.getLocalizedMessage("search.lastDays", 7));
      timePeriodChoiceRenderer.addValue("lastDays:14", PFUserContext.getLocalizedMessage("search.lastDays", 14));
      timePeriodChoiceRenderer.addValue("lastDays:30", PFUserContext.getLocalizedMessage("search.lastDays", 30));
      timePeriodChoiceRenderer.addValue("lastDays:60", PFUserContext.getLocalizedMessage("search.lastDays", 60));
      timePeriodChoiceRenderer.addValue("lastDays:90", PFUserContext.getLocalizedMessage("search.lastDays", 90));
      final DropDownChoice<String> modificationSinceChoice = new DropDownChoice<String>(fieldset.getDropDownChoiceId(),
          new PropertyModel<String>(this, "modificationSince"), timePeriodChoiceRenderer.getValues(), timePeriodChoiceRenderer);
      modificationSinceChoice.setNullValid(true);
      modificationSinceChoice.setRequired(false);
      fieldset.add(modificationSinceChoice, true);
    }

    {
      gridBuilder.newSplitPanel(GridSize.COL33);
      final FieldsetPanel fs = gridBuilder.newFieldset(getString("modifiedBy"), getString("user"));

      final UserSelectPanel userSelectPanel = new UserSelectPanel(fs.newChildId(), new Model<PFUserDO>() {
        @Override
        public PFUserDO getObject()
        {
          return userGroupCache.getUser(searchFilter.getModifiedByUserId());
        }

        @Override
        public void setObject(final PFUserDO object)
        {
          if (object == null) {
            searchFilter.setModifiedByUserId(null);
          } else {
            searchFilter.setModifiedByUserId(object.getId());
          }
        }
      }, parentPage, "modifiedByUserId");
      fs.add(userSelectPanel);
      userSelectPanel.setDefaultFormProcessing(false);
      userSelectPanel.init().withAutoSubmit(true);
    }
    gridBuilder.setCurrentLevel(0); // Go back to main row panel.
  }

  /**
   * Here you can add elements to the given FieldsetPanel or optionsPanel. The optionsPanel is not yet added to the FieldsetPanel.
   * @param optionsFieldsetPanel
   * @param optionsPanel
   */
  protected void onOptionsPanelCreate(final FieldsetPanel optionsFieldsetPanel, final DivPanel optionsCheckBoxesPanel)
  {

  }

  /**
   * Creates a simple TextField and sets the focus on it. Overwrite this method if you want to add for example an auto completion text field
   * (ajax). Please don't forget to call addSearchFieldTooltip() in your method!
   */
  protected TextField< ? > createSearchTextField()
  {
    final TextField<String> searchField = new TextField<String>(InputPanel.WICKET_ID, new PropertyModel<String>(getSearchFilter(),
        "searchString"));
    createSearchFieldTooltip(searchField);
    searchField.add(WicketUtils.setFocus());
    return searchField;
  }

  protected void createSearchFieldTooltip(final Component field)
  {
    WicketUtils.addTooltip(field, getString("search.string.info.title"), getParentPage().getSearchToolTip(), false);
  }

  public void addActionButton(final Component entry)
  {
    this.actionButtons.add(entry);
  }

  public void prependActionButton(final Component entry)
  {
    this.actionButtons.add(0, entry);
  }

  public String getNewActionButtonChildId()
  {
    return this.actionButtons.newChildId();
  }

  @Override
  public void onBeforeRender()
  {
    super.onBeforeRender();
    actionButtons.render();
  }

  protected void setComponentsVisibility()
  {
    if (parentPage.isMassUpdateMode() == true) {
      cancelButtonPanel.setVisible(true);
      searchButtonPanel.setVisible(false);
      resetButtonPanel.setVisible(false);
      nextButtonPanel.setVisible(true);
      setDefaultButton(nextButtonPanel.getButton());
    } else {
      if (parentPage.isSelectMode() == false) {
        // Show cancel button only in select mode.
        cancelButtonPanel.setVisible(false);
      }
      searchButtonPanel.setVisible(true);
      resetButtonPanel.setVisible(true);
      nextButtonPanel.setVisible(false);
      setDefaultButton(searchButtonPanel.getButton());
    }
  }

  /**
   * onchange, onclick or submit without button.
   * @see org.apache.wicket.markup.html.form.Form#onSubmit()
   */
  @Override
  protected void onSubmit()
  {
    super.onSubmit();
    if (modificationSince != null) {
      final int pos = modificationSince.indexOf(':');
      final Integer number;
      if (pos >= 0) {
        number = NumberHelper.parseInteger(modificationSince.substring(pos + 1));
      } else {
        number = null;
      }
      final DateHolder dateHolder = new DateHolder(DatePrecision.MINUTE);
      if ("lastMinute".equals(modificationSince) == true) {
        dateHolder.add(Calendar.MINUTE, -1);
      } else if (modificationSince.startsWith("lastMinutes:") == true) {
        dateHolder.add(Calendar.MINUTE, -number);
      } else if ("lastHour".equals(modificationSince) == true) {
        dateHolder.add(Calendar.HOUR, -1);
      } else if (modificationSince.startsWith("lastHours:") == true) {
        dateHolder.add(Calendar.HOUR, -number);
      } else if ("today".equals(modificationSince) == true) {
        dateHolder.setBeginOfDay();
      } else if ("sinceYesterday".equals(modificationSince) == true) {
        dateHolder.add(Calendar.DAY_OF_YEAR, -1);
        dateHolder.setBeginOfDay();
      } else if (modificationSince.startsWith("lastDays") == true) {
        dateHolder.add(Calendar.DAY_OF_YEAR, -number);
        dateHolder.setBeginOfDay();
      }
      searchFilter.setStartTimeOfModification(dateHolder.getDate());
      startDateTimePanel.markModelAsChanged();
      searchFilter.setStopTimeOfModification(null);
      stopDateTimePanel.markModelAsChanged();
      modificationSince = null;
    }
    getParentPage().onSearchSubmit();
  }

  @SuppressWarnings("unchecked")
  public F getSearchFilter()
  {
    if (this.searchFilter != null) {
      return this.searchFilter;
    } else {
      if (getParentPage().isStoreFilter() == true) {
        final Object filter = getParentPage().getUserPrefEntry(this.getClass().getName() + ":Filter");
        if (filter != null) {
          if (filter.getClass().equals(newSearchFilterInstance().getClass()) == true) {
            try {
              this.searchFilter = (F) filter;
            } catch (final ClassCastException ex) {
              // No output needed, info message follows:
            }
            if (this.searchFilter == null) {
              // Probably a new software release results in an incompability of old and new filter format.
              getLogger().info(
                  "Could not restore filter from user prefs: (old) filter type "
                      + filter.getClass().getName()
                      + " is not assignable to (new) filter type "
                      + newSearchFilterInstance().getClass().getName()
                      + " (OK, probably new software release).");
            }
          }
        }
      }
    }
    if (this.searchFilter == null) {
      this.searchFilter = newSearchFilterInstance();
      this.searchFilter.reset();
      if (getParentPage().isStoreFilter() == true) {
        getParentPage().putUserPrefEntry(this.getClass().getName() + ":Filter", this.searchFilter, true);
      }
    }
    return this.searchFilter;
  }

  public CheckBoxButton createAutoRefreshCheckBoxButton(final String id, final IModel<Boolean> model, final String label)
  {
    return createAutoRefreshCheckBoxButton(id, model, label, null);
  }

  @SuppressWarnings("serial")
  protected CheckBoxButton createAutoRefreshCheckBoxButton(final String id, final IModel<Boolean> model, final String label,
      final String tooltip)
  {
    final CheckBoxButton checkBoxPanel = new CheckBoxButton(id, model, label) {
      @Override
      protected boolean wantOnSelectionChangedNotifications()
      {
        return true;
      };

      @Override
      protected void onSelectionChanged(final Boolean newSelection)
      {
        parentPage.refresh();
      };

    };
    if (tooltip != null) {
      checkBoxPanel.setTooltip(tooltip);
    }
    return checkBoxPanel;
  }

  /**
   * The page size of display tag (result table).
   */
  public Integer getPageSize()
  {
    if (pageSize == null) {
      pageSize = (Integer) getParentPage().getUserPrefEntry(this.getClass().getName() + ":pageSize");
    }
    if (pageSize == null) {
      pageSize = 50;
    }
    return pageSize;
  }

  /**
   * For convenience combo box with quick select of often used time periods.
   */
  public String getModificationSince()
  {
    return modificationSince;
  }

  public void setModificationSince(final String modificationSince)
  {
    this.modificationSince = modificationSince;
  }

  public void setPageSize(final Integer pageSize)
  {
    this.pageSize = pageSize;
    if (getParentPage().isStoreFilter() == true) {
      getParentPage().putUserPrefEntry(this.getClass().getName() + ":pageSize", this.pageSize, true);
    }
  }

  /**
   * For displaying the modified search string for lucene, e. g. "modified searchstring: micromata*"
   * @param component Needed for {@link Component#getString(String)}.
   * @param searchString
   * @return
   */
  public static String getModifiedSearchExpressionLabel(final Component component, final String searchString)
  {
    return component.getString("search.lucene.expression") + " " + StringEscapeUtils.escapeHtml(BaseDao.modifySearchString(searchString));
  }

  /**
   * Any given de-serialized filter will be set from parent page.
   * @param filter
   */
  @SuppressWarnings("unchecked")
  void setFilter(final Object filter)
  {
    searchFilter = (F) filter;
  }

  void copySearchFieldsFrom(final BaseSearchFilter baseFilter)
  {
    searchFilter.copyBaseSearchFieldsFrom(baseFilter);
  }

  /**
   * Used by search cell to define visibility.
   * @return True if not overload.
   */
  protected boolean isFilterVisible()
  {
    return true;
  }

  /**
   * Used by search cell to define visibility of search input string and extended search filter.
   * @return True if not overload.
   */
  protected boolean isSearchFilterVisible()
  {
    return true;
  }

  /**
   * If the derived class returns a text, the keyboard image right to the search field will be shown with the returned string as tool-tip. <br/>
   * If the derived class uses the store-recent-search-terms-functionality then a generic tool-tip about this functionality is used.<br/>
   * Otherwise the image is invisible (default).
   */
  protected String getHelpKeyboardImageTooltip()
  {
    if (parentPage.isRecentSearchTermsStorage() == true) {
      return getString("tooltip.autocomplete.recentSearchTerms");
    } else {
      return null;
    }
  }

  /** This class uses the logger of the extended class. */
  protected abstract Logger getLogger();

  protected boolean showOptionsPanel()
  {
    return parentPage.getBaseDao().isHistorizable();
  }

  protected boolean showHistorySearchAndDeleteCheckbox()
  {
    return parentPage.getBaseDao().isHistorizable();
  }
}