Java tutorial
/** * @author Nigel Cook * * (C) Copyright 2010-2012. Nigel Cook. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Licensed under the terms described in LICENSE file that accompanied this code, (the "License"); you may not use this file * except in compliance with the License. * * 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 n3phele.client.widgets; import java.util.ArrayList; import java.util.List; import com.google.gwt.cell.client.AbstractSafeHtmlCell; import com.google.gwt.cell.client.Cell; import com.google.gwt.cell.client.FieldUpdater; import com.google.gwt.cell.client.IconCellDecorator; import com.google.gwt.cell.client.ValueUpdater; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.event.logical.shared.OpenEvent; import com.google.gwt.event.logical.shared.OpenHandler; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.google.gwt.text.shared.SimpleSafeHtmlRenderer; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.CellWidget; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy; import com.google.gwt.user.client.ui.AbstractImagePrototype; import com.google.gwt.user.client.ui.DisclosurePanel; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.VerticalPanel; import n3phele.client.N3phele; import n3phele.client.model.FileNode; import n3phele.client.model.Repository; import n3phele.client.presenter.helpers.StyledTextCellRenderer; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Label; public class FileNodeBrowserCore extends VerticalPanel { final private HorizontalPanel header; final private FlexTable actions; private FlowPanel crumbs; private CellTable<List<FileNode>> grid; private Button cancelButton; private Button openButton; private TextBox filename; private ListBox repoList; private List<FileNode> crumbElements = new ArrayList<FileNode>(); private BrowserPresenter presenter = null; final int ROWLENGTH; private Image errorIndicator; private List<Repository> repos = null; private final boolean isInput; private boolean isOptional; private DisclosurePanel newFolderPanel; private NewFolderView newFolderView; private boolean isZip; public FileNodeBrowserCore(BrowserPresenter presenter, boolean isInput, int rowLength, int width, MenuItem heading) { this.setWidth(width + "px"); this.ROWLENGTH = rowLength; this.isInput = isInput; this.isOptional = !isInput; this.presenter = presenter; header = new HorizontalPanel(); header.setWidth("100%"); if (heading != null) { Cell<MenuItem> cell = new IconTextCell<MenuItem>(32, 32); CellWidget<MenuItem> headerIcon = new CellWidget<MenuItem>(cell, heading); headerIcon.addStyleName(N3phele.n3pheleResource.css().workspacePanelHeader()); header.add(headerIcon); } if (!isInput) { newFolderPanel = new DisclosurePanel("New Folder"); newFolderView = NewFolderView.newInstance(); newFolderPanel.add(newFolderView); newFolderPanel.addOpenHandler(new OpenHandler<DisclosurePanel>() { @Override public void onOpen(OpenEvent<DisclosurePanel> event) { FileNodeBrowserCore.this.newFolderView.clearName(); } }); newFolderPanel.addCloseHandler(new CloseHandler<DisclosurePanel>() { @Override public void onClose(CloseEvent<DisclosurePanel> event) { String name = FileNodeBrowserCore.this.newFolderView.getFolderName(); if (name != null) { FileNodeBrowserCore.this.newFolder(name); } } }); newFolderView.setDisclosurePanel(newFolderPanel); header.add(newFolderPanel); } this.add(header); actions = new FlexTable(); this.add(actions); actions.setWidth("100%"); crumbs = new FlowPanel(); crumbs.setWidth("100%"); actions.setWidget(0, 0, crumbs); ScrollPanel gridPanel = new ScrollPanel(); gridPanel.setHeight("200px"); grid = new CellTable<List<FileNode>>(); grid.setWidth("100%"); grid.setTableLayoutFixed(true); for (int i = 0; i < ROWLENGTH; i++) { Column<List<FileNode>, FileNode> c = new Column<List<FileNode>, FileNode>( new FileNodeIconTextCell(N3phele.n3pheleResource.folderIcon(), new FileNodeTextCell(), i)) { @Override public FileNode getValue(List<FileNode> object) { int index = ((FileNodeIconTextCell) this.getCell()).getIndex(); if (index < object.size()) { return object.get(index); } else { return null; } } }; c.setFieldUpdater(new FieldUpdater<List<FileNode>, FileNode>() { @Override public void update(int index, List<FileNode> object, FileNode value) { if (value != null) { if (!value.getMime().endsWith("Folder") && !value.getMime().endsWith("Placeholder")) { FileNodeBrowserCore.this.filename.setText(value.getName()); FileNodeBrowserCore.this.openButtonValidate(!isZip); } else { FileNodeBrowserCore.this.filename.setText(null); if (FileNodeBrowserCore.this.presenter != null) { FileNodeBrowserCore.this.openButtonValidate(isZip); FileNodeBrowserCore.this.presenter.selectFolder(value); } } } GWT.log("got " + index + " " + value.toFormattedString()); } }); grid.addColumn(c); grid.setColumnWidth(c, 100.0 / ROWLENGTH, Unit.PCT); } grid.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED); gridPanel.add(grid); actions.setWidget(1, 0, gridPanel); repoList = new ListBox(false); repoList.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { FileNodeBrowserCore.this.filename.setText(null); FileNodeBrowserCore.this.openButtonValidate(false); if (FileNodeBrowserCore.this.presenter != null) { int selected = FileNodeBrowserCore.this.repoList.getSelectedIndex() + (FileNodeBrowserCore.this.isOptional ? -1 : 0); if (selected >= 0 && selected < FileNodeBrowserCore.this.repos.size()) { FileNodeBrowserCore.this.presenter .selectFolder(FileNodeBrowserCore.this.repos.get(selected)); } else { FileNodeBrowserCore.this.presenter.selectFolder((Repository) null); } } } }); actions.setWidget(2, 0, repoList); filename = new TextBox(); filename.addKeyUpHandler(new KeyUpHandler() { @Override public void onKeyUp(KeyUpEvent event) { FileNodeBrowserCore.this.enableOpenButton(false); String filename = FileNodeBrowserCore.this.filename.getText(); String repoURI = FileNodeBrowserCore.this.repoList .getValue(FileNodeBrowserCore.this.repoList.getSelectedIndex()); GWT.log("save " + filename + " on " + repoURI); if (repoURI.length() == 0) repoURI = null; if (FileNodeBrowserCore.this.presenter != null) { validateAndEnableOpenButton(filename, repoURI); } } }); actions.setWidget(2, 1, filename); filename.setWidth((width - 50) + "px"); this.openButton = getOpenButton(this.isInput); if (this.openButton != null) actions.setWidget(3, 2, this.openButton); this.cancelButton = getCancelButton(); if (this.cancelButton != null) actions.setWidget(3, 3, cancelButton); errorIndicator = new Image(N3phele.n3pheleResource.inputErrorIcon()); setErrorText(); actions.setWidget(3, 1, errorIndicator); actions.getCellFormatter().setHorizontalAlignment(3, 1, HasHorizontalAlignment.ALIGN_RIGHT); actions.getFlexCellFormatter().setWidth(3, 3, "80px"); actions.getFlexCellFormatter().setWidth(3, 2, "80px"); actions.getFlexCellFormatter().setWidth(3, 1, (width - 100) + "px"); actions.getFlexCellFormatter().setColSpan(0, 0, 4); actions.getFlexCellFormatter().setColSpan(1, 0, 4); actions.getFlexCellFormatter().setColSpan(2, 1, 3); } private boolean isBlankOrNull(String s) { return s == null || s.trim().length() == 0; } protected Button getOpenButton(boolean isInput) { Button openButton; openButton = new Button(isInput ? "open" : "save"); openButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { if (FileNodeBrowserCore.this.presenter != null) { int index = FileNodeBrowserCore.this.repoList.getSelectedIndex(); String repoURI = FileNodeBrowserCore.this.repoList.getValue(index); if (isBlankOrNull(repoURI)) repoURI = null; String filename = getCanonicalFilename(FileNodeBrowserCore.this.filename.getText()); if (isBlankOrNull(filename)) filename = null; FileNodeBrowserCore.this.presenter.save(repoURI, filename); } } }); openButton.setWidth("80px"); openButton.setEnabled(false); return openButton; } protected Button getCancelButton() { Button cancelButton = new Button("cancel"); cancelButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { if (FileNodeBrowserCore.this.presenter != null) { FileNodeBrowserCore.this.presenter.hide(); } } }); cancelButton.setWidth("80px"); return cancelButton; } private String getCanonicalFilename(String filename) { String result = ""; if (this.crumbElements != null && this.crumbElements.size() > 1) { FileNode tail = this.crumbElements.get(this.crumbElements.size() - 1); String path = tail.getPath(); String name = tail.getName(); if (path == null) { result = name + "/"; } else { result = path + name + "/"; } } return result + filename; } /** * @param name */ protected void newFolder(String name) { GWT.log("new folder " + name); if (name.endsWith("/")) name = name.substring(0, name.length() - 1); String path = getCanonicalFilename(""); FileNode folder = FileNode.newInstance(name, path, "Placeholder", this.crumbElements.get(0).getRepository(), this.crumbElements.get(0).getRepositoryName()); if (this.presenter != null) { this.presenter.addPlaceholder(folder); } } private static class NewFolderView extends FlexTable { private static NewFolderView instance = null; public static NewFolderView newInstance() { if (instance == null) instance = new NewFolderView(); return instance; } public void clearName() { this.folderName.setText(null); name = null; } final private TextBox folderName; final private Button ok; final private Button cancel; private String name = null; private DisclosurePanel parent; protected NewFolderView() { super(); this.setWidget(0, 0, new Label("name")); this.setWidget(0, 1, folderName = new TextBox()); this.setWidget(1, 0, ok = new Button("add")); ok.setTitle("Adds folder name placeholder to browser view.\r\r" + "Actual folder will be created in the repository during command execution.\r" + "Unused placeholders are automatically delelted."); ok.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { NewFolderView.this.name = NewFolderView.this.folderName.getText(); if (NewFolderView.this.parent != null) NewFolderView.this.parent.setOpen(false); } }); this.setWidget(1, 1, cancel = new Button("cancel")); cancel.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { if (NewFolderView.this.parent != null) NewFolderView.this.parent.setOpen(false); } }); } public String getFolderName() { return this.name; } public void setDisclosurePanel(DisclosurePanel panel) { this.parent = panel; } } /** * @param b */ protected void openButtonValidate(boolean b) { if (b) { enableOpenButton(b && this.presenter != null); } else { if (this.presenter != null) { String repoURI = FileNodeBrowserCore.this.repoList .getValue(FileNodeBrowserCore.this.repoList.getSelectedIndex()); String filename = FileNodeBrowserCore.this.filename.getText(); GWT.log("Save " + filename + " from " + FileNodeBrowserCore.this.filename.getText() + " on " + repoURI); validateAndEnableOpenButton(filename, repoURI); } } } private void validateAndEnableOpenButton(String filename, String repoURI) { boolean enabled = false; if (this.isInput) { if (isOptional) { boolean nullFile = isBlankOrNull(filename); boolean nullRepo = isBlankOrNull(repoURI); if (nullFile && nullRepo) { enabled = true; } else if (!nullFile && !nullRepo) { enabled = (!isBlankOrNull(filename)) && FileNodeBrowserCore.this.presenter.validate(repoURI, getCanonicalFilename(filename)); } } else { enabled = (!isBlankOrNull(filename)) && FileNodeBrowserCore.this.presenter.validate(repoURI, getCanonicalFilename(filename)); } } else { boolean nullFilename = filename == null || filename.trim().length() == 0; if (FileNodeBrowserCore.this.isZip) { enabled = (nullFilename || filename.trim().endsWith(".zip")); } else { enabled = !nullFilename || this.repoList.getValue(this.repoList.getSelectedIndex()).length() == 0; } } enableOpenButton(enabled); } private void enableOpenButton(boolean enable) { if (enable) GWT.log("enableOpenButton " + enable); else GWT.log("enableOpenButton " + enable); if (openButton != null) openButton.setEnabled(enable); this.errorIndicator.setVisible(!enable); } public void enableRun(String text) { enableOpenButton(text.equals(getCanonicalFilename(this.filename.getText()))); } public void setPresenter(BrowserPresenter presenter) { this.presenter = presenter; enableOpenButton(false); } public void setRepos(List<Repository> repos, boolean isOptional) { this.repos = repos; this.repoList.clear(); this.isOptional = isOptional; if (this.isOptional) { this.repoList.addItem(isInput ? "unspecified" : "no output", ""); } for (Repository r : repos) { this.repoList.addItem(r.getName(), r.getUri()); } } public void show(List<FileNode> crumbs, List<FileNode> files) { if (crumbs.size() > 0) { String firstCrumb = crumbs.get(0).getRepository(); selectRepoByUri(firstCrumb); int delta = match(crumbElements, crumbs); GWT.log("delta = " + delta); if (trimCrumbs(delta)) delta = 0; for (int i = delta; i < crumbs.size(); i++) { Button button; if (i == 0) { button = new Button(getFilename(crumbs.get(i).getRepositoryName())); } else { this.crumbs.add(new Image(N3phele.n3pheleResource.rightArrowHead())); button = new Button(getFilename(crumbs.get(i).getName())); } button.setStyleName(N3phele.n3pheleResource.css().crumbsButton()); button.addClickHandler(new CrumbsClickHandler(crumbs.get(i), this)); this.crumbs.add(button); } } else { trimCrumbs(0); } crumbElements = crumbs; List<List<FileNode>> rowList = new ArrayList<List<FileNode>>((files.size() + ROWLENGTH - 1) / ROWLENGTH); for (int i = 0; i < files.size(); i = i + ROWLENGTH) rowList.add(files.subList(i, files.size())); grid.setRowData(rowList); openButtonValidate(false); } public static class CrumbsClickHandler implements ClickHandler { private final FileNode path; private final FileNodeBrowserCore scope; public CrumbsClickHandler(FileNode path, FileNodeBrowserCore scope) { this.path = path; this.scope = scope; } /* (non-Javadoc) * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) */ @Override public void onClick(ClickEvent event) { if (path.getName() != null) { boolean valid = scope.selectRepoByUri(path.getRepository()); GWT.log(path.getName() + " " + valid); scope.filename.setText(null); } else { boolean valid = scope.selectRepoByUri(path.getRepository()); GWT.log("Repo name " + valid); scope.filename.setText(null); } scope.openButtonValidate(false); if (scope.presenter != null) { scope.presenter.selectFolder(path); } GWT.log("Crumbs for " + path.toFormattedString()); } } // private boolean selectRepoByName(String name) { // boolean result = false; // if(name != null && this.repos != null) { // int index = this.isOptional? 1: 0; // for(Repository r : this.repos){ // if(r.getName().equals(name)) { // this.repoList.setItemSelected(index, true); // return true; // } // index++; // } // } // // return result; // } private boolean selectRepoByUri(String uri) { boolean result = false; if (uri != null && this.repos != null) { int index = this.isOptional ? 1 : 0; for (Repository r : this.repos) { if (uri.equals(r.getUri())) { this.repoList.setItemSelected(index, true); return true; } index++; } } return result; } // private boolean selectRepoByURI(String uri) { // boolean result = false; // if(uri != null && this.repos != null) { // int index = this.isInput? 0: 1; // for(Repository r : this.repos){ // if(r.getUri().equals(uri)) { // this.repoList.setItemSelected(index, true); // return true; // } // index++; // } // } // // return result; // } /** Provides a click handler for cell rendering FileNode * @author Nigel Cook * * (C) Copyright 2010. All rights reserved. * * */ public static class FileNodeTextCell extends AbstractSafeHtmlCell<FileNode> { /** * Constructs a TextCell that uses a {@link SimpleSafeHtmlRenderer} to render * its text. */ public FileNodeTextCell() { super(MySafeHtmlRenderer.getInstance(), "click", "keydown"); } /** * Constructs a TextCell that uses the provided {@link SafeHtmlRenderer} to * render its text. * * @param renderer a {@link SafeHtmlRenderer SafeHtmlRenderer<String>} instance */ public FileNodeTextCell(SafeHtmlRenderer<FileNode> renderer) { super(renderer, "click", "keydown"); } @Override public void onBrowserEvent(Context context, Element parent, FileNode value, NativeEvent event, ValueUpdater<FileNode> valueUpdater) { super.onBrowserEvent(context, parent, value, event, valueUpdater); if ("click".equals(event.getType())) { onEnterKeyDown(context, parent, value, event, valueUpdater); } } @Override protected void onEnterKeyDown(Context context, Element parent, FileNode value, NativeEvent event, ValueUpdater<FileNode> valueUpdater) { if (valueUpdater != null) { valueUpdater.update(value); } } @Override public void render(Context context, SafeHtml value, SafeHtmlBuilder sb) { if (value != null) { sb.append(value); } } } public static class MySafeHtmlRenderer extends StyledTextCellRenderer<FileNode> { private static MySafeHtmlRenderer instance; public static MySafeHtmlRenderer getInstance() { if (instance == null) { instance = new MySafeHtmlRenderer(N3phele.n3pheleResource.css().fileBrowserIconText()); } return instance; } public MySafeHtmlRenderer(String style) { super(style); } public String getValue(FileNode object) { if (object != null) return getFilename(object.getName()); else return null; } } private static class FileNodeIconTextCell extends IconCellDecorator<FileNode> { final private int index; /** * @param icon * @param cell */ public FileNodeIconTextCell(ImageResource icon, Cell<FileNode> cell, int index) { super(icon, cell); this.index = index; } public int getIndex() { return this.index; } /* (non-Javadoc) * @see com.google.gwt.cell.client.Cell#render(com.google.gwt.cell.client.Cell.Context, java.lang.Object, com.google.gwt.safehtml.shared.SafeHtmlBuilder) */ @Override protected SafeHtml getIconHtml(FileNode value) { SafeHtmlBuilder sb = new SafeHtmlBuilder(); ImageResource icon; if (value == null) { sb.appendHtmlConstant("<div></div>"); } else { if (value.getMime().endsWith("Folder")) { if (value.getMime().endsWith("PublicFolder")) { icon = N3phele.n3pheleResource.publicFolder(); } else { icon = N3phele.n3pheleResource.folderIcon(); } } else if (value.getMime().endsWith("Placeholder")) { icon = N3phele.n3pheleResource.folderAddedIcon(); } else { icon = N3phele.n3pheleResource.fileIcon(); } AbstractImagePrototype proto = AbstractImagePrototype.create(icon); sb.appendHtmlConstant("<div style=\"position:absolute;left:0px;top:0px;line-height:0px;\" title=\"" + SafeHtmlUtils.htmlEscape(getFilename(value.getName())) + "\">"); sb.appendHtmlConstant(proto.getHTML()); sb.appendHtmlConstant("</div>"); } return sb.toSafeHtml(); } } /** Generates the index at which a is dissimilar to b * @param a * @param b * @return */ public int match(List<FileNode> a, List<FileNode> b) { int len = Math.min(a.size(), b.size()); for (int i = 0; i < len; i++) { if (!(nullOrEquals(a.get(i).getRepositoryName(), b.get(i).getRepositoryName()) && nullOrEquals(a.get(i).getName(), b.get(i).getName()))) return i; } return len; } private boolean nullOrEquals(String a, String b) { if (a == null) { return b == null; } return a.equals(b); } public boolean trimCrumbs(int size) { GWT.log("crumbElements has " + crumbElements.size()); if (crumbElements != null && crumbElements.size() > size) { // if(size == 0) { crumbElements.clear(); crumbs.clear(); // } else { // for(int last = crumbElements.size()-1; last >= size; last--) { // crumbElements.remove(last); // GWT.log("remove "+(crumbs.getWidgetCount()-1)); // crumbs.remove(crumbs.getWidgetCount()-1); // GWT.log("remove "+(crumbs.getWidgetCount()-1)); // crumbs.remove(crumbs.getWidgetCount()-1); // } // } return true; } return false; } public static String getFilename(String pathName) { String result = pathName; if (pathName.endsWith("/")) result = pathName.substring(0, pathName.length() - 1); return result; } public interface BrowserPresenter { public void selectFolder(FileNode value); public void selectFolder(Repository repo); public void hide(); public void save(String repoURI, String filename); public boolean validate(String repoURI, String filename); void setView(FileNodeBrowser view); void addPlaceholder(FileNode folder); } /** * @param isZip * @param fileSpecification */ public void setFilename(String filename, boolean isZip, boolean isOptional) { this.isZip = isZip; this.isOptional = isOptional; this.filename.setText(filename); openButtonValidate(filename != null && filename.length() != 0 || isOptional); setErrorText(); } private void setErrorText() { if (isInput && !isOptional) { errorIndicator.setTitle("The specified file is unknown. Please select a known filename"); } else if (isZip) { errorIndicator.setTitle("Select either a folder or specify a filename ending in .zip"); } else { errorIndicator.setTitle("A valid repository and filename must be both specified together"); } } }