/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* 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.xwiki.gwt.wysiwyg.client.plugin.table.feature;
import java.util.EnumSet;
import org.xwiki.gwt.dom.client.Document;
import org.xwiki.gwt.dom.client.Element;
import org.xwiki.gwt.dom.client.Range;
import org.xwiki.gwt.dom.client.Selection;
import org.xwiki.gwt.user.client.StringUtils;
import org.xwiki.gwt.user.client.ui.rta.cmd.Command;
import org.xwiki.gwt.user.client.ui.rta.cmd.internal.InsertBlockHTMLExecutable;
import org.xwiki.gwt.user.client.ui.wizard.Wizard;
import org.xwiki.gwt.user.client.ui.wizard.WizardListener;
import org.xwiki.gwt.user.client.ui.wizard.WizardStepMap;
import org.xwiki.gwt.user.client.ui.wizard.NavigationListener.NavigationDirection;
import org.xwiki.gwt.wysiwyg.client.Images;
import org.xwiki.gwt.wysiwyg.client.Strings;
import org.xwiki.gwt.wysiwyg.client.plugin.table.TableDescriptor;
import org.xwiki.gwt.wysiwyg.client.plugin.table.TablePlugin;
import org.xwiki.gwt.wysiwyg.client.plugin.table.ui.TableConfigWizardStep;
import org.xwiki.gwt.wysiwyg.client.plugin.table.util.TableConfig;
import org.xwiki.gwt.wysiwyg.client.plugin.table.util.TableUtils;
import com.google.gwt.user.client.ui.Image;
/**
* Feature allowing to insert a table in the editor. It is disabled when the caret is positioned in a table.
*
* @version $Id: 865848f68d816386bf40d08f33d909d3ddac702b $
*/
public class InsertTable extends AbstractTableFeature implements WizardListener
{
/**
* Feature name.
*/
public static final String NAME = "inserttable";
/**
* The name of the wizard step that configures a table before inserting it.
*/
private static final String CONFIG_STEP_NAME = "config";
/**
* Insert table wizard.
*/
private Wizard wizard;
/**
* The object used to insert the table.
* <p>
* NOTE: This class should extend {@link InsertBlockHTMLExecutable} instead of aggregating it.
*/
private final InsertBlockHTMLExecutable insertBlockHTMLExecutable;
/**
* Initialize the feature. Table features needs to be aware of the plug-in (here the ClickListener) since they hold
* their own PushButton.
*
* @param plugin table plug-in.
*/
public InsertTable(TablePlugin plugin)
{
super(NAME, new Command(NAME), Strings.INSTANCE.insertTable(), plugin);
insertBlockHTMLExecutable = new InsertBlockHTMLExecutable(rta);
}
/**
* Get table wizard pop-up.
*
* @return the table wizard pop-up instance.
*/
public Wizard getWizard()
{
if (wizard == null) {
TableConfigWizardStep configStep = new TableConfigWizardStep();
configStep.setDirectionName(NavigationDirection.FINISH, Strings.INSTANCE.tableInsertButton());
configStep.setValidDirections(EnumSet.of(NavigationDirection.FINISH));
WizardStepMap insertSteps = new WizardStepMap();
insertSteps.put(CONFIG_STEP_NAME, configStep);
wizard = new Wizard(Strings.INSTANCE.tableInsertDialogCaption(), new Image(Images.INSTANCE.insertTable()));
wizard.setProvider(insertSteps);
wizard.addWizardListener(this);
}
return wizard;
}
/**
* Create a table from TableConfig configuration.
* <p>
* We create the table using innerHTML instead of creating each DOM node in order to improve the speed. In most of
* the browsers setting the innerHTML is faster than creating the DOM nodes and appending them.
*
* @param doc currently edited document.
* @param config table configuration (row number, etc).
* @return the newly created table.
*/
public Element createTable(Document doc, TableConfig config)
{
StringBuffer table = new StringBuffer("<table>");
StringBuffer row = new StringBuffer("<tr>");
for (int i = 0; i < config.getColNumber(); i++) {
row.append("<td>");
// The default cell content depends on the browser. In Firefox the best option is to use a BR. Firefox
// itself uses BRs in order to allow the user to place the caret inside empty block elements. In Internet
// Explorer the best option is to set the inner HTML of each cell to the empty string, after creation. For
// now lets keep the non-breaking space. At some point we should have browser specific implementations for
// FF and IE. Each will overwrite this method and add specific initialization.
row.append(TableUtils.CELL_DEFAULTHTML);
row.append("</td>");
}
row.append("</tr>");
if (config.hasHeader()) {
table.append("<thead>");
if (config.getRowNumber() > 0) {
table.append(row.toString().replace("td", "th"));
}
table.append("</thead>");
}
table.append("<tbody>");
for (int i = config.hasHeader() ? 1 : 0; i < config.getRowNumber(); i++) {
table.append(row.toString());
}
table.append("</tbody></table>");
Element container = doc.createDivElement().cast();
container.setInnerHTML(table.toString());
Element tableElement = (Element) container.getFirstChild();
container.removeChild(tableElement);
return tableElement;
}
/**
* Insert a HTML table in the editor.
*
* @param config creation parameters.
*/
public void insertTable(TableConfig config)
{
Element table = createTable(rta.getDocument(), config);
insertBlockHTMLExecutable.execute(table);
// Place the caret at the beginning of the first cell.
Range range = rta.getDocument().createRange();
range.selectNodeContents(domUtils.getFirstDescendant(table, config.hasHeader() ? TableUtils.COL_HNODENAME
: TableUtils.COL_NODENAME));
range.collapse(true);
Selection selection = rta.getDocument().getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
@Override
public boolean execute(String parameter)
{
if (StringUtils.isEmpty(parameter)) {
// The command has been executed without insertion configuration, start the insert table wizard.
getWizard().start(CONFIG_STEP_NAME, null);
} else {
// Insert the table element.
insertTable((TableConfig) TableConfig.fromJson(parameter));
}
return true;
}
@Override
public boolean isEnabled()
{
return super.isEnabled()
&& TableUtils.getInstance().getTable(TableUtils.getInstance().getCaretNode(rta.getDocument())) == null;
}
@Override
public void onCancel(Wizard sender)
{
if (sender == getWizard()) {
getPlugin().getTextArea().setFocus(true);
}
}
@Override
public void onFinish(Wizard sender, Object result)
{
if (sender == getWizard()) {
getPlugin().getTextArea().setFocus(true);
TableDescriptor descriptor = (TableDescriptor) result;
// Call the command again, passing the insertion configuration as a JSON object.
getPlugin().getTextArea().getCommandManager().execute(
getCommand(),
"{ rows:" + descriptor.getRowCount() + ", cols: " + descriptor.getColumnCount() + ", header: "
+ descriptor.isWithHeader() + " }");
}
}
}