How to calculate a text size? - codenameone

Apparently the com.codename1.ui.plaf.LookAndFeel.getTextAreaSize(TextArea, boolean) doesn't return an exact text size.
I would like to determine the size of a speech bubble, or the size of a possibly multi-line text in a speech bubble.
How would I do that? Is there a utility method for that somewhere in CN1?

This returns the preferred size. With multiline text we size based on the number of rows/columns since the text is modifiable and scrollable its content isn't fully taken into consideration for this calculation. Even when it is taken into consideration this can't be 100% accurate as even 1 pixel difference in width can cause a line break which will reflow everything. There are many special cases involved with text sizing so you shouldn't rely on accuracy. Try using setActAsLabel or SpanLabel.

Apparently there is currently no text area component in Codename One that can be used to size to its content.
Here is a custom one that does that:
private class Hint extends Component {
final String text;
final int rowsGap = new TextArea().getRowsGap();
private Hint(String aText) {
text = aText;
}
#Override
protected Dimension calcPreferredSize() {
int prefW = 0;
int prefH = 0;
Style style = getStyle();
Font font = style.getFont();
List<String> lines = StringUtil.tokenize(text, "\n");
int tally = 0;
for (String line: lines) {
tally++;
prefW = font.stringWidth(line);
}
prefH = tally * (font.getHeight() + rowsGap);
prefW += getUnselectedStyle().getHorizontalPadding() + 5;
prefH += style.getPaddingTop() + style.getPaddingBottom();
if(style.getBorder() != null) {
prefW = Math.max(style.getBorder().getMinimumWidth(), prefW);
prefH = Math.max(style.getBorder().getMinimumHeight(), prefH);
}
if(getUIManager().getLookAndFeel().isBackgroundImageDetermineSize() && style.getBgImage() != null) {
prefW = Math.max(style.getBgImage().getWidth(), prefW);
prefH = Math.max(style.getBgImage().getHeight(), prefH);
}
return new Dimension(prefW, prefH);
}
#Override
public void paint(Graphics aGraphics) {
Style style = getStyle();
int xLeft = style.getPaddingLeft(false), yTop = style.getPaddingTop();
List<String> lines = StringUtil.tokenize(text, "\n");
int y = yTop;
aGraphics.setColor(0x000000);
for (String line: lines) {
aGraphics.drawString(line, getX() + xLeft, getY() + y);
y += style.getFont().getHeight() + rowsGap;
}
}
}

Related

Need To Add Text To Each End Of A Path

I am drawing a diagram of a switch network in WPF. I can add the switches and the connections between them to the diagram. the connections are drawn as Path with LineGeometry as the Data:
Shapes.Path path1;
path1 = new Shapes.Path();
path1.Stroke = Brushes.Black;
path1.StrokeThickness = 1;
LineGeometry line1 = new LineGeometry();
path1.Data = line1;
I add the line to the thumb that I am using to represent the switch:
SwitchAsThumb thumb1 = new SwitchAsThumb;
thumb1.StartLines.Add(line1);
Where SwitchAsThumb is defined as:
public class SwitchAsThumb : Thumb
{
public List<LineGeometry> EndLines { get; private set; }
public List<LineGeometry> StartLines { get; private set; }
}
and this method in my window xaml.cs file to update the end point position of the path:
public void UpdateSwitchConnections(SwitchAsThumb thumb)
{
double left = Canvas.GetLeft(thumb);
double top = Canvas.GetTop(thumb);
for (int i = 0; i < thumb.StartLines.Count; i++)
thumb.StartLines[i].StartPoint = new Point(left + thumb.ActualWidth / 2, top + thumb.ActualHeight / 2);
for (int i = 0; i < thumb.EndLines.Count; i++)
thumb.EndLines[i].EndPoint = new Point(left + thumb.ActualWidth / 2, top + thumb.ActualHeight / 2);
}
I was thinking I could place labels or similar at the same points on the canvas when the lines are updated, but I'm not sure the labels would move when the user drags the switches around to re-arrange the diagram. The method I've implemented to re-draw the thumbs when the user drags them around could probably be updated to move the labels but I'm hoping there's a cleaner solution.

