/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// 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.flowlayout;
import java.util.LinkedList;
import name.fraser.neil.plaintext.DiffMatchPatch;
import name.fraser.neil.plaintext.DiffMatchPatch.Diff;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.projectforge.web.HtmlHelper;
import org.projectforge.web.dialog.ModalDialog;
import org.projectforge.web.wicket.AbstractSecuredPage;
import org.projectforge.web.wicket.WicketUtils;
/**
* Represents a text panel showing the difference between two texts. The old and new version of the text is shown in a pop-over.
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
public class DiffTextPanel extends Panel
{
private static final long serialVersionUID = -4688885351297467119L;
public static final String WICKET_ID = "text";
private final Label label;
private String prettyHtml;
private ModalDialog modalDialog;
private boolean showModalDialog = true;
private IModel<String> newText, oldText;
@SuppressWarnings("serial")
public DiffTextPanel(final String id, final IModel<String> newText, final IModel<String> oldText, final Behavior... behaviors)
{
super(id);
this.newText = newText;
this.oldText = oldText;
label = new Label(WICKET_ID, new Model<String>() {
@Override
public String getObject()
{
if (prettyHtml == null) {
final DiffMatchPatch diffMatchPatch = new DiffMatchPatch();
String newValue = newText.getObject();
if (newValue == null || "null".equals(newValue) == true) {
newValue = getString("label.null");
}
String oldValue = oldText.getObject();
if (oldValue == null || "null".equals(oldValue) == true) {
oldValue = getString("label.null");
}
final LinkedList<Diff> diffs = diffMatchPatch.diff_main(oldValue, newValue);
diffMatchPatch.diff_cleanupSemantic(diffs);
prettyHtml = getPrettyHtml(diffs);
}
return prettyHtml;
}
});
label.add(behaviors).setEscapeModelStrings(false);
add(label);
}
/**
* If set to true, then the user is able to click on this panel and a modal dialog will open with the old and new text to copy.
* @param showModalDialog the showModalDialog to set (default is true).
* @return this for chaining.
*/
public DiffTextPanel setShowModalDialog(final boolean showModalDialog)
{
this.showModalDialog = showModalDialog;
return this;
}
/**
* If the models of old and/or new text were changed you may call this method to force a new running of the diff algorithm.
* @return this for chaining.
*/
public DiffTextPanel recalculate()
{
prettyHtml = null;
return this;
}
/**
* Calls setRenderBodyOnly(false) and setOutputMarkupId(true) for the enclosed label.
* @return the label
*/
public Label getLabel4Ajax()
{
label.setRenderBodyOnly(false).setOutputMarkupId(true);
return label;
}
/**
* @return the label
*/
public Label getLabel()
{
return label;
}
/**
* @see org.apache.wicket.Component#setMarkupId(java.lang.String)
*/
@Override
public DiffTextPanel setMarkupId(final String markupId)
{
label.setOutputMarkupId(true);
label.setMarkupId(markupId);
return this;
}
/**
* @see WicketUtils#setStrong(org.apache.wicket.markup.html.form.FormComponent)
* @return this for chaining.
*/
public DiffTextPanel setStrong()
{
WicketUtils.setStrong(label);
return this;
}
/**
* @see org.apache.wicket.Component#onInitialize()
*/
@SuppressWarnings("serial")
@Override
protected void onInitialize()
{
super.onInitialize();
if (showModalDialog == true && getPage() != null && getPage() instanceof AbstractSecuredPage) {
final AbstractSecuredPage parentPage = (AbstractSecuredPage) getPage();
modalDialog = new ModalDialog(parentPage.newModalDialogId()) {
@Override
public void init()
{
setTitle(getString("changes"));
init(new Form<String>(getFormId()));
{
final FieldsetPanel fs = gridBuilder.newFieldset(getString("history.oldValue")).setLabelSide(false);
final TextArea<String> textArea = new TextArea<String>(fs.getTextAreaId(), oldText);
fs.add(textArea).setAutogrow(1, 10);
textArea.add(AttributeModifier.replace("onClick", "$(this).select();"));
}
{
final FieldsetPanel fs = gridBuilder.newFieldset(getString("history.newValue")).setLabelSide(false);
final TextArea<String> textArea = new TextArea<String>(fs.getTextAreaId(), newText);
fs.add(textArea).setAutogrow(1, 10);
textArea.add(AttributeModifier.replace("onClick", "$(this).select();"));
}
}
};
modalDialog.setBigWindow();
modalDialog.setLazyBinding();
parentPage.add(modalDialog);
final AjaxEventBehavior behavior = new AjaxEventBehavior("onClick") {
@Override
protected void onEvent(final AjaxRequestTarget target)
{
if (modalDialog.isBound() == false) {
// First call, have to initialize it.
modalDialog.init();
target.add(modalDialog.getMainContainer());
modalDialog.bind(target);
}
modalDialog.open(target);
}
};
label.add(behavior);
label.add(AttributeModifier.append("style", "cursor: pointer;"));
}
}
protected String getPrettyHtml(final LinkedList<Diff> diffs)
{
final StringBuilder html = new StringBuilder();
for (final Diff aDiff : diffs) {
final String text = HtmlHelper.escapeHtml(aDiff.text, true);
switch (aDiff.operation) {
case INSERT:
html.append("<ins>").append(text).append("</ins>");
break;
case DELETE:
html.append("<del>").append(text).append("</del>");
break;
case EQUAL:
html.append("<span>").append(text).append("</span>");
break;
}
}
return html.toString();
}
}