/*
* Copyright 2014 Corpuslinguistic working group Humboldt University Berlin.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package annis.gui.components;
import com.vaadin.annotations.JavaScript;
import com.vaadin.server.Resource;
import com.vaadin.ui.AbstractJavaScriptComponent;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.JavaScriptFunction;
import com.vaadin.ui.MenuBar;
import com.vaadin.ui.MenuBar.MenuItem;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.ValoTheme;
import elemental.json.JsonArray;
import elemental.json.JsonType;
import elemental.json.JsonValue;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Embedds a single HTML page and adds navigation to it's headers (if they have
* an id).
*
* This is e.g. usefull for documentation.
*
* @author Thomas Krause <krauseto@hu-berlin.de>
*/
public class NavigateableSinglePage extends VerticalLayout
{
private final static Logger log = LoggerFactory.getLogger(
NavigateableSinglePage.class);
private final IFrameComponent iframe = new IFrameComponent();
private MenuBar navigation;
private final HorizontalLayout toolLayout;
private final Button btPrint;
private final Map<String, MenuItem> menuItemRegistry = new HashMap<>();
private String lastSelectedID;
private final static Pattern regexHeader = Pattern.compile("h([1-6])");
public NavigateableSinglePage(File localFile, URI externalURI)
{
iframe.setSizeFull();
setSpacing(true);
btPrint = new Button("Print");
btPrint.setStyleName(ValoTheme.BUTTON_LINK);
btPrint.setEnabled(false);
btPrint.addClickListener(new Button.ClickListener()
{
@Override
public void buttonClick(Button.ClickEvent event)
{
iframe.printFrame();
}
});
this.toolLayout = new HorizontalLayout(btPrint);
this.toolLayout.setComponentAlignment(btPrint, Alignment.MIDDLE_CENTER);
addComponent(toolLayout);
addComponent(iframe);
setExpandRatio(iframe, 1.0f);
setSource(localFile, externalURI);
}
private void onScroll(String headerID)
{
selectChapterInNavigation(headerID);
}
private void setSource(File localFile, URI externalURI)
{
iframe.getState().setSource(externalURI.toASCIIString());
if (navigation != null)
{
toolLayout.removeComponent(navigation);
}
menuItemRegistry.clear();
navigation = createMenubarFromHTML(localFile, externalURI, menuItemRegistry);
toolLayout.addComponent(navigation, 0);
btPrint.setEnabled(true);
}
private MenuBar createMenubarFromHTML(File localFile, URI externalURI,
Map<String, MenuItem> idToMenuItem)
{
MenuBar mbNavigation = new MenuBar();
mbNavigation.setStyleName("huge");
MenuItem navRoot = mbNavigation.addItem("Choose topic", null);
navRoot.setStyleName("huge");
try(FileInputStream input = new FileInputStream(localFile))
{
Document doc = Jsoup.parse(input, "UTF-8", externalURI.toASCIIString());
ArrayList<MenuItem> itemPath = new ArrayList<>();
// find all headers that have an ID
for (Element e : doc.getElementsByAttribute("id"))
{
Matcher m = regexHeader.matcher(e.tagName());
if (m.matches())
{
int level = Integer.parseInt(m.group(1)) - 1;
// decide wether to expand the path (one level deeper) or to truncate
if (level == 0)
{
itemPath.clear();
}
else if (itemPath.size() >= level)
{
// truncate
itemPath = new ArrayList<>(itemPath.subList(0, level));
}
if (itemPath.isEmpty() && level > 0)
{
// fill the path with empty elements
for (int i = 0; i < level; i++)
{
itemPath.add(createItem(navRoot, itemPath, "<empty>", null));
}
}
MenuItem item = createItem(navRoot, itemPath, e.text(), e.id());
itemPath.add(item);
idToMenuItem.put(e.id(), item);
}
}
}
catch (IOException ex)
{
log.error("Could not parse iframe source", ex);
}
return mbNavigation;
}
private MenuItem createItem(MenuItem rootItem, List<MenuItem> path,
String caption, String id)
{
MenuItem parent;
if (path.isEmpty())
{
parent = rootItem;
}
else
{
parent = path.get(path.size() - 1);
}
MenuItem child = parent.addItem(caption, new IDSelectionCommand(id));
child.setStyleName("huge");
return child;
}
private void selectChapterInNavigation(String idToSelect)
{
if (navigation != null && menuItemRegistry != null && idToSelect != null)
{
MenuItem navRoot = navigation.getItems().get(0);
MenuItem toSelect = menuItemRegistry.get(idToSelect);
if (toSelect != null)
{
navRoot.setText(toSelect.getText());
toSelect.setStyleName("huge-selected");
if(lastSelectedID != null)
{
MenuItem lastSelectedItem = menuItemRegistry.get(lastSelectedID);
if (lastSelectedItem != null && lastSelectedItem != toSelect)
{
lastSelectedItem.setStyleName("huge");
}
}
lastSelectedID = idToSelect;
}
}
}
private class IDSelectionCommand implements MenuBar.Command
{
private final String id;
public IDSelectionCommand(String id)
{
this.id = id;
}
@Override
public void menuSelected(MenuItem selectedItem)
{
if (id != null)
{
iframe.scrollToElement(id);
selectChapterInNavigation(id);
}
}
}
@JavaScript(
{
"vaadin://jquery.js", "navigateablesinglepage.js"
})
private class IFrameComponent extends AbstractJavaScriptComponent
{
public IFrameComponent()
{
addFunction("scrolled", new JavaScriptFunction()
{
@Override
public void call(JsonArray arguments) throws JSONException
{
JsonValue headerID = arguments.get(0);
if(headerID.getType() == JsonType.STRING)
{
onScroll(headerID.asString());
}
getState().setLastScrollPos((int) arguments.getNumber(1));
}
});
}
public void scrollToElement(String id)
{
if(id != null)
{
callFunction("scrollToElement", id);
}
}
public void setSource(Resource res)
{
setResource("content", res);
}
public void printFrame()
{
callFunction("printFrame");
}
@Override
public final IframeState getState()
{
return (IframeState) super.getState();
}
}
}