Array displaying images, need to make another method that takes a paramter NUM to order how many pictures are shown

I explained that horridly in the title, and I apologize, am very new to java and don't quite understand some of it yet.
Anyway,
My cat animation runs to the middle of the screen, scratches twice, then continues to run to the end, is there anyway to write a method that will take an INT NUM and make the cat scratch x amount of times? Not sure how to go about doing this.
Any help would be much appreciated
Here is my current code
/** Run the animation. */
public void nekoRun() {
moveIn();
scratch();
//scratch2(5);
moveOut();
}
/* Move Neko to the centre of the panel. */
private void moveIn() {
for (int i = 0; i < getWidth()/2; i+=10) {
xPos = i;
// swap images
if (currentImage == nekoPics[0])
currentImage = nekoPics[1];
else
currentImage = nekoPics[0];
repaint();
pause(150);
}
}
//neko stops and scratches in the middle of the panel twice
private void scratch() {
for (int i = 0; i < 10; i++) {
// Swap images.
currentImageIndex = nextImageList[currentImageIndex];
currentImage = nekoPics[currentImageIndex];
repaint();
pause(150);
}
}
//Neko runs to the end of the panel
private void moveOut() {
for (int i = xPos; i < getWidth(); i+=10) {
xPos = i;
// swap images
if (currentImage == nekoPics[0])
currentImage = nekoPics[1];
else
currentImage = nekoPics[0];
repaint();
pause(150);
}
}

Subclassed ListView column 0 displays corrupted text on Windows 7

Windows 7 (64 bit), .NET 4 (32 bit app)
I have a subclassed System.Windows.Forms.ListView comprising 2 columns displayed
in "Details" view. Only text fields are involved. The list comprises about 100
rows. On initial display all fields are drawn clearly but when scrolling or
"paging" with the scroll bar the text of some fields in the first column becomes
unreadable. It appears to be overwriten with random lines and blotches.
Is there anything that I can do to ensure that the text is clearly drawn in column 0?
The effect is diminished (clarity improved) when using remote desktop across a
LAN.
The effect disappears (all text is drawn clearly) when using remote desktop
across a WAN.
The ListView has always drawn perfectly on Windows XP.
For the purposes of a stripped down test, column header and DrawItem calls are ingored. The problem area is OnDrawSubItem which comprises the code
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
using (StringFormat sf = new StringFormat(StringFormatFlags.NoWrap))
{
Rectangle rect = new Rectangle(e.Bounds.Left
,e.Bounds.Top, e.Bounds.Width, e.Bounds.Height);
e.Graphics.DrawString(e.SubItem.Text, this.Font, Brushes.Black
,rect, sf);
}
base.OnDrawSubItem(e);
}
The following is the complete code that reproduces the problem:
using System;
using System.Windows.Forms;
using System.Drawing;
///
/// Build by saving to a file called LVTrial.cs and then executing "csc LVTrial.cs"
/// Scroll list up and down on a windows 7 box and the first column of text is corrupted.
///
class MyListView : ListView
{
Random randomiser = new Random();
private const int NUM_ROWS = 100;
private const int NUM_COLS = 2;
public MyListView()
{
this.Location = new System.Drawing.Point(23, 25);
this.OwnerDraw = true;
this.Size = new System.Drawing.Size(625, 387);
this.TabIndex = 0;
this.UseCompatibleStateImageBehavior = false;
this.View = System.Windows.Forms.View.Details;
for (int ii = 0; ii < NUM_COLS; ii++)
{
this.Columns.Add((
(System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())));
}
for (int ii = 0; ii < NUM_ROWS; ii++)
{
this.Items.Add( new ListViewItem(
new string[NUM_COLS] { CreateRandomString(1, 6), CreateRandomString(1, 6) } ) );
}
}
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
using (StringFormat sf = new StringFormat(StringFormatFlags.NoWrap))
{
Rectangle rect = new Rectangle(e.Bounds.Left
,e.Bounds.Top, e.Bounds.Width, e.Bounds.Height);
e.Graphics.DrawString(e.SubItem.Text, this.Font, Brushes.Black
,rect, sf);
}
base.OnDrawSubItem(e);
}
private string CreateRandomString(int minLength, int maxLength)
{
string str = string.Empty;
int length = randomiser.Next(minLength, maxLength + 1);
for (int ii = 0; ii < length; ii++)
{
int asciiChar = randomiser.Next(32, 126); // ascii range
str += Convert.ToChar(asciiChar);
}
return str;
}
}
class LVTrial : Form
{
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new LVTrial());
}
private LVTrial()
{
MyListView myListView = new MyListView();
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(674, 440);
this.Controls.Add(myListView);
this.Text = "LVTrial";
}
}
I have recorded this as a Windows 7 bug at https://connect.microsoft.com/VisualStudio/feedback/details/657909/subclassed-listview-column-0-displays-corrupted-text-on-windows-7
My own workaround for the problem was to insert an additional column at position 0. I now draw a rectangle with a transparent brush in column 0 for each item. All of which stops the text in the column to the right being drawn corrupt. I imagine drawing an image would achieve the same thing but I have not tried it. You can't hide this dummy column as the problem simply transfers to the second column (i.e. the first visible coolumn).
Another tip is that it seems to be performance related. The bug was more likely to occur with 100 items in the list view than 10,000.

