| /******************************************************************************* |
| * Copyright (c) 23.04.2012 Aaron Digulla. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Aaron Digulla - initial API and implementation and/or initial documentation |
| *******************************************************************************/ |
| |
| package m4e.p2.ui |
| |
| import javax.swing.AbstractAction |
| import javax.swing.Action; |
| import javax.swing.JDialog |
| import javax.swing.JFrame |
| import javax.swing.JOptionPane; |
| import javax.swing.JPopupMenu |
| import javax.swing.JScrollPane |
| import javax.swing.JTextField; |
| import javax.swing.JTree |
| import javax.swing.event.DocumentEvent; |
| import javax.swing.event.DocumentListener; |
| import javax.swing.event.TreeSelectionListener |
| import javax.swing.tree.TreePath; |
| import javax.swing.tree.TreeSelectionModel |
| import javax.xml.stream.events.StartDocument; |
| |
| import java.awt.BorderLayout as BL |
| import java.awt.event.ActionEvent; |
| import java.awt.event.MouseAdapter |
| import java.awt.event.MouseEvent |
| import java.awt.event.WindowAdapter |
| import java.awt.event.WindowEvent |
| import java.beans.PropertyChangeEvent |
| import java.beans.PropertyChangeListener |
| import java.util.Enumeration; |
| |
| import groovy.swing.SwingBuilder |
| import groovy.swing.impl.DefaultAction; |
| import m4e.p2.DependencySet; |
| import m4e.p2.IP2Repo |
| import m4e.p2.P2Bundle |
| import m4e.p2.P2Feature |
| import m4e.p2.P2Other |
| import m4e.p2.P2Plugin |
| import m4e.p2.P2Unit |
| |
| class P2RepoView { |
| |
| private IP2Repo repo |
| private File workDir |
| |
| P2RepoView( IP2Repo repo, File workDir ) { |
| this.repo = repo |
| this.workDir = workDir |
| } |
| |
| JFrame mainFrame |
| |
| void show() { |
| def swing = new SwingBuilder() |
| |
| def model = new RepoTreeModel( repo ) |
| JTree repoTree |
| JTextField filter = new JTextField() |
| |
| swing.edt { |
| mainFrame = frame( title: 'P2 Repository View', defaultCloseOperation: JFrame.EXIT_ON_CLOSE, size: [ 800, 800 ], show: true) { |
| borderLayout() |
| hbox( constraints: BL.NORTH ) { |
| label( text: 'Filter:', labelFor: filter, displayedMnemonic: 'F' ) |
| widget( filter ) |
| } |
| scrollPane( constraints: BL.CENTER ) { |
| repoTree = tree( model: model ) |
| } |
| } |
| } |
| |
| repoTree.selectionModel.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION |
| def l = { |
| def node = repoTree.lastSelectedPathComponent |
| selectionChanged( node ) |
| } as TreeSelectionListener |
| repoTree.addTreeSelectionListener( l ) |
| |
| l = new PopupAdapter( workDir: workDir, mainFrame: mainFrame ) |
| repoTree.addMouseListener( l ) |
| |
| l = new FilterChangeListener() { |
| void filterChanged( String value ) { |
| def state = new SavedExpandedState( repoTree ) |
| state.save() |
| |
| model.filter( value ) |
| |
| state.restore() |
| } |
| } |
| filter.document.addDocumentListener( l ) |
| } |
| |
| void selectionChanged( Object node ) { |
| // println node |
| if( node instanceof LabelNode ) { |
| def data = node.data |
| println data |
| |
| if( data instanceof P2Unit ) { |
| println data.xml |
| } else if( data instanceof P2Other ) { |
| println data.xml |
| } |
| } |
| } |
| } |
| |
| class PopupAdapter extends MouseAdapter { |
| |
| File workDir |
| JFrame mainFrame |
| |
| void mousePressed( MouseEvent e ) { |
| if( !e.isPopupTrigger() ) { |
| return |
| } |
| |
| JTree tree = e.source |
| def path = tree.getPathForLocation( e.x, e.y ) |
| if( !path ) { |
| return |
| } |
| |
| tree.selectionPath = path |
| def actions = getActions( tree, path, path.lastPathComponent ) |
| if( !actions ) { |
| return |
| } |
| |
| def popup = new JPopupMenu() |
| actions.each { |
| popup.add( it ) |
| } |
| |
| popup.show( tree, e.x, e.y ) |
| } |
| |
| List<Action> getActions( JTree tree, TreePath path, SwingBundle selection ) { |
| P2Bundle bundle = selection.bundle |
| IP2Repo repo = null |
| for( TreePath current = path.parentPath; current; current = current.parentPath ) { |
| def obj = current.lastPathComponent |
| if( obj instanceof SwingRepo ) { |
| repo = obj.repo |
| break |
| } |
| } |
| |
| // println repo |
| |
| def result = [] |
| if( repo ) { |
| result << new DownloadAction( repo, selection, workDir, true, false ) |
| |
| if( selection.source ) { |
| result << new DownloadAction( repo, selection, workDir, false, true ) |
| result << new DownloadAction( repo, selection, workDir, true, true ) |
| } |
| |
| result << new DownloadWithDependenciesAction( repo, bundle, workDir, mainFrame ) |
| } |
| |
| return result |
| } |
| |
| List<Action> getActions( JTree tree, TreePath path, Object selection ) { |
| return [] |
| } |
| } |
| |
| class DownloadAction extends AbstractAction { |
| |
| IP2Repo repo |
| SwingBundle bundle |
| File workDir |
| boolean downloadBundle |
| boolean downloadSources |
| |
| DownloadAction( IP2Repo repo, SwingBundle bundle, File workDir, boolean downloadBundle, boolean downloadSources ) { |
| super( |
| downloadBundle ? ( |
| downloadSources ? 'Download with source' : 'Download this' |
| ) : 'Download source for this' ) |
| |
| this.repo = repo |
| this.bundle = bundle |
| this.workDir = workDir |
| this.downloadBundle = downloadBundle |
| this.downloadSources = downloadSources |
| } |
| |
| void actionPerformed( ActionEvent e ) { |
| def deps = new DependencySet( repo: repo ) |
| if( downloadBundle ) { |
| deps.add( bundle.bundle ) |
| } |
| if( downloadSources ) { |
| deps.add( bundle.source ) |
| } |
| // println deps.bundles |
| |
| Thread.start { |
| deps.download( workDir ) |
| } |
| } |
| } |
| |
| class DownloadWithDependenciesAction extends AbstractAction { |
| |
| IP2Repo repo |
| P2Bundle bundle |
| File workDir |
| JFrame mainFrame |
| |
| DownloadWithDependenciesAction( IP2Repo repo, P2Bundle bundle, File workDir, JFrame mainFrame ) { |
| super( 'Download with all dependencies...' ) |
| |
| this.repo = repo |
| this.bundle = bundle |
| this.workDir = workDir |
| this.mainFrame = mainFrame |
| } |
| |
| void actionPerformed( ActionEvent e ) { |
| def deps = new DependencySet( repo: repo ) |
| |
| SwingBuilder.build { |
| doOutside { |
| deps.resolveDependencies( bundle.id, bundle.version ) |
| |
| doLater { |
| new DownloadDialog( mainFrame, deps, workDir ) |
| } |
| } |
| } |
| } |
| } |
| |
| // Based on code from http://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html |
| class DownloadDialog extends JDialog implements PropertyChangeListener { |
| |
| JOptionPane optionPane |
| DependencySet deps |
| File workDir |
| |
| public DownloadDialog( JFrame frame, DependencySet deps, File workDir ) { |
| super( frame, false ) |
| resizable = true |
| title = "Confirm Download of ${deps.size()} bundles" |
| |
| this.deps = deps |
| this.workDir = workDir |
| |
| def swing = new SwingBuilder() |
| |
| def textPane = swing.textPane( contentType: 'text/html' ) |
| textPane.text = text() |
| |
| def scrollPane = new JScrollPane( textPane ) |
| |
| optionPane = new JOptionPane( scrollPane, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION ) |
| contentPane = optionPane |
| |
| defaultCloseOperation = DO_NOTHING_ON_CLOSE |
| addWindowListener ( new WindowAdapter() { |
| void windowClosing( WindowEvent e ) { |
| optionPane.value = JOptionPane.CLOSED_OPTION |
| } |
| } ) |
| |
| optionPane.addPropertyChangeListener( this ) |
| |
| pack() |
| visible = true |
| } |
| |
| void propertyChange( PropertyChangeEvent e ) { |
| if( !isVisible() ) { |
| return |
| } |
| |
| if( e.source != optionPane ) { |
| return |
| } |
| |
| String prop = e.propertyName |
| if( JOptionPane.VALUE_PROPERTY != prop && JOptionPane.INPUT_VALUE_PROPERTY != prop ) { |
| return |
| } |
| |
| def value = optionPane.value |
| if( JOptionPane.UNINITIALIZED_VALUE == value ) { |
| return |
| } |
| |
| optionPane.value = JOptionPane.UNINITIALIZED_VALUE |
| |
| // println value |
| if( JOptionPane.OK_OPTION == value ) { |
| startDownload() |
| } |
| |
| close() |
| } |
| |
| void close() { |
| visible = false |
| } |
| |
| void startDownload() { |
| Thread.start { |
| deps.download( workDir ) |
| } |
| } |
| |
| String text() { |
| def text = new StringBuilder() |
| |
| text << "You are about to download <b>${deps.size()}</b> bundles from <tt>${deps.repo.url}</tt> to <tt>${workDir.absolutePath}</tt>:<p>\n\n<table>\n" |
| text << '<tr><th>Type</th><th>Name</th><th>Version</th><th>ID</th></tr>' |
| List<String> bundles = deps.bundles.collect { toHTML( it ) } |
| bundles.sort().each { |
| text << '<tr>' << it << '</tr>' |
| } |
| text << '\n</table>' |
| |
| def unknownIds = deps.unknownIds as ArrayList |
| if( unknownIds ) { |
| text << "<p>\n\nThe following <b>${unknownIds.size()}</b> dependencies couldn't be resolved from this repo:<p>\n\n" |
| unknownIds.sort().each { |
| text << '<tt>' << it << '</tt><br/>' |
| } |
| } |
| |
| return text.toString() |
| } |
| |
| String toHTML( P2Feature item ) { |
| return "<td>Feature</td><td>${item.name}</td><td>${item.version}</td><td>${item.id}</td>" |
| } |
| |
| String toHTML( P2Plugin item ) { |
| return "<td>Plug-in</td><td>${item.name}</td><td>${item.version}</td><td>${item.id}</td>" |
| } |
| |
| String toHTML( Object item ) { |
| return item.toString() |
| } |
| } |
| |
| class SavedExpandedState { |
| private JTree tree |
| private List<String[]> openNodes = [] |
| |
| SavedExpandedState( JTree tree ) { |
| this.tree = tree |
| } |
| |
| void save() { |
| TreePath root = new TreePath( tree.model.getRoot () ) |
| |
| saveRecursive( tree.getExpandedDescendants( root ) ) |
| } |
| |
| private void saveRecursive( Enumeration<TreePath> e ) { |
| for( TreePath path : e ) { |
| savePath( path ) |
| } |
| } |
| |
| private void savePath( TreePath path ) { |
| List<String> segments = [] |
| for( int i=0; i<path.getPathCount(); i++ ) { |
| segments << path.getPathComponent (i).id() |
| } |
| |
| // println segments |
| openNodes << segments.toArray() |
| } |
| |
| void restore() { |
| |
| TreePath root = new TreePath( tree.model.getRoot () ) |
| tree.expandPath( root ) |
| |
| for( String[] segments : openNodes ) { |
| open( segments ) |
| } |
| } |
| |
| private void open( String[] segments ) { |
| TreePath path = toPath( segments ) |
| if( path ) { |
| tree.expandPath( path ) |
| } |
| } |
| |
| private TreePath toPath( String[] segments ) { |
| ITreeNode parent = tree.model.root |
| TreePath path = new TreePath( parent ) |
| |
| for( int i=1; i<segments.size(); i++ ) { |
| String id = segments[i] |
| TreePath next = null |
| |
| for( int j=0; j<parent.getChildCount(); j++ ) { |
| Object child = parent.getChild( j ) |
| |
| if( child.id() == id ) { |
| next = new TreePath( path, child ) |
| parent = child |
| break |
| } |
| } |
| |
| if( !next ) { |
| println "Unable to find ${Arrays.toList( segments )}" |
| return null |
| } |
| |
| path = next |
| } |
| |
| return path |
| } |
| } |
| |
| class FilterChangeListener implements DocumentListener { |
| |
| private String oldValue = '' |
| |
| public void insertUpdate( DocumentEvent e ) { |
| String newValue = e.document.getText( 0, e.document.length ) |
| if( oldValue == newValue ) { |
| return |
| } |
| |
| oldValue = newValue |
| filterChanged( newValue ) |
| } |
| |
| public void removeUpdate (DocumentEvent e) |
| { |
| insertUpdate( e ) |
| } |
| |
| public void changedUpdate (DocumentEvent e) |
| { |
| insertUpdate( e ) |
| } |
| |
| void filterChanged( String value ) { |
| // Do nothing |
| } |
| } |