Java tutorial
/*************************************************************************** * * This file is part of the NFC Eclipse Plugin project at * http://code.google.com/p/nfc-eclipse-plugin/ * * Copyright (C) 2012 by Thomas Rorvik Skjolberg. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ****************************************************************************/ package org.nfc.eclipse.plugin; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.action.IContributionItem; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.action.StatusLineContributionItem; import org.eclipse.jface.util.LocalSelectionTransfer; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnViewerEditor; import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent; import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter; import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.TreeViewerEditor; import org.eclipse.jface.viewers.TreeViewerFocusCellManager; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DragSourceListener; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.dnd.TreeDropTargetEffect; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.part.EditorPart; import org.nfc.eclipse.plugin.model.NdefRecordModelChangeListener; import org.nfc.eclipse.plugin.model.NdefRecordModelContentProvider; import org.nfc.eclipse.plugin.model.NdefRecordModelHintColumnProvider; import org.nfc.eclipse.plugin.model.NdefRecordModelMenuListener; import org.nfc.eclipse.plugin.model.NdefRecordModelNode; import org.nfc.eclipse.plugin.model.NdefRecordModelParent; import org.nfc.eclipse.plugin.model.NdefRecordModelParentProperty; import org.nfc.eclipse.plugin.model.NdefRecordModelPropertyListItem; import org.nfc.eclipse.plugin.model.NdefRecordModelRecord; import org.nfc.eclipse.plugin.model.NdefRecordModelSizeColumnLabelProvider; import org.nfc.eclipse.plugin.model.NdefRecordModelValueColumnLabelProvider; import org.nfc.eclipse.plugin.model.editing.NdefRecordModelEditingSupport; import org.nfc.eclipse.plugin.operation.NdefModelOperation; import org.nfc.eclipse.plugin.terminal.NdefTerminalListener; import org.nfc.eclipse.plugin.terminal.NdefTerminalWrapper; import org.nfctools.ndef.NdefContext; import org.nfctools.ndef.NdefException; import org.nfctools.ndef.NdefMessageEncoder; import org.nfctools.ndef.NdefOperations; import org.nfctools.ndef.Record; public class NdefEditorPart extends EditorPart implements NdefRecordModelChangeListener { protected boolean dirty = false; protected TreeViewer treeViewer; protected NdefModelOperator operator; protected SashForm form; protected NdefMultiPageEditor ndefMultiPageEditor; public NdefEditorPart(NdefModelOperator operator, NdefMultiPageEditor ndefMultiPageEditor) { this.operator = operator; this.ndefMultiPageEditor = ndefMultiPageEditor; } @Override public void update(NdefRecordModelNode ndefRecordModelNode, NdefModelOperation operation) { operator.update(ndefRecordModelNode, operation); modified(true); treeViewer.expandToLevel(ndefRecordModelNode, TreeViewer.ALL_LEVELS); } @Override public void addRecord(NdefRecordModelParent parent, int index, Class<? extends Record> type) { operator.addRecord(parent, index, type); modified(true); if (index == -1) { treeViewer.expandToLevel(parent.getChild(parent.getSize() - 1), TreeViewer.ALL_LEVELS); } else { treeViewer.expandToLevel(parent.getChild(index), TreeViewer.ALL_LEVELS); } } @Override public void addListItem(NdefRecordModelParent parent, int index) { operator.addListItem(parent, index); modified(true); if (index == -1) { treeViewer.expandToLevel(parent.getChild(parent.getSize() - 1), TreeViewer.ALL_LEVELS); } else { treeViewer.expandToLevel(parent.getChild(index), TreeViewer.ALL_LEVELS); } } @Override public void setRecord(NdefRecordModelParentProperty ndefRecordModelParentProperty, Class<? extends Record> type) { operator.setRecord(ndefRecordModelParentProperty, type); modified(true); treeViewer.expandToLevel(ndefRecordModelParentProperty, TreeViewer.ALL_LEVELS); } @Override public void removeRecord(NdefRecordModelRecord node) { operator.removeRecord(node); modified(true); clearStatus(); } private void clearStatus() { setStatus(""); } @Override public void removeListItem(NdefRecordModelPropertyListItem node) { operator.removeListItem(node); modified(true); } protected void modified(boolean terminal) { treeViewer.refresh(); form.update(); setDirty(operator.isDirty()); updateActions(); clearStatus(); refreshStatusLine(); // also fill the last column (i.e. pack or fill) if any hint has been modified packAndFillLastColumn(); if (terminal) { handleTerminal(); } } private void handleTerminal() { if (NdefTerminalWrapper.isAvailable()) { NdefTerminalListener ndefTerminalListener = NdefTerminalWrapper.getNdefTerminalWriteListener(); if (ndefTerminalListener != null) { if (ndefTerminalListener == ndefMultiPageEditor) { NdefOperations ndefOperations = NdefTerminalWrapper.getNdefOperations(); if (ndefOperations != null) { List<Record> records = operator.getRecords(); // add write option IF message can in fact be written NdefMessageEncoder ndefMessageEncoder = NdefContext.getNdefMessageEncoder(); try { ndefMessageEncoder.encode(records); if (ndefOperations.isFormatted()) { ndefOperations.writeNdefMessage(records.toArray(new Record[records.size()])); } else { ndefOperations.format(records.toArray(new Record[records.size()])); } setStatus("Auto-write successful."); } catch (Exception e) { setStatus("Auto-write not possible."); } } } } } } private void updateActions() { getEditorSite().getActionBars().getGlobalActionHandler(ActionFactory.UNDO.getId()) .setEnabled(operator.canUndo()); getEditorSite().getActionBars().getGlobalActionHandler(ActionFactory.REDO.getId()) .setEnabled(operator.canRedo()); //hexEditor.getEditorSite().getActionBars().getGlobalActionHandler(ActionFactory.PASTE.getId()).setEnabled(canPaste()); } public void refreshStatusLine() { final Display display = Display.getDefault(); new Thread() { public void run() { display.syncExec(new Runnable() { public void run() { IActionBars actionBars = getEditorSite().getActionBars(); if (actionBars == null) { return; } IStatusLineManager statusLineManager = actionBars.getStatusLineManager(); if (statusLineManager == null) { return; } IContributionItem[] items = statusLineManager.getItems(); for (IContributionItem item : items) { if (item.getId().equals(NdefMultiPageEditorContributor.class.getName() + ".size")) { StatusLineContributionItem size = (StatusLineContributionItem) item; try { size.setText(operator.toNdefMessage().length + " bytes "); } catch (NdefException e) { size.setText("-"); } } else if (item.getId() .equals(NdefMultiPageEditorContributor.class.getName() + ".terminal")) { StatusLineContributionItem size = (StatusLineContributionItem) item; if (NdefTerminalWrapper.isAvailable()) { if (NdefTerminalWrapper.hasSeenReader()) { if (NdefTerminalWrapper.isReaderEnabledPreference()) { String terminalName = NdefTerminalWrapper.getTerminalName(); if (terminalName != null) { size.setText(terminalName); } else { size.setText("Card reader disconnected"); } } else { size.setText("Card readers disabled"); } } else { size.setText("No card reader"); } } else { size.setText("No card reader"); } } } // set global message using // statusLineManager.setMessage( ..); } }); } }.start(); } @Override public void doSaveAs() { throw new RuntimeException(); } @Override public void init(IEditorSite site, IEditorInput input) throws PartInitException { setSite(site); setInput(input); } @Override public boolean isDirty() { return dirty; } /** * Sets the "dirty" flag. * @param isDirty true if the file has been modified otherwise false */ public void setDirty(boolean isDirty) { // // Set internal "dirty" flag // this.dirty = isDirty; // // Fire the "property change" event to change file's status within Eclipse IDE // firePropertyChange(IEditorPart.PROP_DIRTY); } @Override public boolean isSaveAsAllowed() { return false; } @Override public void createPartControl(Composite composite) { composite.setLayout(new FillLayout()); composite.setBackground(new Color(composite.getDisplay(), 0xFF, 0xFF, 0xFF)); form = new SashForm(composite, SWT.HORIZONTAL); form.setLayout(new FillLayout()); Composite wrapper = new Composite(form, SWT.NONE); wrapper.setLayout(new FillLayout()); treeViewer = new TreeViewer(wrapper, SWT.BORDER | SWT.FULL_SELECTION); treeViewer.getTree().setLinesVisible(true); treeViewer.getTree().setHeaderVisible(true); ColumnViewerToolTipSupport.enableFor(treeViewer); TreeViewerFocusCellManager focusCellManager = new TreeViewerFocusCellManager(treeViewer, new FocusCellOwnerDrawHighlighter(treeViewer)); ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(treeViewer) { @Override protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) { return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL || event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.CR) || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC; } }; TreeViewerEditor.create(treeViewer, focusCellManager, actSupport, ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR | ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION); TreeViewerColumn column = new TreeViewerColumn(treeViewer, 0, SWT.NONE); column.getColumn().setWidth(200); column.getColumn().setMoveable(true); column.getColumn().setText("Record"); column.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { return element.toString(); } // add tooltip here }); column = new TreeViewerColumn(treeViewer, SWT.NONE); column.getColumn().setWidth(200); column.getColumn().setMoveable(true); column.getColumn().setText("Value"); column.setLabelProvider(new NdefRecordModelValueColumnLabelProvider()); column.setEditingSupport( new NdefRecordModelEditingSupport(treeViewer, this, operator.getNdefRecordFactory())); column = new TreeViewerColumn(treeViewer, SWT.NONE); column.getColumn().setWidth(50); column.getColumn().setMoveable(true); column.getColumn().setText("Size"); column.getColumn().setAlignment(SWT.CENTER); column.setLabelProvider(new NdefRecordModelSizeColumnLabelProvider()); // http://blog.eclipse-tips.com/2008/05/single-column-tableviewer-and.html column = new TreeViewerColumn(treeViewer, SWT.NONE); column.getColumn().setMoveable(true); column.getColumn().setResizable(false); column.getColumn().setText("Hint"); column.getColumn().setAlignment(SWT.LEFT); column.setLabelProvider(new NdefRecordModelHintColumnProvider()); column.getColumn().pack(); treeViewer.setContentProvider(new NdefRecordModelContentProvider()); treeViewer.setInput(operator.getModel()); new NdefRecordModelMenuListener(treeViewer, this, ndefMultiPageEditor, operator.getModel()); treeViewer.expandAll(); // we want the last column to 'fill' with the layout // trigger at key points: // first show composite.getDisplay().asyncExec(new Runnable() { public void run() { packAndFillLastColumn(); } }); // resize treeViewer.getTree().addControlListener(new ControlAdapter() { public void controlResized(ControlEvent e) { packAndFillLastColumn(); } }); // manual resizing of columns Tree tree = treeViewer.getTree(); for (int i = 0; i < tree.getColumnCount() - 1; i++) { tree.getColumn(i).addControlListener(new ControlAdapter() { public void controlResized(ControlEvent e) { packAndFillLastColumn(); } }); } // drag and drop Transfer[] types = new Transfer[] { LocalSelectionTransfer.getTransfer() }; int operations = DND.DROP_MOVE; treeViewer.addDragSupport(operations, types, new DragSourceListener() { @Override public void dragFinished(DragSourceEvent event) { // end drag } @Override public void dragSetData(DragSourceEvent event) { // do nothing for LocalSelectionTransfer } @Override public void dragStart(DragSourceEvent event) { int column = NdefEditorPart.this.getColumn(event.x); if (column == 0) { TreeSelection selection = (TreeSelection) treeViewer.getSelection(); Object node = selection.getFirstElement(); if (node instanceof NdefRecordModelRecord) { event.doit = true; NdefRecordModelRecord ndefRecordModelRecord = (NdefRecordModelRecord) node; if (ndefRecordModelRecord.getLevel() == 1) { Activator.info( "Start drag for " + ndefRecordModelRecord.getRecord().getClass().getSimpleName() + " at level " + ndefRecordModelRecord.getLevel()); event.data = node; event.doit = true; } else { Activator.info("Do not start drag for level " + ndefRecordModelRecord.getLevel()); event.doit = false; } } else { Activator.info("Do not start drag"); event.doit = false; } } else { Activator.info("Do not start drag for column " + column); event.doit = false; } } }); treeViewer.addDropSupport(operations, types, new TreeDropTargetEffect(treeViewer.getTree()) { /** * * Check out what kind of (visual) insert feedback to give user in GUI while dragging: insert before or after. * */ @Override public void dragOver(DropTargetEvent event) { Widget item = event.item; if (item instanceof TreeItem) { TreeItem treeItem = (TreeItem) item; NdefRecordModelNode node = (NdefRecordModelNode) treeItem.getData(); if (node instanceof NdefRecordModelRecord) { if (node.getLevel() == 1) { event.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL; Point pt = event.display.map(null, treeViewer.getControl(), event.x, event.y); Rectangle bounds = treeItem.getBounds(); if (pt.y < bounds.y + bounds.height / 3) { event.feedback |= DND.FEEDBACK_INSERT_BEFORE; } else if (pt.y > bounds.y + 2 * bounds.height / 3) { event.feedback |= DND.FEEDBACK_INSERT_AFTER; } else { // event.feedback |= DND.FEEDBACK_SELECT; } } else { // ignore node } } else { // ignore node } } else if (item != null) { // ignore item } else if (item == null) { // ignore null item } super.dragOver(event); } public void drop(DropTargetEvent event) { Activator.info("Drop " + event.getSource() + " " + event.item); Widget item = event.item; if (item instanceof TreeItem) { TreeItem treeItem = (TreeItem) item; NdefRecordModelNode node = (NdefRecordModelNode) treeItem.getData(); if (node instanceof NdefRecordModelRecord) { if (node.getLevel() == 1) { TreeSelection selection = (TreeSelection) treeViewer.getSelection(); NdefRecordModelRecord source = (NdefRecordModelRecord) selection.getFirstElement(); if (source.getLevel() != 1) { return; } Point pt = event.display.map(null, treeViewer.getControl(), event.x, event.y); Rectangle bounds = treeItem.getBounds(); if (pt.y < bounds.y + bounds.height / 3) { Activator.info("Drop " + source + " before " + node); operator.move(source, node.getParent(), node.getParentIndex()); modified(true); } else if (pt.y > bounds.y + 2 * bounds.height / 3) { Activator.info("Drop " + source + " after " + node); operator.move(source, node.getParent(), node.getParentIndex() + 1); modified(true); } else { // event.feedback |= DND.FEEDBACK_SELECT; } } else { Activator.info("Ignore drop node " + node.getClass().getSimpleName() + " at level " + node.getLevel()); } } else { Activator.info("Ignore drop node " + node.getClass().getSimpleName()); } } else if (item != null) { Activator.info("Ignore drop item " + item.getClass().getSimpleName() + " " + item.getData()); } else if (item == null) { // ignore null item } } }); } /** * * Resize last column in tree viewer so that it fills the client area completely if extra space. * */ protected void packAndFillLastColumn() { Tree tree = treeViewer.getTree(); int columnsWidth = 0; for (int i = 0; i < tree.getColumnCount() - 1; i++) { columnsWidth += tree.getColumn(i).getWidth(); } TreeColumn lastColumn = tree.getColumn(tree.getColumnCount() - 1); lastColumn.pack(); Rectangle area = tree.getClientArea(); Point preferredSize = tree.computeSize(SWT.DEFAULT, SWT.DEFAULT); int width = area.width - 2 * tree.getBorderWidth(); if (preferredSize.y > area.height + tree.getHeaderHeight()) { // Subtract the scrollbar width from the total column width // if a vertical scrollbar will be required Point vBarSize = tree.getVerticalBar().getSize(); width -= vBarSize.x; } // last column is packed, so that is the minimum. If more space is available, add it. if (lastColumn.getWidth() < width - columnsWidth) { lastColumn.setWidth(width - columnsWidth); } } private int getColumn(int x) { int a = 0; for (int i = 0; i < treeViewer.getTree().getColumnCount(); i++) { a += treeViewer.getTree().getColumn(i).getWidth(); if (x <= a) { return i; } } return -1; } @Override public void setFocus() { treeViewer.refresh(); refreshStatusLine(); } @Override public String getTitle() { return "NDEF"; } public void undo() { operator.undo(); modified(true); treeViewer.expandAll(); } public void redo() { operator.redo(); modified(true); treeViewer.expandAll(); } @Override public void doSave(IProgressMonitor monitor) { throw new RuntimeException("Not implemented"); } public void refresh() { treeViewer.refresh(); } public void setStatus(String string) { IActionBars actionBars = getEditorSite().getActionBars(); if (actionBars == null) { return; } IStatusLineManager statusLineManager = actionBars.getStatusLineManager(); if (statusLineManager == null) { return; } statusLineManager.setMessage(string); } @Override public void dispose() { super.dispose(); if (NdefTerminalWrapper.isAvailable()) { NdefTerminalListener ndefTerminalReadListener = NdefTerminalWrapper.getNdefTerminalReadListener(); if (ndefTerminalReadListener != null) { if (ndefTerminalReadListener == ndefMultiPageEditor) { NdefTerminalWrapper.setNdefTerminalReadListener(null); } } NdefTerminalListener ndefTerminalWriteListener = NdefTerminalWrapper.getNdefTerminalWriteListener(); if (ndefTerminalWriteListener != null) { if (ndefTerminalWriteListener == ndefMultiPageEditor) { NdefTerminalWrapper.setNdefTerminalWriteListener(null); } } } } }