/*
* Copyright (c) 2010-2012 Research In Motion Limited. All rights reserved.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License, Version 1.0,
* which accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*
*/
package net.rim.ejde.internal.ui.editors.key;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Vector;
import net.rim.ejde.internal.core.ContextManager;
import net.rim.ejde.internal.core.IConstants;
import net.rim.ejde.internal.internalplugin.InternalFragmentReplaceable;
import net.rim.ejde.internal.model.BlackBerryProperties;
import net.rim.ejde.internal.ui.CompositeFactory;
import net.rim.ejde.internal.ui.editors.key.CodeSigningState.OnePackage;
import net.rim.ejde.internal.ui.editors.key.CodeSigningState.TreeNode;
import net.rim.ejde.internal.ui.editors.key.CodeSigningState.TreeNodeAndCheckBox;
import net.rim.ejde.internal.util.Messages;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.EditorPart;
/**
* An instance of this class is associated with a *.key file to select which packages and class in the current project are
* protected by this key file.
*
* For the internal fragment, this class is replaced entirely with a new class that removes access to this editor. Internal users
* are directed to the new editor implmentation.
*/
@InternalFragmentReplaceable
public class PrivateKeyEditor extends EditorPart implements ICheckStateListener, IConstants {
private static final Logger _logger = Logger.getLogger( PrivateKeyEditor.class );
private CheckboxTreeViewer _treeViewer;
private CheckTreeItem _rootTreeItem;
private CodeSigningState _codeSigningState;
private String _inputFileName;
private boolean _isPageModified;
private IProject _project;
private BlackBerryProperties _bbProperties;
private Combo _keyCombo;
/**
* Creates the UI of this editor.
*
* @param node
* The key file.
* @param parent
* The parent composite
* @return
*/
private void createUI( IFile node, Composite parent ) {
// parse the project to get code signing information
try {
parseForPackages( node );
} catch( CoreException e ) {
_logger.error( e );
return;
}
Composite composite = CompositeFactory.gridComposite( parent, 1 );
composite.setLayoutData( GridData.FILL_BOTH );
Vector< String > keys = _codeSigningState.getKeys();
if( !keys.contains( _inputFileName ) ) {
MessageDialog.openError( this.getSite().getShell(), Messages.PrivateKeyEditor_MESSAGE_DIALOG_TITLE,
NLS.bind( Messages.PrivateKeyEditor_UNEXPECTED_KEY_FILE_MESSAGE, _inputFileName ) );
return;
}
// create the combo for key files
_keyCombo = new Combo( composite, SWT.READ_ONLY );
_keyCombo.setLayoutData( new GridData( GridData.FILL_HORIZONTAL ) );
String[] items = new String[ keys.size() ];
_keyCombo.setItems( keys.toArray( items ) );
_keyCombo.addSelectionListener( new SelectionAdapter() {
public void widgetSelected( SelectionEvent e ) {
doSaveInMemory();
// update the singing information when the selection of key file
// is changed
_codeSigningState.setKey( _keyCombo.getItem( _keyCombo.getSelectionIndex() ).toString() );
_treeViewer.setInput( _rootTreeItem );
checkSelectionStatus( _treeViewer.getTree() );
}
} );
_keyCombo.select( keys.indexOf( _inputFileName ) );
// Combo.select() method does not invoke the SelectionEvent, we do it
// implicitly here
_codeSigningState.setKey( _keyCombo.getItem( _keyCombo.getSelectionIndex() ).toString() );
// code signing function button area
Composite buttonComp = CompositeFactory.gridCompositeWithBorder( composite, 4 );
buttonComp.setLayoutData( new GridData( GridData.FILL_HORIZONTAL ) );
Button selectAllPackages = new Button( buttonComp, SWT.PUSH );
selectAllPackages.setText( Messages.PrivateKeyEditor_PROTECT_ALL_PACKAGES_TITLE );
selectAllPackages.setToolTipText( Messages.PrivateKeyEditor_PROTECT_ALL_PACKAGES_TITLE_TOOLTIP );
Button selectAllClasses = new Button( buttonComp, SWT.PUSH );
selectAllClasses.setText( Messages.PrivateKeyEditor_PROTECT_ALL_CLASSES_TITLE );
selectAllClasses.setToolTipText( Messages.PrivateKeyEditor_PROTECT_ALL_CLASSES_TITLE_TOOLTIP );
Button clear = new Button( buttonComp, SWT.PUSH );
clear.setText( Messages.PrivateKeyEditor_CLEAR_PROTECT_TITLE );
clear.setToolTipText( Messages.PrivateKeyEditor_CLEAR_PROTECT_TITLE_TOOLTIP );
Button clearAll = new Button( buttonComp, SWT.PUSH );
clearAll.setText( Messages.PrivateKeyEditor_CLEARALL_PROTECT_TITLE );
clearAll.setToolTipText( Messages.PrivateKeyEditor_CLEARALL_PROTECT_TITLE_TOOLTIP );
selectAllPackages.addSelectionListener( new SelectionListener() {
public void widgetDefaultSelected( SelectionEvent e ) {
// nothing to do
}
public void widgetSelected( SelectionEvent e ) {
_codeSigningState.checkEnabledPackages();
if( _treeViewer != null )
checkSelectionStatus( _treeViewer.getTree() );
handleChange();
}
} );
selectAllClasses.addSelectionListener( new SelectionListener() {
public void widgetDefaultSelected( SelectionEvent e ) {
// nothing to do
}
public void widgetSelected( SelectionEvent e ) {
_codeSigningState.checkEnabledClasses();
if( _treeViewer != null )
checkSelectionStatus( _treeViewer.getTree() );
handleChange();
}
} );
clear.addSelectionListener( new SelectionListener() {
public void widgetDefaultSelected( SelectionEvent e ) {
// nothing to do
}
public void widgetSelected( SelectionEvent e ) {
_codeSigningState.clearEnabledPackagesAndClasses();
if( _treeViewer != null )
checkSelectionStatus( _treeViewer.getTree() );
handleChange();
}
} );
clearAll.addSelectionListener( new SelectionListener() {
public void widgetDefaultSelected( SelectionEvent e ) {
// nothing to do
}
public void widgetSelected( SelectionEvent e ) {
MessageBox messageBox = new MessageBox( getSite().getShell(), SWT.YES | SWT.NO );
messageBox.setMessage( Messages.PrivateKeyEditor_CLEARALL_PROTECT_WARNING_MESSAGE );
if( messageBox.open() == SWT.NO )
return;
_codeSigningState.clearAllPackagesAndClasses();
if( _treeViewer != null ) {
checkSelectionStatus( _treeViewer.getTree() );
_treeViewer.refresh();
}
handleChange();
}
} );
_treeViewer = new CheckboxTreeViewer( composite, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER );
_treeViewer.addCheckStateListener( this );
_treeViewer.getTree().setLayoutData( new GridData( GridData.FILL_BOTH ) );
_treeViewer.addCheckStateListener( new ICheckStateListener() {
public void checkStateChanged( CheckStateChangedEvent event ) {
CheckTreeItem item = (CheckTreeItem) event.getElement();
if( !item.isEnabled() ) {
// if the element is disabled, we change the check status back
// just mimic it is not changed
_treeViewer.setChecked( event.getElement(), !event.getChecked() );
item.setSelected( !event.getChecked() );
}
}
} );
_treeViewer.getTree().addTreeListener( new TreeListener() {
public void treeCollapsed( TreeEvent e ) {
// nothing to do
}
public void treeExpanded( TreeEvent e ) {
// usually the tree is lazily displayed, each time only one
// layer is displayed. We need to mark the selection when we
// expand a tree item
checkSelectionStatus( e.item );
}
} );
_rootTreeItem = (CheckTreeItem) _codeSigningState.getTree();
_treeViewer.setContentProvider( new MyContentProvider() );
_treeViewer.setLabelProvider( new MyLabelProvider() );
_treeViewer.setInput( _rootTreeItem );
checkSelectionStatus( _treeViewer.getTree() );
}
/**
* Checks the selection statuses of all children of <code>item</code> and sets the check statuses of corresponding check-boxes
* in the tree viewer.
*
* @param item
*/
private void checkSelectionStatus( Object obj ) {
TreeItem[] children;
if( obj instanceof Tree )
children = ( (Tree) obj ).getItems();
else if( obj instanceof TreeItem )
children = ( (TreeItem) obj ).getItems();
else
return;
for( int i = 0; i < children.length; i++ ) {
CheckTreeItem item = (CheckTreeItem) children[ i ].getData();
if( item != null ) {
children[ i ].setChecked( item.isSelected() );
_treeViewer.setGrayed( item, !item.isEnabled() );
if( !item.isEnabled() ) {
// children [ i ].setBackground( Display.getCurrent().getSystemColor( SWT.COLOR_GRAY ) );
children[ i ].setForeground( Display.getCurrent().getSystemColor( SWT.COLOR_DARK_GRAY ) );
} else {
// children [ i ].setBackground( Display.getCurrent().getSystemColor( SWT.COLOR_WHITE ) );
children[ i ].setForeground( Display.getCurrent().getSystemColor( SWT.COLOR_BLACK ) );
}
checkSelectionStatus( children[ i ] );
}
}
}
/**
* Parses the project to which <code>node</code> (usually a key file) belongs and retrieves code signing information.
*
* @param node
* @return Code signing information related to the <code>node</code>.
* @throws CoreException
*/
private List< OnePackage > parseForPackages( final IFile node ) throws CoreException {
class ParseAll implements CodeSigningState.Callback {
List< OnePackage > _pakkages = new Vector< OnePackage >();
public TreeNodeAndCheckBox newTreeNodeAndCheckBox( TreeNode parent ) {
CheckTreeItem newItem = new CheckTreeItem( (CheckTreeItem) parent );
return new TreeNodeAndCheckBox( newItem, newItem );
}
public void nodeChanged( CodeSigningState.TreeNode node ) {
// do nothing
}
public void setCurrentlyParsing( String name ) {
// TODO may use this method when we add progress monitor into
// the program.
}
public void initialize() throws CoreException {
_pakkages = _codeSigningState.initialize( _project, _bbProperties, node, this );
}
public List< OnePackage > getPackages() {
return _pakkages;
}
}
ParseAll parseAll = new ParseAll();
parseAll.initialize();
List< OnePackage > pakkages = parseAll.getPackages();
return pakkages;
}
/**
* Saves the page if it is dirty.
*/
public void doSave( IProgressMonitor monitor ) {
if( _isPageModified ) {
_isPageModified = false;
doSaveInMemory();
if( _project != null ) {
ContextManager.PLUGIN.setBBProperties( _project.getName(), _bbProperties, true );
}
firePropertyChange( IEditorPart.PROP_DIRTY );
}
}
private void doSaveInMemory() {
_codeSigningState.updateCheck( _rootTreeItem );
_codeSigningState.ok();
}
public void doSaveAs() {
// do nothing
}
/**
* Initializes this editor.
*
* @param site
* The IEditorSite of this editor.
* @param input
* The IEditorInput of this editor.
*/
public void init( IEditorSite site, IEditorInput input ) throws PartInitException {
if( !( input instanceof IFileEditorInput ) )
throw new PartInitException( Messages.PrivateKeyEditor_INVALID_EDITOR_INPUT_MESSAGE );
setSite( site );
setInput( input );
_codeSigningState = new CodeSigningState();
}
/**
* Handles a property change notification from a nested editor. In our case, the <code>_isPageModified</code> field is
* adjusted as appropriate and superclass is called to notify listeners of the change.
*/
protected void handlePropertyChange( int propertyId ) {
if( propertyId == IEditorPart.PROP_DIRTY ) {
_isPageModified = isDirty();
}
firePropertyChange( propertyId );
}
/**
* Checks if the content is modified.
*
* @return <code>true</code> if the content is modified, <code>false</code> otherwise.
*/
public boolean isDirty() {
return _isPageModified;
}
public boolean isSaveAsAllowed() {
return false;
}
/**
* Gets the selected workspace file (key file) and creates the UI of the editor of it.
*/
public void createPartControl( Composite parent ) {
IEditorInput input = getEditorInput();
IFile file = (IFile) input.getAdapter( IFile.class );
if( file == null ) {
throw new IllegalArgumentException( "Could not load selected file" );
}
_project = file.getProject();
_bbProperties = ContextManager.PLUGIN.getBBProperties( _project.getName(), false );
// key file name must be relative to workspace
_inputFileName = file.getProjectRelativePath().toOSString();
updateTitle();
createUI( file, parent );
}
public void setFocus() {
// nothing to do
}
/**
* Implementation of {@link ICheckStateListener#checkStateChanged(CheckStateChangedEvent)}. When a TreeItem is
* checked/unchecked, the <code>selected</code> attribute of corresponding <code>CheckTreeItem</code> need to be updated.
*
* @param event
*/
public void checkStateChanged( CheckStateChangedEvent event ) {
if( !( event.getElement() instanceof CheckTreeItem ) ) {
return;
}
CheckTreeItem checkTreeItem = (CheckTreeItem) event.getElement();
if( !checkTreeItem.isEnabled() ) {
return;
}
checkTreeItem.setSelected( event.getChecked() );
_codeSigningState.updateCheck( checkTreeItem );
_treeViewer.refresh();
handleChange();
}
/**
* Update the editor's title based upon the content being edited.
*/
private void updateTitle() {
IEditorInput input = getEditorInput();
IFile newFile = (IFile) input.getAdapter( IFile.class );
setPartName( newFile.getName() );
setTitleToolTip( input.getToolTipText() );
}
private void handleChange() {
if( !_isPageModified ) {
_isPageModified = true;
firePropertyChange( IEditorPart.PROP_DIRTY );
}
}
// ------ Inner classes ------
/**
* An instance of this class presents an element of the tree item in the checkbox tree viewer.
*/
private class CheckTreeItem implements CodeSigningState.CheckBox, CodeSigningState.TreeNode {
private boolean _checked;
private boolean _enabled;
private Object _text;
private CheckTreeItem _parent;
private Vector< CheckTreeItem > _childrenVec;
/**
* Constructs an instance of CheckTreeItem.
*
* @param parent
*/
public CheckTreeItem( CheckTreeItem parent ) {
_parent = parent;
_childrenVec = new Vector< CheckTreeItem >();
}
/**
* Gets the children of this item.
*
* @return the children of this item.
*/
public Vector< CheckTreeItem > getChildren() {
return _childrenVec;
}
/**
* Gets the parent of this item.
*
* @return the parent of this item.
*/
public CheckTreeItem getParent() {
return _parent;
}
// ------ Methods in CodeSigningState.CheckBox ------
public boolean isEnabled() {
return _enabled;
}
public boolean isSelected() {
return _checked;
}
public void setEnabled( boolean on ) {
_enabled = on;
}
public void setSelected( boolean on ) {
_checked = on;
}
public String getText() {
return _text.toString();
}
public void setText( String text ) {
_text = text;
}
// ------ Methods in CodeSigningState.TreeNode ------
public void addChild( TreeNode child ) {
_childrenVec.add( (CheckTreeItem) child );
}
public void setText( Object o ) {
_text = o;
}
}
/**
* The content provider of the tree viewer.
*/
class MyContentProvider implements ITreeContentProvider {
public Object[] getChildren( Object parentElement ) {
return ( (CheckTreeItem) parentElement ).getChildren().toArray();
}
public Object getParent( Object element ) {
return ( (CheckTreeItem) element ).getParent();
}
public boolean hasChildren( Object element ) {
return ( (CheckTreeItem) element ).getChildren().size() == 0 ? false : true;
}
public Object[] getElements( Object inputElement ) {
return ( (CheckTreeItem) inputElement ).getChildren().toArray();
}
public void dispose() {
// nothing to do
}
public void inputChanged( Viewer viewer, Object oldInput, Object newInput ) {
// nothing to do
}
}
/**
* The label provider of the tree viewer.
*/
class MyLabelProvider implements ILabelProvider {
public Image getImage( Object element ) {
return null;
}
public String getText( Object element ) {
return ( (CheckTreeItem) element ).getText();
}
public void addListener( ILabelProviderListener listener ) {
// nothing to do.
}
public void dispose() {
// nothing to do
}
public boolean isLabelProperty( Object element, String property ) {
return false;
}
public void removeListener( ILabelProviderListener listener ) {
// nothing to do.
}
}
// return EditorReferences from all the pages in eclipse
public IEditorReference[] getOpenEditorReferences() {
Vector< IEditorReference > openEditorReferences = new Vector< IEditorReference >( 0 );
IWorkbenchPage[] workbenchPage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getPages();
int workbenchPageCount = workbenchPage.length;
for( int i = 0; i < workbenchPageCount; i++ ) {
Collection< IEditorReference > editorReferences = Arrays.asList( workbenchPage[ i ]
.getEditorReferences() );
openEditorReferences.addAll( editorReferences );
}
return openEditorReferences.toArray( new IEditorReference[ 1 ] );
}
public void switchKey( String newKey ) {
if( !newKey.equals( _codeSigningState.getKey() ) ) {
Vector< String > keys = _codeSigningState.getKeys();
int index = keys.indexOf( newKey );
if( index != -1 ) {
doSaveInMemory();
_keyCombo.select( index );
_codeSigningState.setKey( newKey );
_treeViewer.setInput( _rootTreeItem );
checkSelectionStatus( _treeViewer.getTree() );
}
}
}
}