how to draw N lines per inch?

my code below is supposed to display N lines per inch. instead i get a little more than N lines per inch. the distance between lines is somewhat smaller. in addition, changing the screen resolution makes the distance between lines change too. does anyone know how to handle that?
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace MyApp
{
class MyControl : Control
{
private readonly ContainerVisual container = new ContainerVisual();
private readonly DrawingVisual drawing = new DrawingVisual();
private void RenderDrawing()
{
var s = PresentationSource.FromVisual(this);
var dpiX = 96 * s.CompositionTarget.TransformToDevice.M11;
var dpiY = 96 * s.CompositionTarget.TransformToDevice.M22;
double N = 1;
using (var c = drawing.RenderOpen())
{
var p = new Pen(new SolidColorBrush(Colors.Black), 1);
for (int i = 0; i < 10; i++)
{
var x = i * dpiX / N;
c.DrawLine(p, new Point(x, 0), new Point(x, 100));
}
}
}
protected override Size ArrangeOverride(Size s)
{
RenderDrawing();
return s;
}
protected override Visual GetVisualChild(int index)
{
return container;
}
protected override Size MeasureOverride(Size s)
{
return new Size();
}
protected override int VisualChildrenCount
{
get { return 1; }
}
public MyControl()
{
container.Children.Add(drawing);
AddVisualChild(container);
}
}
}
this article seems to discuss the same problem: WPF DPI issues
there is no solution to this problem other than asking the user to set the correct DPI settings that correspond to the physical DPI of the screen. a workaround that i found that makes life a little easier is to use WPF application level scaling as described here: http://www.odewit.net/ArticleContent.aspx?id=WpfDpiScaling&lang=en&format=html
Not sure if this would accomplish what you are looking for, but it should help if the issue has to do with rounding. As I said in comments, your code looks mostly correct, I think it is a rounding issue with the dpi computation. Since you are desiring to render based on 96 dpi, compute the coordinates based on 96dpi and then convert the points to your device. I wrote this more for clarity, you could use a single array of points and just remember that i is the start point and i+1 is the end point and then you would only have to make a single call to transform the points.
private void RenderDrawing()
{
var s = PresentationSource.FromVisual(this);
var dpiX = 96;
int numberOfLines = 10;
double N = 1;
double spacing = dpiX / N;
var startPoints = new Point[numberOfLines]();
var endPoints = new Point[numberOfLines]();
for (int i = 0; i < numberOfLines; i++)
{
var x = i * spacing;
startPoints[i] = new Point(x, 0);
endPoints[i] = new Point(x, 100);
}
s.CompositionTarget.TransformToDevice.Transform(startPoints);
s.CompositionTarget.TransformToDevice.Transform(endPoints);
using (var c = drawing.RenderOpen())
{
using (var p = new Pen(new SolidColorBrush(Colors.Black), 1))
{
for(int i=0; i < numberOfLines; i++)
{
c.DrawLine(p, startPoints[i], endPoints[i]);
}
}
}
}

