java swing drag and drop on Ubuntu 20.04.4 LTS - java

I didn't change my code, but drag and drop recently stopped working to external applications (Firefox/Chrome browsers, dolphin, etc.) from a java Swing application running on Ubuntu. We think this may have changed after an Ubuntu upgrade.
The problem happens dropping onto Firefox/Google Chrome or onto file explorers on Ubuntu. The problem doesn't affect drag and drop within our Swing application.
When a drag is initiated, you can see the icon for dragging correctly shows, but when you drag over any of the drop locations, they show a red cancel type icon showing you can't drop there. Letting go of the mouse at that point does nothing in the java application code.
What needs to be changed in our java app to support what is expected? Drag and drop still works fine within the java app.
Here is the java code used to support a Transfer:
public static DataFlavor uriListFlavor; // initialized elsewhere as new DataFlavor("text/uri-list;class=java.lang.String");
JTable docsTable; // initialized elsewhere
docsTable.setTransferHandler(new DocTransferHandler());
//etc.
public static class DocTransferHandler extends TransferHandler {
DocTransferHandler() {
}
#Override
protected Transferable createTransferable(JComponent component) {
ArrayList<File> files = new ArrayList<>();
if (component instanceof JTable) {
JTable table = (JTable)component;
int[] rows = table.getSelectedRows();
for (int row : rows) {
String filePath = String.valueOf(table.getTModel().getValueAt(row, FILE_PATH_COLUMN));
String fileName = String.valueOf(table.getTModel().getValueAt(row, FILE_NAME_COLUMN));
files.add(new File(filePath+"/"+fileName));
}
}
return new Transferable() {
#Override
public DataFlavor[] getTransferDataFlavors() {
DataFlavor[] flavors = new DataFlavor[2];
flavors[0] = DataFlavor.javaFileListFlavor;
flavors[1] = uriListFlavor;
return flavors;
}
#Override
public boolean canImport(TransferSupport support) {
return (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor) || support.isDataFlavorSupported(uriListFlavor);
}
#Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (flavor.equals(CaseManagedDocs.uriListFlavor)) {
String uriList = "";
for (File file : files) {
uriList += file.toURI() + System.lineSeparator();
}
return uriList;
} else if (flavor.equals(DataFlavor.javaFileListFlavor)) {
return files;
}
throw new UnsupportedFlavorException(flavor);
}
};
}

Related

Change project nature in a RCP application

