I'm currently writing a CAD-like program for logic circuits (it's my first "graphics intensive" program ever). When I place a component on the schematic, let say an AND gate (which is Region class at its root), I want to be able to interact with it (select it, change its properties, etc). So far, so good. I can click on it and everything go well. However, if I click outside of it, the mouse click event still show the component as it source(!).
Digging a bit further, I put some traces in the mouse click handler and found out that getBoundsInLocal() and getBoundsInParent() return bounds that are around 50% larger than it should be. The getLayoutBounds(), getWidth() and getHeight() do return the correct value.
The pane onto which the components are laid out is a simple Pane object, but it uses setScaleX() and setScaleY() to implement zooming capabilities. I did try to disable them, with no luck.
public abstract class SchematicComponent
extends Region {
private Shape graphicShape = null;
public Shape getGraphicShape() {
if( isShapeDirty() ) {
if( graphicShape != null ) {
getChildren().remove( graphicShape );
}
graphicShape = createShape();
markShapeDirty( false );
if( graphicShape != null ) {
getChildren().add( graphicShape );
}
}
return graphicShape;
}
abstract protected Shape createShape();
}
abstract public class CircuitComponent
extends SchematicComponent {
}
abstract public class LogicGate
extends CircuitComponent {
#Override
protected void layoutChildren() {
super.layoutChildren();
Pin outPin;
final double inputLength = getInputPinsMaxLength();
// Layout the component around its center.
// NOTE: I did try to set the center offset to 0 with no luck.
Point2D centerOffset = getCenterPointOffset().multiply( -1 );
Shape gateShape = getGraphicShape();
if( gateShape != null ) {
gateShape.setLayoutX( centerOffset.getX() + inputLength );
gateShape.setLayoutY( centerOffset.getY() );
}
/* Layout the output pins. */
outPin = getOutputPin();
if( outPin != null ) {
outPin.layout();
outPin.setLayoutX( centerOffset.getX() + getWidth() );
outPin.setLayoutY( centerOffset.getY() + getHeight() / 2 );
}
/* Compute the first input pin location and the gap between each
pins */
double pinGap = 2;
double y;
if( getInputPins().size() == 2 ) {
y = centerOffset.getY() + getHeight() / 2 - 2;
pinGap = 4;
}
else {
y = centerOffset.getY() + ( getHeight() / 2 ) - getInputPins().size() + 1;
}
/* Layout the input pins */
for( Pin inPin : getInputPins() ) {
inPin.layout();
inPin.layoutXProperty().set( centerOffset.getX() );
inPin.layoutYProperty().set( y );
y += pinGap;
}
}
}
// The actual object placed on the schematic
public class AndGate
extends LogicGate {
#Override
protected double computePrefWidth( double height ) {
// NOTE: computeMin/MaxWidth methods call this one
double width = getSymbolWidth() + getInputPinsMaxLength();
double length = 0;
width += length;
if( getOutputPin().getLength() > 0 ) {
width += getOutputPin().getLength();
}
return width; // Always 16
}
#Override
protected double computePrefHeight( double width ) {
// NOTE: computeMin/MaxHeight methods call this one
return getSymbolHeight() + getExtraHeight(); // Always 10
}
#Override
protected Shape createShape() {
Path shape;
final double extraHeight = getExtraHeight();
final double inputLength = getInputPinsMaxLength();
final double outputLength = getOutputPin().getLength();
/* Width and Height of the symbol itself (i,e, excluding the
input/output pins */
final double width = getWidth() - inputLength - outputLength;
final double height = getHeight() - extraHeight;
/* Starting point */
double startX = 0;
double startY = extraHeight / 2;
ArrayList<PathElement> elements = new ArrayList<>();
elements.add( new MoveTo( startX, startY ) );
elements.add( new HLineTo( startX + ( width / 2 ) ) );
elements.add( new ArcTo( ( width / 2 ), // X radius
height / 2, // Y radius
180, // Angle 180°
startX + ( width / 2 ), // X position
startY + height, // Y position
false, // large arc
true ) ); // sweep
elements.add( new HLineTo( startX ) );
if( extraHeight > 0 ) {
/* The height of the input pins is larger than the height of
the shape so we need to add extra bar on top and bottom of
the shape.
*/
elements.add( new MoveTo( startX, 0 ) );
elements.add( new VLineTo( extraHeight + height ) );
}
else {
elements.add( new VLineTo( startY ) );
}
shape = new Path( elements );
shape.setStroke( getPenColor() );
shape.setStrokeWidth( getPenSize() );
shape.setStrokeLineJoin( StrokeLineJoin.ROUND );
shape.setStrokeLineCap( StrokeLineCap.ROUND );
shape.setFillRule( FillRule.NON_ZERO );
shape.setFill( getFillColor() );
return shape;
}
} // End: LogiGate
// SchematicView is the ScrollPane container that handles the events
public class SchematicView
extends ScrollPane {
/* Mouse handler inner class */
private class MouseEventHandler
implements EventHandler<MouseEvent> {
#Override
public void handle( MouseEvent event ) {
if( event.getEventType() == MouseEvent.MOUSE_CLICKED ) {
processMouseClicked( event );
}
else { /* ... more stuff ... */ }
}
private void processMouseClicked( MouseEvent event ) {
Object node = event.getSource();
SchematicSheet sheet = getSheet();
Bounds local = ( (Node) node ).getLayoutBounds();
Bounds local1 = ( (Node) node ).getBoundsInLocal();
Bounds parent = ( (Node) node ).getBoundsInParent();
// At this point, here is what I get:
// node.getHeight() = 10 -> Good
// local.getHeight() = 10 -> Good
// local1.getHeight() = 15.6499996... -> Not expected!
// parent.getHeight() = 15.6500015... -> Not expected!
/*... More stuff ... */
}
}
So at this point, I'm running of clues of what is going on. Where do these getBoundsInXXX() values come from? They doesn't match with the parent's scale values either. The same goes with getWidth(): I get 24.825000... instead of 16.
Looking at this, I understand why clicking outside the component works as if I clicked on it. Its bounds are about 50% bigger than what it should be.
I googled the damn thing and search some doc for almost 2 days now and I'm still baffled. I think I understand that getBoundsInXXX() methods do their own computation but could it be off by that much? I don't thing so. My best guess is that it is something inside the createShape() method but I just can't figure what it is.
Anyone has a clue of what is going on?
Many thanks for your help.
P.S.: This is my first post here, so hopefully I did it right ;)
I think I finally found the problem :)
Basically, the Pin custom shape was drawn in the negative part of X axis (wrong calculations, my bad!). My best guess is that somehow, Java notices that I drew outside the standard bounds and then added the extra space used to the bounds, hence, adding 50% to width, which matches the length of the Pin. Drawing it in the positive region seem to have fixed the problem.
I'm not 100% sure if that is the right answer, but it make sense and it is now working has expected.
Related
I have faced with the problem that scrollIntoView some times wokrs good, some times no (in most cases not good).So I tried to find an alternative and some kind of it was this article.
But in my program i needed also a horizontal scrolling to element and vertical scrolling showing element in the top of parent window.
So I modifed code from article.
/* This is the main function where which pass two ref parameters of Parent element & Child element */
function scrollIntoView(parent, child) {
const parentBounding = parent.getBoundingClientRect(),
clientBounding = child.getBoundingClientRect();
const parentTop = parentBounding.top,
clientTop = clientBounding.top,
parentLeft = parentBounding.left,
clientLeft = clientBounding.left - 400; /* 400 is a shift so that each difference is not nailed to the upper left edge. You can delete it */
if (parentTop >= clientTop) {
scrollTo(parent, -(parentTop - clientTop), 300, 'vertical');
}
else {
scrollTo(parent, clientTop - parentTop, 300, 'vertical');
}
if (parentLeft >= clientLeft) {
scrollTo(parent, -(parentLeft - clientLeft), 300, 'horizontal');
}
else {
scrollTo(parent, clientLeft - parentLeft, 300, 'horizontal');
}
}
function scrollTo(element, to, duration, type) {
let start = (type == 'vertical') ? element.scrollTop : element.scrollLeft,
currentTime = 0,
increment = 20;
let animateScroll = function() {
currentTime += increment;
let val = easeInOutQuad(currentTime, start, to, duration);
if (type == 'vertical') {
element.scrollTop = val;
}
else {
element.scrollLeft = val;
}
if (currentTime < duration) {
setTimeout(animateScroll, increment);
}
}
animateScroll();
}
/* Function for smooth scroll animation with the time duration */
function easeInOutQuad(time, startPos, endPos, duration) {
time /= duration / 2;
if (time < 1) return (endPos / 2) * time * time + startPos;
time--;
return (-endPos / 2) * (time * (time - 2) - 1) + startPos;
}
This feature is experimental (At the time of my comment)
Both scrollIntoView() its scrollIntoViewOption.
I'm trying to get the radius of a Circle Shape after I scaled it. I don't know how to do it. I'm developing a software (with javaFX library) that will let the user add Shapes (Circle, Rectangle, Squares,...)to a Panel, simply "modify" them, save the drawing and load it. I'm managing the saving/loading stuff by cycling through the Nodes the user added (there's a button to create Circle, Rectangle, ... and add it to the Panel), taking the properties I need to load them (centerX, centerY, scaleX, scaleY, ...) and, when button "save" pressed, to save them in a txt file. (stil working on loading, but my thought was to read the previous txt file and cycle it to re-create Shapes on panel in the same position, scale, rotation.)
Here's the code I wrote this is the portion of code that create circle:
public Circle createCircle(double x, double y, double r, Color color) {
Circle circle = new Circle(x, y, r, color);
circle.setCursor(Cursor.HAND);
circle.setOnMousePressed((t) -> {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
Circle c = (Circle) (t.getSource());
c.toFront();
});
circle.setOnMouseDragged((t) -> {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
Circle c = (Circle) (t.getSource());
c.setCenterX(c.getCenterX() + offsetX);
c.setCenterY(c.getCenterY() + offsetY);
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
});
return circle;
}
Here's the code i use to scale it:
public void addMouseScrolling(Node node) {
node.setOnScroll((ScrollEvent event) -> {
// Adjust the zoom factor as per your requirement
double zoomFactor = 1.05;
double deltaY = event.getDeltaY();
if (deltaY < 0){
zoomFactor = 2.0 - zoomFactor;
}
node.setScaleX(node.getScaleX() * zoomFactor);
node.setScaleY(node.getScaleY() * zoomFactor);
});
}
And this is the code of the save button:
Button btn2 = new Button("Save");
btn2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
int i=0;
for (Node node: root.getChildren()) i++;
String stringa_salvataggio[] = new String[i];
String stringa="";
i=0;
String scal_X_s, scal_Y_s, Lyt_X_s, Lyt_Y_s, width_s, height_s;
for (Node node: root.getChildren()){
String stringa_nodo = node.toString();
Double scal_X = node.getScaleX();
scal_X_s = Double.toString(scal_X);
Double scal_Y = node.getScaleY();
scal_Y_s = Double.toString(scal_X);
Double Lyt_X = node.getLayoutX();
Lyt_X_s = Double.toString(scal_X);
Double Lyt_Y = node.getLayoutY();
Lyt_Y_s = Double.toString(scal_X);
Double width = node.getLayoutBounds().getWidth();
width_s = Double.toString(scal_X);
Double height = node.getLayoutBounds().getHeight();
height_s = Double.toString(scal_X);
stringa = stringa + scal_X_s + scal_Y_s + "\n";
stringa_salvataggio[i] = stringa;
System.out.println("-------");
i++;
System.out.println("Nodo " + i + ":");
System.out.println("scalX:" + scal_X);
System.out.println("scalY:" + scal_Y);
System.out.println("LayoutX:" + Lyt_X);
System.out.println("LayoutY:" + Lyt_Y);
System.out.println("Width:" + width);
System.out.println("Height:" + height);
System.out.println(stringa_nodo);
//save on .txt
}
System.out.println(stringa_salvataggio);
}
});
As I said before, I don't know how to get the radius of Circle to recreate it on loading. Rectangles and squares aren't a problem, I can take all infos I need.
Thanks in advance.
I have the following list appearing on UI of an android application which tells about the date and the temperature on that date.
temp degree
11/01 --.-- c
11/02 21.7 c
11/03 22.5 c
Here I want to find the position of string "--.--" and then click on it, so that I can update the list. Is there any way to find the position of string? I know that solo.searchText() will tell whether the string is present or not, but what about the position?
// i think it will help you
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, final View view,
int position, long id) {
// TODO Auto-generated method stub
if(yourliststring.trim().equals("--.--"))
{
//your condition
}
else
{
//your condition
}
}
});
I got a simple way to find and click on the string
if(solo.searchText("--.--")){
solo.clickLongOnText("--.--");
}
All I need is to click on the string "--.--".
I'm not sure what position you need, however if you need position on screen you can simple do:
TextView textView = solo.getText("--.--"); // or solo.getText(Pattern.quote("--.--")); if needed
int[] xy = new int[2];
textView.getLocationOnScreen(xy);
final int viewWidth = textView.getWidth();
final int viewHeight = textView.getHeight();
final float x = xy[0] + (viewWidth / 2.0f);
final float y = xy[1] + (viewHeight / 2.0f);
this way you have central point of view (x, y).
If you need position of text in list, you can do:
private int getPosition(ListView listView, String text) {
for (int i = 0; i < listView.getAdapter().getCount(); i++) {
Object o = listView.getAdapter.getItemAtPosition(i);
if (o instanceof TextView) {
if (((TextView)o).getText().toString().equals(text)) {
return i;
}
}
}
return -1; // not found
}
you can call it for instance this way (set proper parameters):
int position = getPosition((ListView) solo.getView(ListView.class, 0), "--.--");
Everything written without testing and even compiling, however main idea should be fine.
Clutter 1.12
Cogl 1.10
Vala or C or Python.
I might have a fundamental misunderstanding here —
I think of "Actors" as 3D polygon things. I think of their colours as either vertex colors or as texture-mapping. In this light, I have been trying to draw a custom Actor and fill it with stuff drawn via Cairo. I'm not getting anywhere.
Code is included below (in Vala). Can anyone set me right about Clutter's basics (the docs just don't cut it) or, if I'm close, help me get that code working.
I expect to see a rounded rectangle with a smiley face within. What I'm seeing instead is the Cogl path fill covering* the face. I think the paint() is being done after the drawme()
*If you set the Clutter.Color to "#0001" in method paint(), you'll see this.
/*
Clutter Actor with custom paint() and filled with a Cairo-drawn texture.
(NOT yet working.)
Compile with:
valac \
--pkg clutter-1.0 \
--pkg cogl-1.0 \
somename.vala
*/
public class SmileyRect : Clutter.Actor {
//private vars
private Clutter.Canvas _canvas;
private bool _flip = false;
private int _w = 300;
private int _h = 300;
//Constructor
construct {
_canvas = new Clutter.Canvas();
_canvas.set_size( this._w, this._h );
this.set_size( this._w, this._h );
this.set_content( _canvas );
//Connect to the draw signal - this is as-per Clutter docs.
_canvas.draw.connect( drawme );
//Make it reactive and connect to the button-press-event
this.set_reactive( true );
this.button_press_event.connect( button_press );
}
/*
Button press signal handler.
Changes the colour of what will be painted on the canvas.
*/
private bool button_press ( Clutter.ButtonEvent evt ) {
this._flip = !this._flip; //Jiggle a value.
this.redraw(); // Forces re-run of the drawme() method.
return true; //all done with this signal.
}
//Common function to draw Cogl stuff - used in paint and pick.
private void draw_rr( Clutter.Color? color ) {
if (color != null ) { Cogl.set_source_color4ub(color.red,color.green,color.blue,color.alpha); }
Cogl.Path.round_rectangle( 0, 0, this._w, this._h, 15, 0.3f );
Cogl.Path.close();
// When from paint():
// Is there some way to fill this Cogl path with the contents
// of this._canvas? Or some other paradigm?
if (color != null ) { Cogl.Path.fill(); }
}
/* Some kind of freaky, this magic paint() thing.
Took me ages to get it running.
I want to draw a rounded rectangle as the basic shape
of this actor.
*/
public override void paint() {
stdout.printf("paint runs.\n");
// I did try a transparent color #0000 - just to see. No go.
// #000f - draws a black rounded rect *OVER* the Cairo canvas. It covers
// the smiley face.
this.draw_rr( Clutter.Color.from_string("#0000") );
}
/* I followed paint() example, but the argument was tricky.
I eventually found it from some PyClutter source code.
*/
public override void pick(Clutter.Color color) {
stdout.printf("pick runs.\n");
this.draw_rr( color );
}
/*
Draws the Cairo art to the canvas.
I want this art to be the bitmap/pixmap that *fills* the
basic rounded rectangle shape of this actor.
i.e I want the smile face cairo rectangle to be *within* the
polygon that is draw via Cogl in paint() method.
Does this even make sense?
*/
private bool drawme( Cairo.Context ctx, int width, int height) {
//Draw a rectangle
double[] col;
if (this._flip) {
col = {1f,0f,0f};
}
else {
col = {0f,1f,0f};
}
ctx.set_source_rgb( col[0], col[1], col[2] );
ctx.rectangle( 0, 0, width, height );
ctx.fill();
//Draw a smiley face.
// Aside: Been trying to avoid all the weird casting of the floats/doubles used below
// (see the generated c source.) Not at all sure what's going on there.
// Also not sure about that 'f' on the numbers. Where/when do I have to use it?
double pi = 3.14f;
double w = (double) width;
double h = (double) height;
ctx.set_line_width( 6f );
ctx.set_source_rgb( 0f, 0f, 0.8f );
//eyes
ctx.move_to( w/3f, h/3f );
ctx.rel_line_to( 0f, h/6f );
ctx.move_to( 2*(w/3f), h/3f );
ctx.rel_line_to( 0f, h/6f );
ctx.stroke();
ctx.set_source_rgb( 1f, 1f, 0f );
double rad = (w > h) ? h : w;
//face
ctx.arc( w/2f, h/2f, (rad/2f) - 20f,0f,2f * pi );
ctx.stroke();
//smile
ctx.arc( w/2f, h/2f, (rad/3f) -10f, pi/3f, 2f * (pi/3f) );
ctx.stroke();
return true;
}
//Redraw - forces invalidate which trips the draw event
public void redraw() {
this._canvas.invalidate();
}
} //end SmileyRect class
void main( string[] args ) {
Clutter.init(ref args);
var stage = new Clutter.Stage();
stage.set_size(400,400);
var rs = new SmileyRect();
stage.add_child(rs);
rs.redraw();
stage.destroy.connect(Clutter.main_quit);
stage.show();
Clutter.main();
}
Can the width of the tab-header of a CTabItem be set arbitrary?
Thnx in advance!
Kind regard,
Marcus
You can try to override CTabItem, but this solution doesn't look as nice solution (nevertheless I use it):
CTabItem tabItem;
final int tabWidth = 90;
tabItem = new CTabItem( tabFolder, SWT.NONE ) {
#Override
public Rectangle getBounds( ) {
Rectangle bounds = super.getBounds();
bounds.x = 0 * tabWidth;
bounds.width = tabWidth;
return bounds;
}
};
tabItem.setText( __lang.str( this, "Tab Item I" ) );
tabItem = new CTabItem( tabFolder, SWT.NONE ) {
#Override
public Rectangle getBounds( ) {
Rectangle bounds = super.getBounds();
bounds.x = 1 * tabWidth;
bounds.width = tabWidth;
return bounds;
}
};
tabItem.setText( __lang.str( this, "Tab Item II" ) );
tabItem = new CTabItem( tabFolder, SWT.NONE ) {
#Override
public Rectangle getBounds( ) {
Rectangle bounds = super.getBounds();
bounds.x = 2 * tabWidth;
bounds.width = tabWidth;
return bounds;
}
};
tabItem.setText( __lang.str( this, "Tab Item III" ) );
No you can't do that. Not at least by any exposed API (read public methods).
A possible solution is to extend the code of CTabFolder and CTabItem.
As a workaround, you can pad the title with extra spaces
using String.format like:
cTabItem.setText(String.format("%-15s", orgTitle));
This will add extra spaces to the end of the string if its length is less than 15 characters.
Check this for more details:
https://docs.oracle.com/javase/tutorial/essential/io/formatting.html