In a GTK# application, I am using a TreeView inside a ScrolledWindow, and try to find the position of an item inside the TreeStore to draw lines starting from that item in Cairo. For now, I am using a hard coded value for cell height, which works for few items, but when other font sizes are used or the item list gets longer, there is a visible offset. Is there a method to get the cell height of a TreeView, or another way to get the vertical position of an item in the TreeStore?
Here is the method, that I currently use:
int GetYPositionForPort (TreeView tree, TreeStore store, Port selectedPort)
{
// Is there a way to get the cell height?
int cellHeight = 24;
// We start in the middle of the first Treeview item
int position = cellHeight / 2;
ScrolledWindow treeParent = tree.Parent as ScrolledWindow;
if (treeParent != null) {
position -= Convert.ToInt32 (treeParent.Vadjustment.Value);
}
TreeIter clientIter;
TreeIter portIter;
if (store.GetIterFirst (out clientIter)) {
do {
if (store.IterHasChild (clientIter)
&& tree.GetRowExpanded (store.GetPath (clientIter))) {
if (store.IterChildren (out portIter, clientIter)) {
do {
position += cellHeight;
} while (((Port)store.GetValue(portIter, 0) != selectedPort
|| (Client)store.GetValue(clientIter, 0) != selectedPort.Client)
&& store.IterNext(ref portIter));
}
}
//Necessary because the first Treeview item only counts as 1/2 cell height.
if (((Client)store.GetValue (clientIter, 0)) == selectedPort.Client) {
break;
}
position += cellHeight;
} while (store.IterNext(ref clientIter));
}
return position;
}
You can see the error on the bottom of the screenshot where the lines are not aligned with the items in the TreeView:
Reading the documentation, I have come up with the following ugly piece of code:
int GetCellHeight (TreeView tree)
{
if (_cellHeight > 0) {
return _cellHeight;
}
int offsetX;
int offsetY;
int cellWidth;
Gdk.Rectangle rectangle = new Gdk.Rectangle ();
TreeViewColumn column = tree.GetColumn (0);
// Getting dimensions from TreeViewColumn
column.CellGetSize(rectangle, out offsetX, out offsetY,
out cellWidth, out _cellHeight);
// And now get padding from CellRenderer
CellRenderer renderer = column.CellRenderers[0];
_cellHeight += (int)renderer.Ypad;
return _cellHeight;
}
This is what it looks like now:
Related
I just made my image generator work with PNG files. For now, it's divided into 3 categories (backgrounds, objects & texts). These are now all combined, and with every mouse click it randomises these PNGs.
I made three toggles, where you could to choose to show either the background and the objects on top, all of them, or all separate. Whenever I run the sketch, it shows the "grey" background, but when I use the toggles, it doesn't show anything, or shows a flickering image, where the mouse-click can't be used to go to the next image. I can't seem to find the problem. Hopefully, you can help. :)
import controlP5.*;
boolean showBackground = false;
boolean showObjects = false;
boolean showGrids = false;
ControlP5 cp5;
PImage[] myImageArray = new PImage[8];
PImage[] myImageArray2 = new PImage[15];
PImage[] myImageArray3 = new PImage[15];
void setup() {
size(1436, 847);
background(211, 211, 211);
for (int i=0; i<myImageArray.length; i++) {
myImageArray[i] = loadImage ( "o" + i + ".png");
myImageArray2[i] = loadImage ( "g" + i + ".png");
myImageArray3[i] = loadImage( "b" + i + ".jpg");
cp5 = new ControlP5(this);
// create a toggle and change the default look to a (on/off) switch look
cp5.addToggle("showBackground")
.setPosition(40, 250)
.setSize(50, 20)
.setValue(true)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showObjects")
.setPosition(40, 400)
.setSize(50, 20)
.setValue(true)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showGrid")
.setPosition(40, 600)
.setSize(50, 20)
.setValue(true)
.setMode(ControlP5.SWITCH);
}
display();
}
void display() {
image(myImageArray3[(int)random(myImageArray.length)], 0, 0, 1436, 847); // b
image(myImageArray2[(int)random(myImageArray.length)], 0, 0, 1436, 847); // g
image(myImageArray[(int)random(myImageArray.length)], 0, 0, 1436, 847); // o
}
void mousePressed() {
display();
}
void draw() {
pushMatrix();
if (showBackground==false) {
image(myImageArray3[(int)random(myImageArray.length)], 0, 0, 1436, 847); // b
} else {
background(211, 211, 211);
}
if (showGrids==false) {
image(myImageArray2[(int)random(myImageArray.length)], 0, 0, 1436, 847); // g
} else {
background(211, 211, 211);
}
if (showObjects==false) {
image(myImageArray[(int)random(myImageArray.length)], 0, 0, 1436, 847); // o
} else {
background(211, 211, 211);
}
popMatrix();
}
Here are a couple of things where the logic your wrote in your code might not match what you had in mind:
When you call display() on mouse it renders those 3 images once (also it will be different images within those since it's using a randomised index). Similarly in draw(), when an does get picked to be rendered, frames will be flickering fast as a random index is generated multiple times per second(each frame). You may want to randomise indices in a different event (e.g. mouse or key press) and store this value in a variable you can re-use.
the conditions you use in draw(): you probably meant to check if the values are true(toggled enabled/turned on in controlP5) ? (e.g. e.g. if (showBackground==true) and initialise the toggles with false, instead of true?)
a big one: in draw() , after each condition(showBackground,showGrids,showObjects), if it's false, you're clearing the the whole frame (so a previous image would be erased)
you have 3 arrays, but you use the size of the first(myImageArray.length) only, which means, even though you may have more images for myImageArray2 and myImageArray3, you're not loading, nor displaying them.
The third grid is labeled "showGrid" when it should be "showGrids": if you aren't consistent with the toggle labels and variable names, toggles won't update the variable names.
you should use more descriptive names for the arrays: it will make it easier to scan/follow your code on the long run.
there's no need to add toggles multiple times in the for loop where you load images: once will do.
Here's what I mean:
import controlP5.*;
boolean showBackground = false;
boolean showObjects = false;
boolean showGrids = false;
ControlP5 cp5;
PImage[] objects = new PImage[8];
PImage[] grids = new PImage[15];
PImage[] backgrounds = new PImage[15];
int currentImage = 0;
void setup() {
size(1436, 847);
//load objects
for (int i=0; i<objects.length; i++) {
objects[i] = loadImage ( "o" + i + ".png");
}
//load grids
for(int i = 0 ; i < grids.length; i++){
grids[i] = loadImage ( "g" + i + ".png");
}
//load backgrounds
for(int i = 0 ; i < grids.length; i++){
backgrounds[i] = loadImage( "b" + i + ".jpg");
}
//setup UI
cp5 = new ControlP5(this);
// create a toggle and change the default look to a (on/off) switch look
cp5.addToggle("showBackground")
.setPosition(40, 250)
.setSize(50, 20)
.setValue(false)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showObjects")
.setPosition(40, 400)
.setSize(50, 20)
.setValue(false)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showGrids")
.setPosition(40, 600)
.setSize(50, 20)
.setValue(false)
.setMode(ControlP5.SWITCH);
}
void mousePressed() {
//go to next image index
currentImage = currentImage + 1;
//check if the incremented index is still valid, otherwise, reset it to 0 (so it doesn't go out of bounds)
if (currentImage >= objects.length) {
currentImage = 0;
}
}
void draw() {
//clear current frame
background(211);//for gray scale value you can just use one value: the brightness level :)
if (showBackground==true) {
image(backgrounds[currentImage], 0, 0, 1436, 847); // b
}
if (showGrids==true) {
image(grids[currentImage], 0, 0, 1436, 847); // g
}
if (showObjects==true) {
image(objects[currentImage], 0, 0, 1436, 847); // o
}
}
Note that currently the same index is used for all 3 arrays.
You may want to add a separate index variable for each array (e.g. currentObjectIndex, currentBackgroundIndex, currentGridIndex) that you can increment independently of each other.
I recommend having a bit more patience and double checking your code first.
Visualise what each line of code will do, then check if it actually does what you expect it to do. Either you will learn something new or improve your logic.
Also, if mentally joggling 3 arrays is tricky (and it can be), go one step back: try your logic with one array only until you get the hang of it, then move on.
A step backwards is sometimes a step forward when you're going in the wrong direction.
As creative as you'd like to be with Processing, bare in mind, the interface to plugging your ideas to it is still a series of one instruction at a time, each precisely tuned to do exactly what you want it to do. There's room for fun, but unfortunately you need to get past the boring parts first
So i wrote this radial Menu controlled by the trackpad on the left-hand wand. It determine which button to magnify by my fingers position on trackpad.
The Weird movement can be seen here.
Here i attacked my code related to this problem, the code for left wand.
SteamVR_TrackedObject obj; //The wand
public GameObject buttonHolder; //All the buttons will be children of this object
public bool buttonEnabled;
void Awake() {
obj = GetComponent<SteamVR_TrackedObject>(); //this will be left hand controller
}
void Update() {
var device = SteamVR_Controller.Input((int)obj.index);
//if touchpad touched
if (device.GetTouch(SteamVR_Controller.ButtonMask.Touchpad))
{
if (buttonEnabled) //if radial menu is open
{
//touchPadAngle: Get the angle between touch coord and X-axis
Vector2 touchedCoord = device.GetAxis(EVRButtonId.k_EButton_Axis0); //what is this line each variable
float touchPadAngle = VectorAngle(new Vector2(1, 0), touchedCoord); //(1, 0) is X-axis
// ------------------- Find closest button ------------------------
//Description: The process will be done by calculating the angle between button_Vector2 and X-axis (button_V2_to_10)
// And then find the button with the closest angler difference with (touchPadAngle).
float minAngle = float.PositiveInfinity;
Transform minButton = transform; //Temperatry assign wand tranform to it.
float pad_N_button_Angle = 0.0f; //Angle between touchPadAngle and buttonAngle.
Vector2 button_V2_to_10;
float button_Angle;
foreach (Transform bt in buttonHolder.transform)
{
button_V2_to_10 = new Vector2(transform.position.x, transform.position.z) - new Vector2(bt.position.x, bt.position.z);
button_Angle = VectorAngle(new Vector2(1, 0), button_V2_to_10);
pad_N_button_Angle = Mathf.Abs(button_Angle - touchPadAngle);
//Both buttonAngle and touchPadAngle range from -180 to 180, avoid Abs(170 - (-170)) = 340
pad_N_button_Angle = (pad_N_button_Angle > 180) ? Mathf.Abs(pad_N_button_Angle - 360) : pad_N_button_Angle;
if (pad_N_button_Angle < minAngle)
{
minButton = bt;
minAngle = pad_N_button_Angle;
}
}
//Magnify the closest button
foreach (Transform bt in buttonHolder.transform)
{
GameObject btGO = bt.gameObject;
if (!btGO.GetComponentInChildren<ButtomHandler>().onHover && bt == minButton) {
//Magnify
}
else if (bt != minButton && btGO.GetComponentInChildren<ButtomHandler>().onHover)
{
//minify
}
}
}
else {
activateButtonMenu();
}
}
//dis-hover all button if leave touch pad
if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Touchpad)) {
//Hover the closest button
foreach (Transform bt in buttonHolder.transform)
{
GameObject btGO = bt.gameObject;
if (btGO.GetComponentInChildren<ButtomHandler>().onHover)
{
//minify
}
}
}
I'm quite stucked here, Any help would really be appreciated
"the closest angler difference with (touchPadAngle)"
shouldn't you consider more than one axis for a radial dial?
I am a newbie to programming GUIs and Processing. My questions is how can I get a list of checkboxes that I can scroll through? What I want is exactly the list of countries on the right here (http://goo.gl/MIKHi4).
I looked through the ControlP5 library and was able to find Checkboxes, but I don't know how I can make a scrollable list of them.
Thank you.
I had also been searching for this last week and hoping that there was a ready-for-use library for me to easily add the scrollable checkboxes to my application, but finally I had no luck. At last, what I did was implementing my own scrollable list of checkboxes.
Firstly, I added a ControlP5 slider as the scroll bar, and then at each frame, got value from the slider and draw the specific checkboxes based on that value.
Suppose that you have a list of 200 countries for the user to select. Then the code will be like:
ControlP5 cp5;
Slider scrollBar;
PFont fLabel;
int boxOver = -1; //Indicate mouse is over which checkbox
boolean[] boxSelected; //Checkbox selected or not
void setup() {
size(1024, 800);
colorMode(HSB, 360, 100, 100);
cp5 = new ControlP5();
scrollbar = cp5.addSlider("scrollbar")
.setPosition(1005, 110)
.setRange(0, 180)
.setSize(15, 490)
.setHandleSize(30)
.setSliderMode(Slider.FLEXIBLE)
.setValue(180); //Put handler at top because 0 value is at bottom of slider
fLabel = createFont("Arial", 12, false);
boxSelected = new boolean[200];
for(int i=0;i<200;i++) {
boxSelected[i] = false;
}
}
void draw() {
noFill();
stroke(200, 255);
rect(820, 110, 200, 490); //The outline of the scrollable box
stroke(150, 255);
int count = 0;
//Suppose that you want to display 20 items each time
for(int i=180-(int)scrollBar.getValue();i<180-(int)scrollBar.getValue()+20;i++) {
if(boxOver < 0) {
if(mouseX>=825 && mouseX<837 && mouseY >= 120+count*24 && mouseY <= 132+count*24) {
boxOver = i;
cursor(HAND);
}
}
if(boxSelected[i]) {
fill(50); //If the box is selected, fill this box
} else {
fill(360);
}
rect(825, 120+count*24, 12, 12); //Draw the box
//Draw the label text
textFont(fLabel);
fill(50);
text(countries[i], 843, 132+count*24); //Suppose the country names are stored in countries[]
count++;
}
}
void mousePressed() {
if(boxOver >=0) {
boxSelected[boxOver] = !boxSelected[boxOver]; //Toggle selection
}
}
Hope this helps you, or anyone who may encounter the same problem in the future.
There is now an example in the experimental examples called ControlP5SliderList
I'm in charge of maintaining an application which can draw graphs using JFreeChart.
The application is written in Eclipse-RCP and SWT and use a ChartComposite to display the charts.
The ChartComposite has been partially overridden in order to customize contextual menus depending on the selection:
#Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
chart = createChart(timeSeriesDataset);
chartComposite = new MyChartComposite(this, parent, SWT.NONE, chart, true);
chartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
selectionProvider = new GenericObjectSelectionProvider();
getSite().setSelectionProvider(selectionProvider);
// add information to the status line:
selectionProvider.addSelectionChangedListener(statusLineListener);
addDropSupport();// add D'n D support for dropping TimeSeries
}
protected JFreeChart createChart(TimeSeriesCollection ptimeSeriesDataset) {
JFreeChart vChart = ChartFactory.createTimeSeriesChart(null, "time", "values", ptimeSeriesDataset, true,
false, false);
vChart.setBackgroundPaint(Color.white);
XYPlot plot = vChart.getXYPlot();
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
// plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
plot.setDomainCrosshairVisible(true);
plot.setRangeCrosshairVisible(true);
plot.setRenderer(new /*OptimisedXYLineAndShapeRenderer()*/ StandardXYItemRendererFast());
XYItemRenderer renderer = plot.getRenderer();
renderer.setBaseToolTipGenerator(new MyXYSeriesToolTipGenerator());
renderer.setBaseItemLabelGenerator(new MyXYSeriesItemLabelGenerator());
renderer.setLegendItemLabelGenerator(new MyXYSeriesLegendItemLabelGenerator());
if (renderer instanceof XYLineAndShapeRenderer) {
XYLineAndShapeRenderer r = (XYLineAndShapeRenderer) renderer;
r.setBaseShapesVisible(false);
r.setBaseShapesFilled(true);
}
SimpleDateFormat dateFormat = getDateFormatAbscissa();
if (dateFormat != null){
DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(dateFormat);
}
return vChart;
}
My problem is that when too many variables are added to the chart (a TimeSeriesChart) the caption takes too much space and the graph disappears from the view:
ChartComposite with 2 series
ChartComposite many series
I tried to create a ScrollComposite to scroll in the ChartComposite and the result is a little better; but it only makes it possible to add more items in the caption before the graph disappears again:
ScrolledComposite scrollableChart = new ScrolledComposite(parent, SWT.BORDER|SWT.V_SCROLL);
chartComposite = new MyChartComposite(this, scrollableChart, SWT.NONE, chart, true);
//chartComposite = new MyChartComposite(this, parent, SWT.NONE, chart, true);
//chartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
scrollableChart.setContent(chartComposite);
scrollableChart.setExpandVertical(true);
scrollableChart.setExpandHorizontal(true);
scrollableChart.setMinSize(ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH, ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH);
My question is: How to provide a real scrollbar to the ChartComposite in order to keep the graph when many series are plotted on the graph?
I was able to sync a slider to the XYSeries in SWT using a ChartComosite and Slider objects through the use of FormData. And every time I move the slider I capture that event and update the chart myself according to my needs.
My use case may be different than yours, but it's worth to take a look to my answer here.
If you have questions regarding my implementation, described in that answer, feel free to ask for details
After several unsuccessful tries, I decided to limit the number of LegendItem shown on the chart.
To change the legend items to display in the LegendTitle I used a slider. The most difficult part was to recreate the a new LegendTitle when using the slider; I am not sure that my solution is optimal or elegant but at least it is working.
To make this possible I needed to listen to series creation (ChartChangeEventType.DATASET_UPDATED) to refresh the LegendTitle and set slider values.
So here is the code:
public class MyExampleChartComposite extends ChartComposite {
// snip
/**
* A slider to choose the legend items to display
*/
private Slider legendSlider;
/**
* Number of legend items to display on the chart
*/
private final static int NUMBER_OF_LEGENDITEMS_TO_DISPLAY = 10;
private void createPartControl(Composite parent, int style) {
JFreeChart chart = createChart();
setChart(chart);
legendSlider = new Slider(parent, SWT.NONE|SWT.H_SCROLL);
legendSlider.addSelectionListener(new SelectionListener() {
#Override
public void widgetSelected(SelectionEvent arg0) {
refreshLegend();
}
});
private JFreeChart createChart() {
chart.addChangeListener(new ChartChangeListener() {
#Override
public void chartChanged(ChartChangeEvent e) {
if (e.getType().equals(ChartChangeEventType.DATASET_UPDATED)) {
refreshLegend();
}
}
});
}
/**
* Refresh the the LegendItems and the slider,
* according to slider selection.
*/
public void refreshLegend() {
// Display LegendItems according to the selection
// display the 10 nearest legend item (current selected element included)
int begin = legendSlider.getSelection() - (NUMBER_OF_LEGENDITEMS_TO_DISPLAY/2);
int end = legendSlider.getSelection() + (NUMBER_OF_LEGENDITEMS_TO_DISPLAY/2 -1);
int seriesEndIndex = Math.max(getSeriesCount()-1, 0);
// if begin is less than 0
// set it to 0, and increase end to display 10 items
if (begin < 0) {
begin = 0;
end = NUMBER_OF_LEGENDITEMS_TO_DISPLAY - 1;
}
// if end is greater than the number of series plotted
// set it to the max possible value and increase begin to
// display 10 items
if (end > seriesEndIndex) {
end = seriesEndIndex;
begin = seriesEndIndex - (NUMBER_OF_LEGENDITEMS_TO_DISPLAY - 1);
}
end = Math.min(seriesEndIndex, end);
begin = Math.max(begin, 0);
// Refresh only if begin != end
if (end != begin) {
refreshLegendItems(begin, end);
refreshLegendSlider();
} else {
// in this case no more series are plotted on the chart
// clear legend
getChart().clearSubtitles();
}
}
/**
* Refresh the LegendTitle.
* Display only LegendItems between beginIndex and toIndex,
* to preserve space for the chart.
* #param beginIndex index of the {#link LegendItemCollection} used as the beginning of the new {#link LegendTitle}
* #param endIndex index of the {#link LegendItemCollection} used as the end of the new {#link LegendTitle}
*/
private void refreshLegendItems(int beginIndex, int endIndex) {
// Last 10 items
final LegendItemCollection result = new LegendItemCollection();
// get the renderer to retrieve legend items
XYPlot plot = getChart().getXYPlot();
XYItemRenderer renderer = plot.getRenderer();
// Number of series displayed on the chart
// Construct the legend
for (int i = beginIndex; i <= endIndex; i++) {
LegendItem item = renderer.getLegendItem(0, i);
result.add(item);
}
// Because the only way to create a new LegendTitle is to
// create a LegendItemSource first
LegendItemSource source = new LegendItemSource() {
LegendItemCollection lic = new LegendItemCollection();
{lic.addAll(result);}
public LegendItemCollection getLegendItems() {
return lic;
}
};
// clear previous legend title
getChart().clearSubtitles();
// Create the new LegendTitle and set its position
LegendTitle legend = new LegendTitle(source);
legend.setHorizontalAlignment(HorizontalAlignment.CENTER);
legend.setVerticalAlignment(VerticalAlignment.CENTER);
legend.setPosition(RectangleEdge.BOTTOM);
legend.setBorder(1.0, 1.0, 1.0, 1.0);
legend.setVisible(true);
// Add the LegendTitle to the graph
getChart().addLegend(legend);
}
/**
* Set values of the slider according to the number of series
* plotted on the graph
*/
private void refreshLegendSlider() {
int max = getSeriesCount() -1;
int selection = Math.min(legendSlider.getSelection(), max);
legendSlider.setValues(selection, 0, max, 1, 1, 1);
}
}
I have created a usercontrol just to contain an Image, because I haveto use Measureoverride and arrangeoverride methods and I can't create a subclass (Image is seled)... anyway, the thing is that when i call this.Image.Measure(SizeIwantto giveto the image) the desiredSize field of the image is not set... anybody know why?
I have been managing the Layouting methods before and It worked...
Here is the code (I have already checked all the other sizes and none of them is 0 or NaN)
protected override Size MeasureOverride(Size availableSize)
{
//the size that the element wants to have
Size imageDesiredSize = new Size();
imageDesiredSize = availableSize;
//if the current element's size is a pertentage
if ((futureHeight != 0))
{
imageDesiredSize.Height = availableSize.Height * futureHeight / 100;
}
if ((futureWidth != 0))
{
imageDesiredSize.Width = availableSize.Width * futureWidth / 100;
}
if (widthWrap)
{
imageDesiredSize.Width = ((BitmapImage)this.Source).PixelWidth;
}
if (heightWrap)
{
imageDesiredSize.Height = ((BitmapImage)this.Source).PixelHeight;
}
System.Diagnostics.Debug.WriteLine("imagedesired" + imageDesiredSize);
this.image.Measure(imageDesiredSize);
System.Diagnostics.Debug.WriteLine("desiredsize" + this.image.DesiredSize);
return imageDesiredSize;
}
In the end It was a really simple solution... In the constructor of the UserControl I added this line: this.Content = image;
And now the content of the Usercontrol is drawn in the screen :-)