I have a problem with rendering gutter icons, in my IntelliJ idea plugin project.
I want to render gutter icon next to a line number, but it renders only small rectangle. Gutter icon path is loaded properly and gutter icon size is 12x12px, format png. Can you help me?
My code:
public static void addLineHighlight(Document document, int lineNumber,
String text) {
Icon highlightIcon = IconLoader.getIcon("META-INF/fail.png");
addGutterIcon(getRangeHighlighter(document, lineNumber), highlightIcon, text);
}
#NotNull
private static RangeHighlighter getRangeHighlighter(Document document, int lineNumber) {
MarkupModel markupModel = getMarkupModel(document);
TextAttributes textAttributes = getTextAttributes();
RangeHighlighter highlighter;
highlighter = markupModel.addLineHighlighter(lineNumber, 66 , textAttributes);
return highlighter;
}
private static void addGutterIcon(#NotNull RangeHighlighter highlighter, Icon icon, String text) {
highlighter.setGutterIconRenderer(new GutterIconRenderer() {
#Override
public boolean equals(Object obj) {
return false;
}
#Override
public int hashCode() {
return 0;
}
#NotNull
#Override
public Icon getIcon() {
return icon;
}
});
}
private static MarkupModel getMarkupModel(Document document) {
return DocumentMarkupModel.forDocument(document, TestSingleton.getInstance().getProject(), true);
}
#NotNull
private static TextAttributes getTextAttributes() {
TextAttributes textAttributes = null;
textAttributes = new TextAttributes();
textAttributes.setBackgroundColor(JBColor.RED);
textAttributes.setErrorStripeColor(JBColor.RED);
return textAttributes;
}
}
I believe you should be using com.intellij.codeInsight.daemon.LineMarkerProvider.
See this post and this example.
We have a large Java app that is used on both Windows and OSX.
We do custom Drag and Drop between 2 of our JTables.
On Windows, this works perfectly. The custom cursor is displayed as you drag over the target JTable.
On the Mac, the custom cursor is never displayed. Instead a gray rectangle (border only) is displayed when you start dragging. This rectangle is the width of the table column, and the height of the table. Our logging is showing that the dragOver() and dropActionChanged() methods are getting called, and we are setting the custom cursor. It just never gets displayed.
If I disable our custom cursor code, the same box is displayed - but it has the Circle/slash icon in the middle as well.
I want to get rid of the weird box, and display the custom cursor.
Excerpts from the code:
private class FileTransferHandler extends TransferHandler {
private static final long serialVersionUID = 1L;
private final Logger log = LogManager.getLogger();
private final CursorDragSourceListener dragSourceListener = new CursorDragSourceListener();
// Left out the Drop handling code that was here
#Override
public int getSourceActions( final JComponent c) {
log.debug("FileTransferHandler.getSourceAction: ");
return COPY | MOVE;
}
#Override
protected Transferable createTransferable( final JComponent c) {
log.debug("FileTransferHandler.createTransferable:");
List<iFilePage> pages = new ArrayList<iFilePage>();
// Left out the code that builds the pages list
DragSource.getDefaultDragSource().addDragSourceListener(dragSourceListener);
dragSourceListener.setCursorChoice(pages.size() == 1);
return new FilePageTransferable(pages);
}
#Override
protected void exportDone( final JComponent c,
final Transferable t,
final int action) {
log.debug("FileTransferHandler.exportDone: {}", action, t);
tblFixed.getSelectionModel().clearSelection();
DragSource.getDefaultDragSource().removeDragSourceListener(dragSourceListener);
return;
}
}
private static class CursorDragSourceListener implements DragSourceListener {
private Cursor singlePage = null;
private Cursor multiPage = null;
private Cursor badSinglePage = null;
private Cursor useCursor = null;
private boolean useSingle = false;
public CursorDragSourceListener() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
URL url;
String name;
Image img;
url = FileUtils.getResourceURL("/images/page.png");
name = "DragPage";
img = toolkit.createImage(url);
singlePage = toolkit.createCustomCursor(img, new Point(16, 16), name);
url = FileUtils.getResourceURL("/images/badpage_stack.png");
name = "DragBadPage";
img = toolkit.createImage(url);
badSinglePage = toolkit.createCustomCursor(img, new Point(16, 16), name);
url = FileUtils.getResourceURL("/images/page_stack.png");
name = "DragPageStack";
img = toolkit.createImage(url);
multiPage = toolkit.createCustomCursor(img, new Point(16, 16), name);
return;
}
public void setCursorChoice( final boolean single) {
log.debug("CursorDragSourceListener.setCursorChoice: {}", single);
useSingle = single;
if (useSingle) {
useCursor = singlePage;
} else {
useCursor = multiPage;
}
return;
}
#Override
public void dropActionChanged( final DragSourceDragEvent dsde) {
log.debug("CursorDragSourceListener.dropActionChanged: {}", dsde.getDropAction(), useSingle);
if (dsde.getDropAction() == 2) {
if (!useSingle) {
useCursor = badSinglePage;
} else {
useCursor = singlePage;
}
} else {
if (useSingle) {
useCursor = singlePage;
} else {
useCursor = multiPage;
}
}
dsde.getDragSourceContext().setCursor(useCursor);
return;
}
#Override
public void dragOver( final DragSourceDragEvent dsde) {
try {
Object x = dsde.getDragSourceContext().getTransferable()
.getTransferData(DataFlavor.javaFileListFlavor);
log.trace("CursorDragSourceListener.dragOver: {}", (x != null) ? x.getClass().getSimpleName() : "null");
if (x instanceof ArrayList) {
dsde.getDragSourceContext().setCursor(useCursor);
}
} catch (Exception e) {
log.error("CursorDragSourceListener.dragOver:", e);
}
}
#Override
public void dragExit( final DragSourceEvent dse) {
}
#Override
public void dragEnter( final DragSourceDragEvent dsde) {
}
#Override
public void dragDropEnd( final DragSourceDropEvent dsde) {
}
}
After a bunch more checking and analysis, it turns out that our Custom Selection Model was causing this problem on OSX.
We have a selection model that allows you to select multiple individual cells, not just whole rows.
So the getMinSelectionindex() and getMaxSelectionIndex() methods returned dummy data, since we never used them.
That works fine on MS Win, but apparently the OSX drag and drop for JTable uses those calls.
After modifying our code to return reasonable values, the selection box is no longer as tall as the table.
The custom cursors appear most of the time, but still randomly disappear for no apparent reason.
I have a TableView with 4 rows and for each one a custom cell. The first one is just an image, which one I want to update a few times, a string, a string with word wrap and an second image as a button.
// image
tcUrlStatus.setCellValueFactory(new ColumnImageFactory());
tcUrlStatus.setCellFactory(new ColumnCallback());
// one line string
tcUrlName.setCellValueFactory(new ColumnNameFactory());
tcUrlName.setCellFactory(new ColumnCallback());
// two line string
tcUrlDate.setCellValueFactory(new ColumnDateFactory());
tcUrlDate.setCellFactory(new ColumnCallback());
// image as a button
tcDelete.setCellValueFactory(new ColumnDeleteFactory());
tcDelete.setCellFactory(new ColumnCallback());
And here are the custom cells
class ColumnCallback implements Callback<TableColumn<Quartet<Boolean, String, String, String>, Object>, TableCell<Quartet<Boolean, String, String, String>, Object>>{
#Override
public TableCell<Quartet<Boolean, String, String, String>, Object> call(TableColumn<Quartet<Boolean, String, String, String>, Object> column) {
return new ColumnCell();
}
}
class ColumnImageFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Object, String, String, String>, String>, ObservableValue<Object>> {
#Override
public ObservableValue<Object> call(TableColumn.CellDataFeatures<Quartet<Object, String, String, String>, String> data) {
return new ReadOnlyObjectWrapper<>(data.getValue().getValue0());
}
}
class ColumnNameFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, String>, ObservableValue<String>> {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, String> data) {
return new ReadOnlyObjectWrapper<>(data.getValue().getValue1());
}
}
class ColumnDateFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, Object>, ObservableValue<Object>> {
#Override
public ObservableValue<Object> call(TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, Object> data) {
return new ReadOnlyObjectWrapper<>(data.getValue().getValue2());
}
}
class ColumnDeleteFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, Object>, ObservableValue<Object>> {
#Override
public ObservableValue<Object> call(TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, Object> data) {
return new ReadOnlyObjectWrapper<>(data.getValue().getValue3());
}
}
class ColumnCell extends TableCell<Quartet<Boolean, String, String, String>, Object> {
#Override
protected void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
if (item instanceof Boolean) {
setText(null);
Image image;
AnchorPane pane = new AnchorPane();
if ((boolean) item) {
image = new Image(Main.class.getResourceAsStream("/hourglass.gif"));
} else {
image = new Image(Main.class.getResourceAsStream("/clean.gif"));
}
ImageView imageView = new ImageView(image);
imageView.setFitWidth(30);
imageView.setY(5);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
pane.getChildren().add(imageView);
setGraphic(pane);
}else {
if (item instanceof String) {
if (item.equals("delete")) {
AnchorPane pane = new AnchorPane();
Image image = new Image(Main.class.getResourceAsStream("/cross.png"));
ImageView imageView = new ImageView(image);
imageView.setFitWidth(20);
imageView.setY(10);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
imageView.setCursor(Cursor.HAND);
pane.getChildren().add(imageView);
imageView.setOnMouseClicked((Event event) -> {
Quartet<Boolean, String, String, String> row = this.getTableView().getSelectionModel().getSelectedItem();
Controller.localJson.remove(row.getValue1());
this.getTableView().getItems().remove(row);
});
setGraphic(pane);
} else {
HBox pane = new HBox();
Label label = new Label();
label.setText((String) item);
label.setTextAlignment(TextAlignment.CENTER);
if (((String) item).length() < 20) {
label.setWrapText(true);
label.setAlignment(Pos.CENTER);
}
pane.setPrefHeight(40);
label.setPrefHeight(40);
pane.getChildren().add(label);
setGraphic(pane);
}
setText(null);
}
}
} else {
setText(null);
setGraphic(null);
}
}
}
Now I want to change the Image of the tcUrlStatus column/cell. I already figured out how to get the row or the value of it, but I can't figure out how to set the value or rather change from true to false or false to true for updating the image.
get row and value:
private int getTableRowIndex(String url){ // url is the second column
int counter = 0;
for (Object row:tvUrls.getItems()){
if ((((Quartet) row).getValue1() == url)){
return counter;
}
counter ++;
}
return -1;
}
int rowIndex = getTableRowIndex(url);
Object item = tvUrls.getItems().get(rowIndex);
It would be easiest to do this by using javafx properties in your Quartet class, e.g. assuming the type used for the value0 bean is T:
// TODO: Is using a readonly property really needed here ???
// if not, use SimpleObjectProperty instead
private final ReadOnlyObjectWrapper<T> value0 = new ReadOnlyObjectWrapper<>();
public T getValue0() {
return value0.get();
}
// TODO: should the setter really be public ???
public void setValue0(T newValue) {
value0.set(newValue);
}
public ReadOnlyObjectProperty<T> value0Property() {
return value0.getReadOnlyProperty();
}
Which allows you to use new PropertyValueFactory<>("value0") instead of your custom cell value factory, and, even more important, means the TableView components will be notified of changes in the Quartet instances.
This way you can simply use
quartetInstance.setValue0(newValue0);
and the cell will get updated.
Using the PropertyValueFactory would have the same effect as using the following cellValueFactory in this case:
class ColumnImageFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Object, String, String, String>, String>, ObservableValue<Object>> {
#Override
public ObservableValue<Object> call(TableColumn.CellDataFeatures<Quartet<Object, String, String, String>, String> data) {
return data.getValue().value0Property();
}
}
If you cannot add javaFX properties to the Quartet class you need some other way of updating the TableView after the change. TableView.refresh() would work (provided you use JavaFX version >= 8u60) or writing an adapter in case you have implemented the observer patten in the Quartet class some other way...
I solved it with chaning from the Quartet Class to the SimpleObjectProperty Class.
Init:
tcUrlStatus.setCellValueFactory(new PropertyValueFactory<ColumnCellValue, Boolean>("status"));
tcUrlName.setCellValueFactory(new PropertyValueFactory<ColumnCellValue, String>("url"));
tcUrlDate.setCellValueFactory(new PropertyValueFactory<ColumnCellValue, String>("date"));
tcDelete.setCellValueFactory(new PropertyValueFactory<ColumnCellValue, Boolean>("delete"));
tcUrlStatus.setCellFactory(new ColumnStatusCell());
tcUrlName.setCellFactory(new ColumnStringCell(false));
tcUrlDate.setCellFactory(new ColumnStringCell(true));
tcDelete.setCellFactory(new ColumnDeleteCell());
Adding rows:
tvUrls.getItems().add(new ColumnCellValue(true, url, date));
Updating cells:
ColumnCellValue statusRow = (ColumnCellValue)
tvUrls.getItems().get(rowIndex);
Column classes:
public class ColumnCellValue{
private final ObjectProperty status;
private final ObjectProperty url;
private final ObjectProperty date;
private final ObjectProperty delete = new SimpleObjectProperty<Boolean>(true);
ColumnCellValue(Boolean status, String url, String date) {
this.status = new SimpleObjectProperty<Boolean>(status);
this.url = new SimpleObjectProperty<String>(url);
this.date = new SimpleObjectProperty<String>(date);
}
public Object getDate() {
return date.get();
}
public ObjectProperty dateProperty() {
return date;
}
public void setDate(Object date) {
this.date.set(date);
}
public Object getDelete() {
return delete.get();
}
public ObjectProperty deleteProperty() {
return delete;
}
public void setDelete(Object delete) {
this.delete.set(delete);
}
public Object getStatus() {
return status.get();
}
public ObjectProperty statusProperty() {
return status;
}
public void setStatus(Object status) {
this.status.set(status);
}
public Object getUrl() {
return url.get();
}
public ObjectProperty urlProperty() {
return url;
}
public void setUrl(Object url) {
this.url.set(url);
}
}
class ColumnStatusCell implements Callback<TableColumn<Boolean, Boolean>,TableCell<Boolean, Boolean>>{
#Override
public TableCell<Boolean, Boolean> call(TableColumn<Boolean, Boolean> param) {
AnchorPane pane = new AnchorPane();
ImageView imageView = new ImageView();
imageView.setFitWidth(30);
imageView.setY(5);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
TableCell<Boolean,Boolean> cell = new TableCell<Boolean,Boolean>(){
public void updateItem(Boolean item, boolean empty) {
if(item!=null){
Image image;
if (item) {
image = new Image(Main.class.getResourceAsStream("/hourglass.gif"));
} else {
image = new Image(Main.class.getResourceAsStream("/clean.gif"));
}
imageView.setImage(image);
}
}
};
pane.getChildren().add(imageView);
cell.setGraphic(pane);
return cell;
}
}
class ColumnStringCell implements Callback<TableColumn<String, String>,TableCell<String, String>>{
private boolean wrap = false;
ColumnStringCell(boolean wrap){
this.wrap = wrap;
}
#Override
public TableCell<String, String> call(TableColumn<String, String> param) {
TableCell<String,String> cell = new TableCell<String,String>(){
public void updateItem(String item, boolean empty) {
if(item!=null){
Label label = new Label();
label.setText(item);
label.setPrefHeight(40);
label.setTextAlignment(TextAlignment.CENTER);
label.setWrapText(wrap);
setGraphic(label);
}
}
};
return cell;
}
}
class ColumnDeleteCell implements Callback<TableColumn<Boolean, Boolean>,TableCell<Boolean, Boolean>>{
#Override
public TableCell<Boolean, Boolean> call(TableColumn<Boolean, Boolean> param) {
AnchorPane pane = new AnchorPane();
ImageView imageView = new ImageView();
imageView.setFitWidth(20);
imageView.setY(10);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
imageView.setCursor(Cursor.HAND);
TableCell<Boolean,Boolean> cell = new TableCell<Boolean,Boolean>(){
public void updateItem(Boolean item, boolean empty) {
if (item != null) {
Image image = new Image(Main.class.getResourceAsStream("/cross.png"));
imageView.setImage(image);
}
}
};
imageView.setOnMouseClicked((Event event) -> {
TableView table = (TableView) ((ImageView) event.getSource()).getParent().getParent().getParent().getParent().getParent().getParent().getParent();
ColumnCellValue row = (ColumnCellValue) (table).getSelectionModel().getSelectedItem();
Controller.localJson.remove(row.getUrl().toString());
table.getItems().remove(row);
table.refresh();
});
pane.getChildren().add(imageView);
cell.setGraphic(pane);
return cell;
}
}
Is there a default way to allow the users deselect a selected row?
If not, I want to make the row to be deselected on another click. How can I achieve it?
In the bellow I attached the code piece for my tree grid tg
// ---------------------- set up columns ------------------------ \\
// set up name column
ColumnConfig<ProductMapping, String> cc1 = new ColumnConfig<ProductMapping, String>(new ValueProvider<ProductMapping, String>(){
#Override
public String getValue(ProductMapping object) {
return object.getName();
}
#Override
public void setValue(ProductMapping object, String value) {
object.setName(value);
}
#Override
public String getPath() {
return "name";
}
});
cc1.setWidth(200);
cc1.setHeader("Name");
// setup solution column
ColumnConfig<ProductMapping, String> cc2 = new ColumnConfig<ProductMapping, String>(new ValueProvider<ProductMapping, String>() {
#Override
public String getValue(ProductMapping object) {
return object.getSolution();
}
#Override
public void setValue(ProductMapping object, String value) {
object.setSolution(value);
}
#Override
public String getPath() {
return "solution";
}
});
cc2.setHeader("Solution");
cc2.setWidth(200);
// setup condition column
ColumnConfig<ProductMapping, String> cc3 = new ColumnConfig<ProductMapping, String>(new ValueProvider<ProductMapping, String>() {
#Override
public String getValue(ProductMapping object) {
return object.getCondition();
}
#Override
public void setValue(ProductMapping object, String value) {
object.setCondition(value);
}
#Override
public String getPath() {
return "condition";
}
});
cc3.setHeader("Condition");
cc3.setWidth(200);
// create column model
List<ColumnConfig<ProductMapping,?>> ccl = new LinkedList<ColumnConfig<ProductMapping,?>>();
ccl.add( cc1);
ccl.add( cc2);
ccl.add( cc3);
ColumnModel<ProductMapping> cm = new ColumnModel<ProductMapping>(ccl);
// Create the tree grid using the store, column model and column config for the tree column
tg = new TreeGrid<ProductMapping>(treeStore, cm, ccl.get(0));
// tg.getSelectionModel().get
tg.addRowClickHandler(new RowClickEvent.RowClickHandler(){
#Override
public void onRowClick(RowClickEvent event) {
tgRowClicked(event);
}
});
I have a textual editor that extends AbstractTextEditor and I also have an Outline that needs to be saved when its content is modified by the user. I am currently using a Saveable which is added to the editor.
If the editor was marked as 'dirty' and it is saved, the Saveableis saved as well. However, if the Saveable's state changes to 'dirty', the * next to the file name does not appear. The save button in the top menu bar does show, but when I click it, nothing happens.
This is my implementation:
public class MyTextEditor extends AbstractTextEditor {
...
public void setOutlineSaveable(Saveable saveable) {
this.outlineSaveable = saveable;
ISaveablesLifecycleListener lifecycleListener = (ISaveablesLifecycleListener)getSite().getService(ISaveablesLifecycleListener.class);
lifecycleListener.handleLifecycleEvent( new SaveablesLifecycleEvent(this, SaveablesLifecycleEvent.POST_OPEN, new Saveable[] {saveable}, false));
}
#Override
public Saveable[] getSaveables() {
if(outlineSaveable != null) {
// copy Saveables from super.getSaveables() to a new array
Saveable[] superSaveables = super.getSaveables();
Saveable[] res = new Saveable[superSaveables.length + 1];
int i = 0;
for(; i < superSaveables.length; i++) {
res[i] = superSaveables[i];
}
res[i] = outlineSaveable;
return res;
}
else
return super.getSaveables();
}
public void saveableDirty() {
firePropertyChange(PROP_DIRTY);
}
}
My ContentOutlinePage:
public class GraphicalOutlinePage extends ContentOutlinePage {
...
private GraphicalOutlineSaveable saveable;
public Saveable getSaveable() {
return saveable;
}
class GraphicalOutlineSaveable extends Saveable {
private boolean dirty = false;
private IEditorPart editor;
public GraphicalOutlineSaveable(IEditorPart editor) {
this.editor = editor;
}
#Override
public void doSave(IProgressMonitor monitor) throws CoreException {
viewer.doSave(monitor);
dirty = false;
}
#Override
public boolean equals(Object obj) {
System.err.println("GraphicalOutline.GraphicalOutlineSaveable.equals");
return obj instanceof GraphicalOutlineSaveable && ((Saveable)obj).getName() == getName();
}
#Override
public ImageDescriptor getImageDescriptor() {
return editor.getEditorInput().getImageDescriptor();
}
#Override
public String getName() {
return "Graphical Outline: " + editor.getEditorInput().getName();
}
#Override
public String getToolTipText() {
return "";
}
#Override
public boolean isDirty() {
System.err.println("GraphicalOutlinePage.GraphicalOutlineSaveable.isDirty: " + dirty);
return dirty;
}
public void setDirty() {
System.err.println("GraphicalOutlinePage.GraphicalOutlineSaveable.setDirty");
dirty = true;
// notify text editor about property change
if(editor instanceof AbstractTextEditor) {
((MyTextEditor)editor).saveableDirty();
}
}
#Override
public int hashCode() {
return viewer.hashCode();
}
}
}
vieweris a GraphicalViewerdisplayed in the ContentOutlinePage.
Somewhere in another class, I then call:
textEditor.setSaveable(grOutlinePage.getSaveable());
You may need to override the main editor isDirty() method and test each of the Saveable objects dirty flags.
It seems that the handling of multiple Saveables is not done as cleanly is it might have been.