I have in my RCP application a View that extends the CommonNavigator class. The projects in the workspace of my application should have different icons depending on their location on the disk: the projects that exist locally in the workspace should have a different icon from the projects that were imported.
I realised this by defining in the plugin.xml two project natures: MyProjectNature and MyProjectNatureImported with different icons and changing between the natures accordingly with the following method:
private void updateProjectNature(final IWorkspace lf_workspace)
{
String l_workspacePath = lf_workspace.getRoot().getLocation().toString();
IProject[] l_projectsInWorkspace = lf_workspace.getRoot().getProjects();
for (IProject l_project : l_projectsInWorkspace)
{
try
{
File l_projectFile = new File(l_workspacePath + l_project.getFullPath().toString());
final IProjectDescription l_projectDescription = l_project.getDescription();
final String[] l_currentNatures = l_projectDescription.getNatureIds();
final String[] l_newNatures = new String[l_currentNatures.length];
int l_index = 0;
if (l_projectFile.exists())
{
for (String l_nature : l_currentNatures)
{
if (l_nature.equals(MyProjectNatureImported.NATURE_ID))
{
l_newNatures[l_index] = MyProjectNature.NATURE_ID;
}
else
{
l_newNatures[l_index] = l_nature;
}
l_index++;
}
}
else
{
for (String l_nature : l_currentNatures)
{
if (l_nature.equals(MyProjectNature.NATURE_ID))
{
l_newNatures[l_index] = MyProjectNatureImported.NATURE_ID;
}
else
{
l_newNatures[l_index] = l_nature;
}
l_index++;
}
}
l_projectDescription.setNatureIds(l_newNatures);
l_project.setDescription(l_projectDescription, null);
}
catch (CoreException e)
{
LOGGER.warning("Error when setting the project nature of the project " + l_project.getName() + ": " + e.getMessage());
}
}
}
When I call this method from the ResourceChangeListener that I added to the workspace, I get an error for each project that it is locked and cannot be editted:
final IWorkspace lf_workspace = ResourcesPlugin.getWorkspace();
lf_workspace.addResourceChangeListener(new IResourceChangeListener()
{
#Override
public void resourceChanged(IResourceChangeEvent event)
{
updateProjectNature(lf_workspace);
}
});
But when I create a Job that runs each few seconds, then it works:
Job l_testJob = new Job("Update navigator")
{
#Override
protected IStatus run(IProgressMonitor monitor)
{
updateProjectNature(lf_workspace);
schedule(1000);
return Status.OK_STATUS;
}
#Override
public boolean shouldSchedule()
{
// Check if the job should be scheduled / executed or not
return !PlatformUI.getWorkbench().isClosing();
}
};
l_testJob.schedule(1000);
I would like to call the method only when changes are made to the workspace and not each second (to save the resources), but I don't understand why I get the error and cannot change the nature from the listener while from the job there is no problem.
Any ideas?
The workspace is locked while the resource change event is sent so that listeners can't make any more changes.
You should be able to submit a single Job from your listener with a scheduling rule to delay the job until the workspace is available. You should use a WorkspaceJob to make sure the update is atomic.
class UpdateNatureJob extends WorkspaceJob
{
UpdateNatureJob()
{
// Scheduling rule
setRule(ResourcesPlugin.getWorkspace().getRoot());
}
#Override
public IStatus runInWorkspace(final IProgressMonitor monitor)
{
... your nature update
return Status.OK_STATUS;
}
}
You are misusing project natures. While your actual problem can be worked around with an extra job, it is an indicator that project nature are likely the wrong means to achieve different images.
The designated way to emphasize those differences like local and imported projects are decorators. Decorators allow you to decorate or exchange image and text of an element (almost) irrespective where it is shown.

Getting 'invalid drawable'

I'm trying to use OpenGL directly from Java using JNA on Mac OSX (I have done it successfully with Windows and Linux). I've browsed thru JOGL source but they use CALayers which I don't understand yet. I would like to just simply use NSOpenGLView if possible and place it over top the AWT Canvas. I find the NSWindow using JNA and add the NSOpenGLView I created and it seems to work except when I call [nsOpenGLContext setView] or [nsOpenGLView lockFocus] I get an 'invalid drawable' error. I learned from Rococoa how to use ObjectiveC from Java.
Here is some sample code:
private static boolean createMac(GL gl, Component c) {
NSAutoreleasePool pool = new NSAutoreleasePool();
pool.alloc();
pool.init();
gl.nsopenglview = new NSOpenGLView();
gl.nsopenglview.alloc();
Pointer ptr = Native.getWindowPointer(findWindow(c));
NSObject nsComponent = new NSObject();
nsComponent.obj = ptr;
Pointer cClass = nsComponent._class();
NSView view = new NSView();
view.alloc();
boolean isView = view.isKindOfClass(cClass);
// JFLog.log("test=" + isView);
if (isView) {
view.dealloc();
view.obj = ptr; //do NOT dealloc this (usually NSWindowViewAWT)
gl.nswindow = view.window();
} else {
view.dealloc();
gl.nswindow = new NSWindow();
gl.nswindow.obj = ptr;
}
NSOpenGLPixelFormat fmt = new NSOpenGLPixelFormat();
fmt.alloc();
fmt.initWithAttributes(new int[] {
NSOpenGLPFAWindow,
// NSOpenGLPFAAccelerated, //is not available on my test system
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAColorSize,24,
NSOpenGLPFADepthSize,16,
0 //zero terminate list
}
);
if (fmt.obj == null) {
JFLog.log("NSOpenGLPixelFormat initWithAttributes failed");
return false;
}
if (gl.nsopenglview != null) {
gl.nsopenglview.initWithFrame(new NSRect(c.getBounds()), fmt);
}
NSView content = gl.nswindow.contentView();
JFLog.log("content view=" + content.obj);
content.addSubview(gl.nsopenglview);
JFLog.log("layered=" + content.wantsLayer());
//use created context
gl.nsopenglcontext = gl.nsopenglview.openGLContext();
//create some resize/move listeners
final GL _gl = gl;
final Component _c = c;
c.addComponentListener(new ComponentListener() {
public void componentResized(ComponentEvent e) {
_gl.nsopenglview.setFrame(new NSRect(_c.getBounds()));
}
public void componentMoved(ComponentEvent e) {
_gl.nsopenglview.setFrame(new NSRect(_c.getBounds()));
}
public void componentShown(ComponentEvent e) {}
public void componentHidden(ComponentEvent e) {}
});
if (api == null) {
api = new GLFuncs();
gl.glLibrary = NativeLibrary.getInstance("OpenGL");
try {
Field fields[] = api.getClass().getFields();
for(int a=0;a<fields.length;a++) {
String name = fields[a].getName();
try {
fields[a].set(api, gl.glLibrary.getFunction(name));
} catch (Throwable t) {
JFLog.log("OpenGL:Warning:Function not found:" + name);
}
}
} catch (Exception e) {
JFLog.log(e);
}
}
pool.release();
return true;
}
I can't use the drawRect function in NSOpenGLView so I just lockFocus, use gl commands and unlockFocus when done. But the NSOpenGLContext doesn't have a view assigned and trying to assign the one I created generates the 'invalid drawable'.
Any ideas?
If you want a full working demo goto http://javaforce.sf.net and download v7.15.0, run ant in /jf and then in /projects/jtest3d and then execute run.sh (click the GLCanvas test).
I got it working! The problem was in Rococoa (or possibly a bug in JNA). Their NSRect structure does not pass to [NSOpenGLView initWithFrame] or [NSWindow initWithContentRect] properly. If I pass the 4 fields directly (x,y,width,height) to the function instead of the Structure itself then it works. Also I used [NSObject performSelectorOnMainThread] to make sure I do all GUI stuff on the main thread.
So it is possible to use OpenGL using pure JNA from Java. No native code needed.
This should be available in my javaforce.sf.net in v7.16 which I'll release in a while.
Thanks.

