content: package ch.uzh.ifi.attempto.acewiki.gui;
  
  import nextapp.echo.app.Color;
  import nextapp.echo.app.Column;
  import nextapp.echo.app.Component;
  import nextapp.echo.app.Extent;
  import nextapp.echo.app.Font;
  import nextapp.echo.app.Grid;
  import nextapp.echo.app.Insets;
  import nextapp.echo.app.Row;
  import nextapp.echo.app.event.ActionEvent;
  import ch.uzh.ifi.attempto.acewiki.Wiki;
  import ch.uzh.ifi.attempto.acewiki.core.Comment;
  import ch.uzh.ifi.attempto.acewiki.core.ModuleElement;
  import ch.uzh.ifi.attempto.acewiki.core.ModuleElement.InvalidSyntaxException;
  import ch.uzh.ifi.attempto.acewiki.core.OntologyElement;
  import ch.uzh.ifi.attempto.acewiki.gf.GfEngine;
  import ch.uzh.ifi.attempto.acewiki.gf.TypeGfModule;
  import ch.uzh.ifi.attempto.echocomp.GeneralButton;
  import ch.uzh.ifi.attempto.echocomp.HSpace;
  import ch.uzh.ifi.attempto.echocomp.MessageWindow;
  import ch.uzh.ifi.attempto.echocomp.SimpleErrorMessageWindow;
  import ch.uzh.ifi.attempto.echocomp.SolidLabel;
  import ch.uzh.ifi.attempto.echocomp.Style;
  import ch.uzh.ifi.attempto.echocomp.VSpace;
  import ch.uzh.ifi.attempto.gfservice.GfModule;
  import ch.uzh.ifi.attempto.gfservice.GfServiceException;
  
  import com.google.common.base.Splitter;
  
  /**
   * TODO: localize strings
   *
   * @author Kaarel Kaljurand
   * @author Tobias Kuhn
   */
  public class ModulePage extends ArticlePage {
  
  	private static final long serialVersionUID = -5592272938081004472L;
  
  	private static final String ACTION_EDIT = "Edit module";
  	private static final String ACTION_CHECK = "Check module";
  
  	public final static Splitter SPLITTER_NL = Splitter.on('\n');
  
  	private final ModuleElement mElement;
  	private final Wiki mWiki;
  
  	private Row mFormattedModuleContent;
  
  	public ModulePage(ModuleElement element, Wiki wiki) {
  		super(wiki, element);
  		mElement = element;
  		mWiki = wiki;
  		getTitle().setColor(Style.specialForeground);
  	}
  
  	public OntologyElement getOntologyElement() {
  		return mElement;
  	}
  
  
  	public void actionPerformed(ActionEvent e) {
  		super.actionPerformed(e);
  		if (ACTION_EDIT.equals(e.getActionCommand())) {
  			if (!getWiki().isEditable()) {
  				getWiki().showLoginWindow();
  			} else {
  				EditorDialog.Builder editor = new EditorDialog.Builder(mElement.getModuleContent(), this)
  				.setTitle("Module Editor")
  				.setSize(600, 600)
  				.setFont(new Font(Font.MONOSPACE, Font.PLAIN, new Extent(12)))
  				.setPositiveButton(new Executable() {
  					@Override
  					public void execute(Object... args) {
  						boolean success = parse();
  						// If there were no syntax errors then push the grammar to the server.
  						if (success) {
  							GfEngine engine = (GfEngine) mWiki.getEngine();
  							try {
  								engine.getGfGrammar().upload(
  										new GfModule(mElement.getWord(),
  												mElement.getModuleContent().getText()));
  							} catch (GfServiceException e) {
  								mWiki.showWindow(new MessageWindow("Error", "Push to server failed."));
  								e.printStackTrace();
  							}
  						}
  					}
  				});
  				mWiki.showWindow(editor.create());
  			}
  		} else if (ACTION_CHECK.equals(e.getActionCommand())) {
  			if (!getWiki().isEditable()) {
  				getWiki().showLoginWindow();
  			} else {
  				boolean success = parse();
  				if (success) {
  					mWiki.showWindow(new MessageWindow("Success", "There are no syntax errors."));
  				}
  			}
  		}
  	}
  
  
  	protected void doUpdate() {
  		setTabRow(TabRow.getEmptyTabRow());
  
  		Column textColumn = getTextColumn();
  		textColumn.removeAll();
  
  		Grid referencesGrid = new Grid(5);
  		referencesGrid.setInsets(new Insets(0, 2, 8, 2));
  		for (ModuleElement oe : mWiki.getOntology().getOntologyElements(ModuleElement.class)) {
  			if (oe != mElement && oe.references(mElement)) {
  				referencesGrid.add(new WikiLink(oe, oe.getWord(), mWiki, false));
  			}
  		}
  
  		Row buttonRow = new Row();
  		buttonRow.setCellSpacing(new Extent(10));
  		buttonRow.add(new GeneralButton(ACTION_EDIT, this));
  		buttonRow.add(new GeneralButton(ACTION_CHECK, this));
  
  
  		textColumn.add(referencesGrid);
  		textColumn.add(new VSpace(20));
  		textColumn.add(buttonRow);
  		textColumn.add(new VSpace(20));
  
  		Comment grammarContent = mElement.getModuleContent();
  		if (grammarContent == null || grammarContent.getText().isEmpty()) {
  			mElement.replaceModuleContent(mElement.getDefaultContent());
  			grammarContent = mElement.getModuleContent();
  		}
  		mFormattedModuleContent = getModuleColumn(grammarContent.getText());
  		textColumn.add(mFormattedModuleContent);
  
  		getTitle().setText(mElement.getWord());
  	}
  
  
  	private boolean parse() {
  		// TODO: this blocks, do it in the background
  		try {
  			mElement.parse();
  		} catch (InvalidSyntaxException iex) {
  			Integer l = iex.getLine();
  			Integer c = iex.getColumn();
  			mWiki.showWindow(new SimpleErrorMessageWindow(
  					"Error at line/column = " + (l == null ? "?" : l) + "/" + (c == null ? "?" : c),
  					iex.getText()));
  			if (l != null) highlightSyntaxError(l);
  			return false;
  		} catch (Exception ex) {
  			mWiki.showWindow(new SimpleErrorMessageWindow("Error", ex.getMessage()));
  			return false;
  		}
  		return true;
  	}
  
  
  	private void highlightSyntaxError(int nth1) {
  		if (mFormattedModuleContent != null &&
  				mFormattedModuleContent.getComponent(0) != null &&
  				nth1 <= mFormattedModuleContent.getComponent(0).getComponentCount()) {
  			mFormattedModuleContent.getComponent(0).getComponent(nth1 - 1).setBackground(Color.PINK);
  		}
  	}
  
  
  	private Row getModuleColumn(String text) {
  		Column colNumbers = new Column();
  		Column colLines = new Column();
  		int lineNumber = 0;
  		for (String s : SPLITTER_NL.split(text)) {
  			lineNumber++;
  			Row rowNumber = new Row();
  			rowNumber.add(makeLineNumber(lineNumber));
  			colNumbers.add(rowNumber);
  
  			Row rowLine = new Row();
  			rowLine.add(markupLine(s));
  			colLines.add(rowLine);
  		}
  		Row row = new Row();
  		row.add(colNumbers);
  		row.add(new HSpace(20));
  		row.add(colLines);
  		return row;
  	}
  
  
  	private Component markupLine(String text) {
  		StringBuilder sb = new StringBuilder();
  		Row row = new Row();
  		for (String s : TypeGfModule.tokenizeText(text)) {
  			// Check if the element is in the ontology
  			OntologyElement oe = mWiki.getOntology().getElement(s); // TODO: reuse the references set from the statement
  			if (oe == null) {
  				sb.append(s);
  			} else {
  				if (sb.length() > 0) {
  					row.add(makePlainText(sb.toString()));
  					sb.setLength(0);
  				}
  				row.add(new HSpace());
  				row.add(new WikiLink(oe, s, mWiki, false));
  				row.add(new HSpace());
  			}
  		}
  		row.add(makePlainText(sb.toString()));
  		return row;
  	}
  
  
  	/**
  	 * Turns the given string into a label.
  	 * Empty strings also get a representation, otherwise they would not create a row.
  	 */
  	private static Component makePlainText(String s) {
  		if (s.isEmpty()) {
  			s = "\u00A0";
  		}
  		SolidLabel label = new SolidLabel(s, Font.PLAIN);
  		label.setForeground(Style.darkForeground);
  		label.setFont(new Font(Font.MONOSPACE, Font.PLAIN, new Extent(12)));
  		label.setFormatWhitespace(true); // TODO: maybe this should be part of any SolidLabel
  		return label;
  	}
  
  
  	private static SolidLabel makeLineNumber(int lineNumber) {
  		SolidLabel label = new SolidLabel(String.format("%4d", lineNumber), Font.PLAIN);
  		label.setForeground(new Color(120, 120, 120));
  		label.setFont(new Font(Font.MONOSPACE, Font.PLAIN, new Extent(12)));
  		label.setFormatWhitespace(true); // TODO: maybe this should be part of any SolidLabel
  		return label;
  	}
  
  }