Google App Engine Geohashing

I am writing a web application using GWT and App Engine. My application will need to post and query items based on their latitude, longitude.
As a result of google's distributed database design you can't simple query a set of inequalities. Instead they suggest doing geohashing. The method is described on this page.
http://code.google.com/appengine/articles/geosearch.html
Essentially you pre compute a bounding box so that you can query items that have been tagged with that bounding box.
There is one part of the process that I don't understand. What does the "slice" attribute mean?
Thanks for your help!
For a complete java portage of Geomodel, please see http://code.google.com/p/javageomodel/.
There is a demo class to explain you how to use it.
Rather than implementing the geohash yourself, you might be interested in the GeoModel open source project that implements a geohash-like system on Google App Engine. Rather than understanding all the details, you can just import this library and make calls like proximity_fetch() and bounding_box_fetch().
This more recent article describes how it works and provides an example that uses it.
Instead of defining a bounding box with 4 coordinates (min and max latitude, min and max longitude), you can define it with the coordinates of the North-West corner of the box, and two parameters : resolution and slice.
The resolution defines the scale of the box, it is implemented as the number of figures below the decimal-point.
The slice is the width and height of the box, using the least significant figure as its unit.
The comments in geobox.py explain this in more details, with good examples :
To query for members of a bounding box, we start with some input coordinates
like lat=37.78452 long=-122.39532 (both resolution 5). We then round these
coordinates up and down to the nearest "slice" to generate a geobox. A "slice"
is how finely to divide each level of resolution in the geobox. The minimum
slice size is 1, the maximum does not have a limit, since larger slices will
just spill over into lower resolutions (hopefully the examples will explain).
Some examples:
resolution=5, slice=2, and lat=37.78452 long=-122.39532:
"37.78452|-122.39532|37.78450|-122.39530"
resolution=5, slice=10, and lat=37.78452 long=-122.39532:
"37.78460|-122.39540|37.78450|-122.39530"
resolution=5, slice=25, and lat=37.78452 long=-122.39532:
"37.78475|-122.39550|37.78450|-122.39525"
I'm working on a GWT/GAE project and had the same problem. My solution was to use a Geohash class that I modified slightly to be GWT-friendly. It's great for my needs of proximity searches.
If you've never seen Geohashes in action, check out Dave Troy's JS demo page.
An alternative to do geo spatial searches in App Engine is the Search Api. You won't need to worry about geohashing or implementation details, and you'll be able to search for elements close to geo point.
https://developers.google.com/appengine/docs/python/search/overview#Performing_Location-Based_Searches
I was working on a GAE project with geohashing and this python library did the trick for me: http://mappinghacks.com/code/geohash.py.txt
I was also in need of a Java version of GeoModel. I was working with Geohash before, which allowed me to fetch locations in a given bounding box. But there are considerable limitations to this when it comes to sorting: in order to get BigTable to accept a filter like geohash > '" + bottomLeft + "' && geohash < '" + topRight + "'", you have to order the list by geohash as well, which makes it impossible to sort it by other criteria (especially if you want to use pagination). At the same time, I just can't think of a solution to sort the results by distance (from a given user-position, i.e. the center of the bounding box), other than in Java-code. Again, this will not work if you need to have pagination.
Because of these problems I had to use a different approach, and GeoModel/Geoboxes seemed to be the way. So, I ported the Python-code to Java and it's just working fine! Here is the result:
public class Geobox {
private static double roundSlicedown(double coord, double slice) {
double remainder = coord % slice;
if (remainder == Double.NaN) {
return coord;
}
if (coord > 0) {
return coord - remainder + slice;
} else {
return coord - remainder;
}
}
private static double[] computeTuple(double lat, double lng,
int resolution, double slice) {
slice = slice * Math.pow(10, -resolution);
double adjustedLat = roundSlicedown(lat, slice);
double adjustedLng = roundSlicedown(lng, slice);
return new double[] { adjustedLat, adjustedLng - slice,
adjustedLat - slice, adjustedLng };
}
private static String formatTuple(double[] values, int resolution) {
StringBuffer s = new StringBuffer();
String format = String.format("%%.%df", resolution);
for (int i = 0; i < values.length; i++) {
s.append(String.format(format, values[i]).replace(',','.'));
if (i < values.length - 1) {
s.append("|");
}
}
return s.toString();
}
public static String compute(double lat, double lng, int resolution,
int slice) {
return formatTuple(computeTuple(lat, lng, resolution, slice),
resolution);
}
public static List<String> computeSet(double lat, double lng,
int resolution, double slice) {
double[] primaryBox = computeTuple(lat, lng, resolution, slice);
slice = slice * Math.pow(10, -resolution);
List<String> set = new ArrayList<String>();
for (int i = -1; i < 2; i++) {
double latDelta = slice * i;
for (int j = -1; j < 2; j++) {
double lngDelta = slice * j;
double[] adjustedBox = new double[] { primaryBox[0] + latDelta,
primaryBox[1] + lngDelta, primaryBox[2] + latDelta,
primaryBox[3] + lngDelta };
set.add(formatTuple(adjustedBox, resolution));
}
}
return set;
}
}
sorry for the late answer, but I didn't return to this page for some time. A GeoDao implementation using the Geobox approach could look like this:
public class GeoDaoImpl extends DaoImpl<T extends GeoModel> {
// geobox configs are: resolution, slice, use set (1 = true)
private final static int[][] GEOBOX_CONFIGS =
{ { 4, 5, 1 },
{ 3, 2, 1 },
{ 3, 8, 0 },
{ 3, 16, 0 },
{ 2, 5, 0 } };
public GeoDaoImpl(Class<T> persistentClass) {
super(persistentClass);
}
public List<T> findInGeobox(double lat, double lng, int predefinedBox, String filter, String ordering, int offset, int limit) {
return findInGeobox(lat, lng, GEOBOX_CONFIGS[predefinedBox][0], GEOBOX_CONFIGS[predefinedBox][1], filter, ordering, offset, limit);
}
public List<T> findInGeobox(double lat, double lng, int resolution, int slice, String filter, String ordering, int offset, int limit) {
String box = Geobox.compute(lat, lng, resolution, slice);
if (filter == null) {
filter = "";
} else {
filter += " && ";
}
filter += "geoboxes=='" + box + "'";
return super.find(persistentClass, filter, ordering, offset, limit);
}
public List<T> findNearest(final double lat, final double lng, String filter, String ordering, int offset, int limit) {
LinkedHashMap<String, T> uniqueList = new LinkedHashMap<String, T>();
int length = offset + limit;
for (int i = 0; i < GEOBOX_CONFIGS.length; i++) {
List<T> subList = findInGeobox(lat, lng, i, filter, ordering, 0, limit);
for (T model : subList) {
uniqueList.put(model.getId(), model);
}
if (uniqueList.size() >= length) {
break;
}
}
List<T> list = new ArrayList<T>();
int i = 0;
for (String key : uniqueList.keySet()) {
if (i >= offset && i <= length) {
list.add(uniqueList.get(key));
}
i++;
}
Collections.sort(list, new Comparator<T>() {
public int compare(T model1, T model2) {
double distance1 = Geoutils.distFrom(model1.getLatitude(), model1.getLongitude(), lat, lng);
double distance2 = Geoutils.distFrom(model2.getLatitude(), model2.getLongitude(), lat, lng);
return Double.compare(distance1, distance2);
}
});
return list;
}
#Override
public void save(T model) {
preStore(model);
super.save(model);
}
private void preStore(T model) {
// geoboxes are needed to find the nearest entities and sort them by distance
List<String> geoboxes = new ArrayList<String>();
for (int[] geobox : GEOBOX_CONFIGS) {
// use set
if (geobox[2] == 1) {
geoboxes.addAll(Geobox.computeSet(model.getLatitude(), model.getLongitude(), geobox[0], geobox[1]));
} else {
geoboxes.add(Geobox.compute(model.getLatitude(), model.getLongitude(), geobox[0], geobox[1]));
}
}
model.setGeoboxes(geoboxes);
}
}

Resources