JTable does not render

When page with applet is loaded JTable object's content is rendered perfectly. When I close a tab and reopen it content set at start is rendered, but when the content I changed via setValeAt() table becomes empty. When i remove all html tags from data model everything works. I do nothing with table's default renderer only implent data model. All values are set via setValeAt() and it works (i checked it).
Can someone have any idea what could be wrong?
Here is sample code wich reproduces the error. Run the applet (values will be changing) and then close the tab (NOT browser) and reopen it. I tested it in Firefox 3.6.13 and Java 1.6.0_22-b04 on Linux and Windows XP. It acts the same.
import javax.swing.JApplet;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
public class test extends JApplet
{
class model extends AbstractTableModel
{
#Override
public int getColumnCount()
{
return 3;
}
#Override
public int getRowCount()
{
return 3;
}
#Override
public Object getValueAt(int arg0, int arg1)
{
return "<html><b>" + Math.random() + "</b></html>"; // does not work
//return "" + Math.random(); // work
}
public void setValueAt(Object newValue, int row, int col)
{
fireTableCellUpdated(row, col );
}
}
JTable t = new JTable();
public void init()
{
t.setModel( new model() );
add( t );
}
public void start()
{
new Thread( new Runnable()
{
#Override
public void run()
{
int i=0;
try
{
while( ++i<100 )
{
Thread.sleep(100); // thanks to camickr
t.setValueAt(Void.class, (int)(Math.random()*10) %3, (int)(Math.random()*10) %3 );
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}).start();
}
}
EDIT:
HTML applet code inside index.html
<applet width="500px" height="500px" alt="test test test" code="test.class"></applet>
Compilation code:
javac test.java
produces: files test$1.class test.class test$model.class
Running first time OK. Make new tab and then close applet tab. Reopen applet tab (do not close browser) and table content does not render.
EDIT 2
IT IS A JVM BUG! see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6995386 and others related. It only affects JRE 1.6.0_22
I couldn't reproduce the problem using JDK6_7 on XP.
values will be changing
The values don't keep changing since you don't sleep after each update to the model. I changed the code slightly:
// Thread.sleep(100);
while( ++i<100 )
{
t.setValueAt(Void.class, (int)(Math.random()*10) %3, (int)(Math.random()*10) %3 );
Thread.sleep(500);
}

Drag and Drop compressed files from swing to native [update]

I am trying to move files from a compressed archive to the native system (basycally, windows' eplorer) through drag and drop operations.
My only idea at this moment is to create a TransferHandler, which, when launched, will decompress the file in a temporary directory and set up that file as Transferable. Here is a snippet of code to make myself more clear:
private class FileTransferHandler extends TransferHandler {
protected Transferable createTransferable(JComponent c) {
List<File> files = new ArrayList<File>();
try {
File temp = createTempDirectory();
String path = temp.getAbsolutePath();
decompressTo(path);
files.add(new File(path));
} catch (Exception e) { e.printStackTrace(); };
return new FileTransferable(files);
}
public int getSourceActions(JComponent c) {
return COPY;
}
}
private class FileTransferable implements Transferable {
private List<File> files;
public FileTransferable(List<File> files) {
this.files = files;
}
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{ DataFlavor.javaFileListFlavor };
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(DataFlavor.javaFileListFlavor);
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (!isDataFlavorSupported(flavor)) {
throw new UnsupportedFlavorException(flavor);
}
return files;
}
}
(not valid anymore: The thing that puzzles me is that this way it somehow works: but only if after I release the mouse button I am not doing anything else.)
update: After more testing I observed that actually the file data is transferred from the temp directory to destination after I click in a zone that accepts that DataFlavor. If I click in a folder, the temp files are transferred to that folder. If I click in the console window, the path to the temp file appears in the console window.
So, if you please, I would like some pointers to direct me in the right way.
p.s.: the idea with decompressing to temp folder first came after observing that WinRar is doing the same thing.
p.p.s.: sorry if the question seems stupid, but I am mostly a web programmer just dabbling in desktop programming.
Well, this is apparently nearing the best results you can get.
I'll check your code when I have some time for it.

Eclipse JFace's Wizards

I need a wizard which second page content depends on the first page's selection. The first page asks the user the "kind" of filter he wants to create and the second one asks the user to create one filter instance of the selected "kind".
JFace's wizards pages contents (createControl(...) method) are all created when the wizard is open and not when a given page is displayed (this allow JFace to know the wizard size I guess ??).
Because of this, I have to create my second page content BEFORE the wizard is opened BUT I can't since the second page's content depends on the first page selection.
For now the cleaner solution I found consists in creating all (seconds) pages before the wizard is open (with their content) and override the getNextPage() method in the first page's implementation.
The main drawback of that solution is that it can be be expensive when there are many second pages to create.
What do you think about that solution ? How do you manage your wizard's pages ? Is there any cleaner solution I missed ?
The approach is right if you are several other pages which are
completely different one with another
depends on the previous choices made in a previous page
Then you can add the next page dynamically (also as described here)
But if you have just a next page with a dynamic content, you should be able to create that content in the onEnterPage() method
public void createControl(Composite parent)
{
//
// create the composite to hold the widgets
//
this.composite = new Composite(parent, SWT.NONE);
//
// create the desired layout for this wizard page
//
GridLayout layout = new GridLayout();
layout.numColumns = 4;
this.composite.setLayout(layout);
// set the composite as the control for this page
setControl(this.composite);
}
void onEnterPage()
{
final MacroModel model = ((MacroWizard) getWizard()).model;
String selectedKey = model.selectedKey;
String[] attrs = (String[]) model.macroMap.get(selectedKey);
for (int i = 0; i < attrs.length; i++)
{
String attr = attrs[i];
Label label = new Label(this.composite, SWT.NONE);
label.setText(attr + ":");
new Text(this.composite, SWT.NONE);
}
pack();
}
As shown in the eclipse corner article Creating JFace Wizards:
We can change the order of the wizard pages by overwriting the getNextPage method of any wizard page.Before leaving the page, we save in the model the values chosen by the user. In our example, depending on the choice of travel the user will next see either the page with flights or the page for travelling by car.
public IWizardPage getNextPage(){
saveDataToModel();
if (planeButton.getSelection()) {
PlanePage page = ((HolidayWizard)getWizard()).planePage;
page.onEnterPage();
return page;
}
// Returns the next page depending on the selected button
if (carButton.getSelection()) {
return ((HolidayWizard)getWizard()).carPage;
}
return null;
}
We define a method to do this initialization for the PlanePage, onEnterPage() and we invoke this method when moving to the PlanePage, that is in the getNextPage() method for the first page.
If you want to start a new wizard based on your selection on the first page, you can use the JFace base class org.eclipse.jface.wizard.WizardSelectionPage.
The example below shows a list of available wizards defined by an extension point.
When you press Next, the selected wizard is started.
public class ModelSetupWizardSelectionPage extends WizardSelectionPage {
private ComboViewer providerViewer;
private IConfigurationElement selectedProvider;
public ModelSetupWizardSelectionPage(String pageName) {
super(pageName);
}
private class WizardNode implements IWizardNode {
private IWizard wizard = null;
private IConfigurationElement configurationElement;
public WizardNode(IConfigurationElement c) {
this.configurationElement = c;
}
#Override
public void dispose() {
}
#Override
public Point getExtent() {
return new Point(-1, -1);
}
#Override
public IWizard getWizard() {
if (wizard == null) {
try {
wizard = (IWizard) configurationElement
.createExecutableExtension("wizardClass");
} catch (CoreException e) {
}
}
return wizard;
}
#Override
public boolean isContentCreated() {
// TODO Auto-generated method stub
return wizard != null;
}
}
#Override
public void createControl(Composite parent) {
setTitle("Select model provider");
Composite main = new Composite(parent, SWT.NONE);
GridLayout gd = new GridLayout(2, false);
main.setLayout(gd);
new Label(main, SWT.NONE).setText("Model provider");
Combo providerList = new Combo(main, SWT.NONE);
providerViewer = new ComboViewer(providerList);
providerViewer.setLabelProvider(new LabelProvider() {
#Override
public String getText(Object element) {
if (element instanceof IConfigurationElement) {
IConfigurationElement c = (IConfigurationElement) element;
String result = c.getAttribute("name");
if (result == null || result.length() == 0) {
result = c.getAttribute("class");
}
return result;
}
return super.getText(element);
}
});
providerViewer
.addSelectionChangedListener(new ISelectionChangedListener() {
#Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
if (!selection.isEmpty()
&& selection instanceof IStructuredSelection) {
Object o = ((IStructuredSelection) selection)
.getFirstElement();
if (o instanceof IConfigurationElement) {
selectedProvider = (IConfigurationElement) o;
setMessage(selectedProvider.getAttribute("description"));
setSelectedNode(new WizardNode(selectedProvider));
}
}
}
});
providerViewer.setContentProvider(new ArrayContentProvider());
List<IConfigurationElement> providers = new ArrayList<IConfigurationElement>();
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint extensionPoint = registry
.getExtensionPoint(<your extension point namespace>,<extension point name>);
if (extensionPoint != null) {
IExtension extensions[] = extensionPoint.getExtensions();
for (IExtension extension : extensions) {
IConfigurationElement configurationElements[] = extension
.getConfigurationElements();
for (IConfigurationElement c : configurationElements) {
providers.add(c);
}
}
}
providerViewer.setInput(providers);
setControl(main);
}
The corresponding wizard class looks like this:
public class ModelSetupWizard extends Wizard {
private ModelSetupWizardSelectionPage wizardSelectionPage;
public ModelSetupWizard() {
setForcePreviousAndNextButtons(true);
}
#Override
public boolean performFinish() {
// Do what you have to do to finish the wizard
return true;
}
#Override
public void addPages() {
wizardSelectionPage = new ModelSetupWizardSelectionPage("Select a wizard");
addPage(wizardSelectionPage);
}
}
Another alternative is to #Override setVisible. You can update page values or add additional widgets at that time.
I have a different solution.
If page depends on the result of page 1, create a variable and pass it into to first page, when that wizard page has the option from the user, then the last thing before the page is closed is to set the variable to the required value.
Then pass this variable to wizard, then pass it to the next wizard page. Then do a simple if statement and that way you get both choices together.
Remember that in most code there is only a small difference in the user options, so remember not to get bogged down in duplicating your code.

Categories

Resources