Google's Text-To-Speech API from Android app - javascript

I want to use Google's Text-To-Speech API in an Android app but I only could find the way to do it from web (Chrome). This is my first attempt to play "Hello world" from the app.
playTTS is the onClick and it is been executed, but no sound is played. Is there any JS/Java library I need to import? Is it possible to generate an audio file from it?
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myBrowser = new WebView(this);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
}
public void playTTS(View view) {
myBrowser.loadUrl("javascript:speechSynthesis.speak(
SpeechSynthesisUtterance('Hello World'))");
}

In Android java code your Activity/other Class should implement TextToSpeech.OnInitListener. You will get a TextToSpeech instance by calling TextToSpeech(context, this). (Where context refers to your application's Context -- can be this in an Activity.) You will then receive a onInit() callback with status which tells whether the TTS engine is available or not.
You can talk by calling tts.speak(textToBeSpoken, TextToSpeech.QUEUE_FLUSH, null) or tts.speak(textToBeSpoken, TextToSpeech.QUEUE_ADD, null). The first one will interrupt any "utterance" that is still being spoken and the latter one will add the new "utterance" to a queue. The last parameter is not mandatory. It could be an "utterance id" defined by you in case you want to monitor the TTS status by setting an UtteranceProgressListener. (Not necessary)
In Java code a simple "TTS talker" class could be something like:
public class MyTtsTalker implements TextToSpeech.OnInitListener {
private TextToSpeech tts;
private boolean ttsOk;
// The constructor will create a TextToSpeech instance.
MyTtsTalker(Context context) {
tts = new TextToSpeech(context, this);
}
#Override
// OnInitListener method to receive the TTS engine status
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
ttsOk = true;
}
else {
ttsOk = false;
}
}
// A method to speak something
#SuppressWarnings("deprecation") // Support older API levels too.
public void speak(String text, Boolean override) {
if (ttsOk) {
if (override) {
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
}
else {
tts.speak(text, TextToSpeech.QUEUE_ADD, null);
}
}
}
}

Code for TTS:
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.Toast;
import java.util.HashMap;
import java.util.Locale;
public class KiwixTextToSpeech {
public static final String TAG_ASKQ = "askq;
private Context context;
private OnSpeakingListener onSpeakingListener;
private WebView webView;
private TextToSpeech tts;
private boolean initialized = false;
/**
* Constructor.
*
* #param context the context to create TextToSpeech with
* #param webView {#link android.webkit.WebView} to take contents from
* #param onInitSucceedListener listener that receives event when initialization of TTS is done
* (and does not receive if it failed)
* #param onSpeakingListener listener that receives an event when speaking just started or
* ended
*/
public KiwixTextToSpeech(Context context, WebView webView,
final OnInitSucceedListener onInitSucceedListener,
final OnSpeakingListener onSpeakingListener) {
Log.d(TAG_ASKQ, "Initializing TextToSpeech");
this.context = context;
this.onSpeakingListener = onSpeakingListener;
this.webView = webView;
this.webView.addJavascriptInterface(new TTSJavaScriptInterface(), "tts");
initTTS(onInitSucceedListener);
}
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
private void initTTS(final OnInitSucceedListener onInitSucceedListener) {
tts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
Log.d(TAG_ASKQ, "TextToSpeech was initialized successfully.");
initialized = true;
onInitSucceedListener.onInitSucceed();
} else {
Log.e(TAG_ASKQ, "Initilization of TextToSpeech Failed!");
}
}
});
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onStart(String utteranceId) {
}
#Override
public void onDone(String utteranceId) {
Log.e(TAG_ASKQ, "TextToSpeech: " + utteranceId);
onSpeakingListener.onSpeakingEnded();
}
#Override
public void onError(String utteranceId) {
Log.e(TAG_ASKQ, "TextToSpeech: " + utteranceId);
onSpeakingListener.onSpeakingEnded();
}
});
}
/**
* Reads the currently selected text in the WebView.
*/
public void readSelection() {
webView.loadUrl("javascript:tts.speakAloud(window.getSelection().toString());", null);
}
/**
* Starts speaking the WebView content aloud (or stops it if TTS is speaking now).
*/
public void readAloud() {
if (tts.isSpeaking()) {
if (tts.stop() == TextToSpeech.SUCCESS) {
onSpeakingListener.onSpeakingEnded();
}
} else {
Locale locale = LanguageUtils.ISO3ToLocale(ZimContentProvider.getLanguage());
int result;
if (locale == null
|| (result = tts.isLanguageAvailable(locale)) == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.d(TAG_ASKQ, "TextToSpeech: language not supported: " +
ZimContentProvider.getLanguage() + " (" + locale.getLanguage() + ")");
Toast.makeText(context,
context.getResources().getString(R.string.tts_lang_not_supported),
Toast.LENGTH_LONG).show();
} else {
tts.setLanguage(locale);
// We use JavaScript to get the content of the page conveniently, earlier making some
// changes in the page
webView.loadUrl("javascript:" +
"body = document.getElementsByTagName('body')[0].cloneNode(true);" +
// Remove some elements that are shouldn't be read (table of contents,
// references numbers, thumbnail captions, duplicated title, etc.)
"toRemove = body.querySelectorAll('sup.reference, #toc, .thumbcaption, " +
" title, .navbox');" +
"Array.prototype.forEach.call(toRemove, function(elem) {" +
" elem.parentElement.removeChild(elem);" +
"});" +
"tts.speakAloud(body.innerText);");
}
}
}
/**
* Returns whether the TTS is initialized.
*
* #return <code>true</code> if TTS is initialized; <code>false</code> otherwise
*/
public boolean isInitialized() {
return initialized;
}
/**
* Releases the resources used by the engine.
*
* #see android.speech.tts.TextToSpeech#shutdown()
*/
public void shutdown() {
tts.shutdown();
}
/**
* The listener which is notified when initialization of the TextToSpeech engine is successfully
* done.
*/
public interface OnInitSucceedListener {
public void onInitSucceed();
}
/**
* The listener that is notified when speaking starts or stops (regardless of whether it was a
* result of error, user, or because whole text was read).
*
* Note that the methods of this interface may not be called from the UI thread.
*/
public interface OnSpeakingListener {
public void onSpeakingStarted();
public void onSpeakingEnded();
}
private class TTSJavaScriptInterface {
#JavascriptInterface
#SuppressWarnings("unused")
public void speakAloud(String content) {
String[] lines = content.split("\n");
for (int i = 0; i < lines.length - 1; i++) {
String line = lines[i];
tts.speak(line, TextToSpeech.QUEUE_ADD, null);
}
HashMap<String, String> params = new HashMap<>();
// The utterance ID isn't actually used anywhere, the param is passed only to force
// the utterance listener to be notified
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "kiwixLastMessage");
tts.speak(lines[lines.length - 1], TextToSpeech.QUEUE_ADD, params);
if (lines.length > 0) {
onSpeakingListener.onSpeakingStarted();
}
}
}
}

Related

Getting the size of a Blazor page with Javascript

I'm learning JSInterop with Blazor.
I would like to get the width and the height of a Blazor page in oder to size à canvas.
I'm using the default Blazor template with the menu bar on the left part of the screen.
On the html part of the Canvas Test 1 page I have:
#page "/canvas_test1"
#inject IJSRuntime JsRuntime
<canvas #ref="canvas1"></canvas>
<button #onclick="SetCanvasSize">Set canvas size</button>
In the code part I have:
ElementReference canvas1;
async Task SetCanvasSize()
{
await JsRuntime.InvokeVoidAsync("SetCanvasSize", canvas1);
}
In the Javascript file I have:
function SetCanvasSize(element) {
ctx = element.getContext('2d');
console.log(window.innerWidth);
console.log(document.documentElement.scrollWidth);
}
But both methodes
window.innerWidth and document.documentElement.scrollWidth
give the width of the entire window not only the page that contains the canvas.
How can I get the width of only the page without the side menu?
Thank you
I think you're a little confused about what a Page is in Blazor. It's basically a component that you provide a route to for navigation. If you include a layout (which is usually set by default), that is in fact part of the page. All the things on the page, like your canvas, are just html elements. It is correctly telling you the size of the window, which you can see will change if you resize your browser.
html elements have the properties "offsetWidth" and "offsetHeight." So, for your use you want to pass the element to measure, and use element.offsetWidth:
function GetCanvasSize(element) {
alert ("Element measurements: " + element.offsetWidth + ", " + element.offsetHeight);
}
You can see more of the members of html elements here, including common methods and events: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetWidth
Note that this list is missing some important things you'd like to know, especially the various touch events which you'll need for dragging elements on mobile clients (mouse events won't work on phones).
You need to get the dimensions of the containing element. You also need to listen for resizing events.
I use a service that can take a ref for any element and listen for changes.
bCore.js
export function listenToWindowResize(dotNetHelper) {
function resizeEventHandler() {
dotNetHelper.invokeMethodAsync('WindowResizeEvent');
}
window.addEventListener("resize", resizeEventHandler);
dotNetHelper.invokeMethodAsync('WindowResizeEvent');
}
export function getBoundingRectangle(element, parm) {
return element.getBoundingClientRect();
}
export function getWindowSizeDetails(parm) {
var e = window, a = 'inner';
if (!('innerWidth' in window)) {
a = 'client';
e = document.documentElement || document.body;
}
let windowSize =
{
innerWidth: e[a + 'Width'],
innerHeight: e[a + 'Height'],
screenWidth: window.screen.width,
screenHeight: window.screen.height
};
return windowSize;
}
JSInteropCoreService.cs
public class JSInteropCoreService : IJSInteropCoreService, IAsyncDisposable
{
private readonly Lazy<Task<IJSObjectReference>> moduleTask;
private bool isResizing;
private System.Timers.Timer resizeTimer;
private DotNetObjectReference<JSInteropCoreService> jsInteropCoreServiceRef;
public JSInteropCoreService(IJSRuntime jsRuntime)
{
this.resizeTimer = new System.Timers.Timer(interval: 25);
this.isResizing = false;
this.resizeTimer.Elapsed += async (sender, elapsedEventArgs) => await DimensionsChanged(sender!, elapsedEventArgs);
this.moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>(
identifier: "import", args: "./_content/BJSInteroptCore/bCore.js").AsTask());
}
public event NotifyResizing OnResizing;
public event NotifyResize OnResize;
public async ValueTask InitializeAsync()
{
IJSObjectReference module = await GetModuleAsync();
this.jsInteropCoreServiceRef = DotNetObjectReference.Create(this);
await module.InvokeVoidAsync(identifier: "listenToWindowResize", this.jsInteropCoreServiceRef);
this.BrowserSizeDetails = await module.InvokeAsync<BrowserSizeDetails>(identifier: "getWindowSizeDetails");
}
public async ValueTask<BrowserSizeDetails> GetWindowSizeAsync()
{
IJSObjectReference module = await GetModuleAsync();
return await module.InvokeAsync<BrowserSizeDetails>(identifier: "getWindowSizeDetails");
}
public async ValueTask<ElementBoundingRectangle> GetElementBoundingRectangleAsync(ElementReference elementReference)
{
IJSObjectReference module = await GetModuleAsync();
return await module.InvokeAsync<ElementBoundingRectangle>(identifier: "getBoundingRectangle", elementReference);
}
[JSInvokable]
public ValueTask WindowResizeEvent()
{
if (this.isResizing is not true)
{
this.isResizing = true;
OnResizing?.Invoke(this.isResizing);
}
DebounceResizeEvent();
return ValueTask.CompletedTask;
}
public BrowserSizeDetails BrowserSizeDetails { get; private set; } = new BrowserSizeDetails();
private void DebounceResizeEvent()
{
if (this.resizeTimer.Enabled is false)
{
Task.Run(async () =>
{
this.BrowserSizeDetails = await GetWindowSizeAsync();
isResizing = false;
OnResizing?.Invoke(this.isResizing);
OnResize?.Invoke();
});
this.resizeTimer.Restart();
}
}
private async ValueTask DimensionsChanged(object sender, System.Timers.ElapsedEventArgs e)
{
this.resizeTimer.Stop();
this.BrowserSizeDetails = await GetWindowSizeAsync();
isResizing = false;
OnResizing?.Invoke(this.isResizing);
OnResize?.Invoke();
}
public async ValueTask DisposeAsync()
{
if (moduleTask.IsValueCreated)
{
IJSObjectReference module = await GetModuleAsync();
await module.DisposeAsync();
}
}
private async Task<IJSObjectReference> GetModuleAsync()
=> await this.moduleTask.Value;
}
public class ElementBoundingRectangle
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public double Top { get; set; }
public double Right { get; set; }
public double Bottom { get; set; }
public double Left { get; set; }
}
public class BrowserSizeDetails
{
public double InnerWidth { get; set; }
public double InnerHeight { get; set; }
public int ScreenWidth { get; set; }
public int ScreenHeight { get; set; }
}
I debounce the resize events using a 25ms interval to prevent lag.

Loading Images from URL Is Producing Unexpected Results

I am loading images from Wikipedia into a Grid view. For the most part this is working correctly. Because there could possible be up to 200 or more images being loaded I am try to run it in a new thread. I see a definite delay when scrolling from my Album tab to the Artist tab that is loading the images. I am also see some lag as images are still getting load while scrolling up and down the list. Also when I scroll back to the top of the list place holders that previously occupied by the default image because I am unable to get an image from Wikipedia are now occupied by images from another artist.
When I scroll back to the song list and then back to the artist list the view is reset but it still has a lot of delay when going into the artist tab.
This image is what the screen looks like when first entering the Artist tab.
This image is what the screen looks like after scrolling to the bottom of the list and back to the top.
As you can see the <unknow. and AJR have had their default image replaced.
Here is my code that I am calling to load the images from Wikipedia.
#Override
public void onBindViewHolder(#NonNull ARV holder, int position) {
Artist artist = artistList.get(position);
if(artist!=null) {
holder.artistName.setText(artist.artistName);
String bandName = artist.artistName;
bandName = bandName.replace(' ','_');
try {
String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
URL url = new URL(imageUrl);
ImageLoader.getInstance().displayImage(imageUrl, holder.artistImage,
new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
.resetViewBeforeLoading(true).build());
} catch (IOException e) {
e.printStackTrace();
}
/*ImageLoader.getInstance().displayImage(getCoverArtPath(context,artist.id),holder.artistImage,
new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
.resetViewBeforeLoading(false).build());*/
}
}
private StringBuilder getUrlSource(String site) throws IOException {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
URL localUrl = null;
localUrl = new URL(site);
URLConnection conn = localUrl.openConnection();
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line = "";
String html;
StringBuilder ma = new StringBuilder();
while ((line = reader.readLine()) != null) {
ma.append(line);
Log.i(ContentValues.TAG, "StringBuilder " + ma);
}
Log.i(ContentValues.TAG, "Final StringBuilder " + ma);
return ma;
}
public static String cutImg(StringBuilder split){
int start=split.indexOf("\"source\":")+new String("\"source\":\"").length();
split.delete(0, start);
split.delete(split.indexOf("\""), split.length());
Log.i(ContentValues.TAG, "StringBuilder " + split);
return split.toString();
}
Here is the code that is call the Artist Fragment.
public class ArtistFragment extends Fragment {
int spanCount = 3; // 2 columns
int spacing = 20; // 20px
boolean includeEdge = true;
private RecyclerView recyclerView;
private ArtistAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_artist, container, false);
recyclerView = view.findViewById(R.id.artistFragment);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
Thread t = new Thread()
{
public void run()
{
// put whatever code you want to run inside the thread here.
new LoadData().execute("");
}
};
t.start();
return view;
}
public class LoadData extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... strings) {
if(getActivity()!=null) {
adapter=new ArtistAdapter(getActivity(),new ArtistLoader().artistList(getActivity()));
}
return "Executed";
}
#Override
protected void onPostExecute(String s) {
recyclerView.setAdapter(adapter);
if(getActivity()!=null) {
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));
}
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
}
}
I have also tried this using Picasso using the following code:
bandName = artist.artistName;
bandName = bandName.replace(' ','_');
try {
String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
URL url = new URL(imageUrl);
Picasso.get().load(imageUrl).placeholder(R.drawable.album)
.error(R.drawable.artistdefault).into(holder.artistImage);
} catch (IOException e) {
e.printStackTrace();
}
The results are pretty much the same as when I used Android-Universal-Image-Loader. I have been try for several days to fix this, I have tried several different examples that I found on Stack overflow but none of them seem to resolve the issues I am seeing. I am hoping that someone will be able to identify what I am doing incorrectly.
Thanks in advance.
ArtistFragmentconverted to Kotlin
class ArtistFragment : Fragment() {
var spanCount = 3 // 2 columns
var spacing = 20 // 20px
var includeEdge = true
var retrofit: Retrofit? = null
var wikiService: WikiService? = null
var adapter: ArtistAdapter? = null
private var recyclerView: RecyclerView? = null
private var viewModelJob = Job()
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private var progress_view: ProgressBar? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_artist, container, false)
recyclerView = view.findViewById(R.id.artistFragment)
recyclerView?.setLayoutManager(GridLayoutManager(activity, 3))
progress_view = view.findViewById(R.id.progress_view)
initWikiService()
initList()
//LoadData().execute("")
return view
}
override fun onDestroy() {
super.onDestroy()
viewModelJob.cancel()
}
private fun initList() {
recyclerView!!.addItemDecoration(GridSpacingItemDecoration(spanCount, spacing, includeEdge))
adapter = ArtistAdapter(this)
adapter?.items = ArrayList()
adapter?.listener = this
recyclerView?.adapter = adapter
viewModelScope.launch {
progress_view.visibility = View.VISIBLE
val wikiPages = getWikiPages()
adapter?.items = wikiPages
progress_view?.visibility = View.GONE
}
}
private suspend fun getWikiPages(): ArrayList<Artist> {
val newItems = ArrayList<Artist>()
withContext(Dispatchers.IO) {
ArtistData.artists.map { artist ->
async { wikiService?.getWikiData(artist) }
}.awaitAll().forEach { response ->
val pages = response?.body()?.query?.pages
pages?.let {
for (page in pages) {
val value = page.value
val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
val title = value.title ?: "Unknown"
val url = value.thumbnail?.source
newItems.add(Artist(id, title, albumCount = 0, songCount = 0, artistUrl = url!!))
}
}
}
}
return newItems
}
private fun initWikiService() {
retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
wikiService = retrofit?.create(WikiService::class.java)
}
I believe I have resolved most of the issues I was previously seeing I am now down to the following problems:
Artist.item.map { artist -> - Not sure how this should be called, Unresolved reference: item
}.awaitAll().forEach { response -> = forEach is telling me Overload resolution ambiguity. All these functions match.
public inline fun Iterable<TypeVariable(T)>.forEach(action: (TypeVariable(T)) → Unit): Unit defined in kotlin.collections
public inline fun <K, V> Map<out TypeVariable(K), TypeVariable(V)>.forEach(action: (Map.Entry<TypeVariable(K), TypeVariable(V)>) → Unit): Unit defined in kotlin.collections
newItems.add(Artist(id, title, url)) - I know that the variables for the Artist Model need to go here, but when I put them there they are unresolved.
I have reworked the ArtistAdapter not sure if it is correct though.
class ArtistAdapter(private val context: ArtistFragment, private val artistList: List<Artist>?) : RecyclerView.Adapter<ArtistAdapter.ARV>() {
private var dimension: Int = 64
init {
val density = context.resources.displayMetrics.density
dimension = (density * 64).toInt()
hasStableIds()
}
var items: MutableList<Artist> = ArrayList()
set(value) {
field = value
notifyDataSetChanged()
}
var listener: Listener? = null
interface Listener {
fun onItemClicked(item: Artist)
abstract fun ArtistAdapter(context: ArtistFragment): ArtistAdapter
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ARV {
return ARV(LayoutInflater.from(parent.context).inflate(R.layout.artist_gride_item, parent,
false))
}
override fun onBindViewHolder(holder: ARV, position: Int) {
holder.onBind(getItem(position))
}
private fun getItem(position: Int): Artist = items[position]
override fun getItemId(position: Int): Long = items[position].id
override fun getItemCount(): Int {
return artistList?.size ?: 0
}
inner class ARV(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val artistNameView: TextView = itemView.findViewById(R.id.artistName)
private val artistAlbumArtView: SquareCellView = itemView.findViewById(R.id.artistAlbumArt)
fun onBind(item: Artist) {
artistNameView.text=item.artistName
if(item.artistURL!=null) {
Picasso.get()
.load(item.artistURL)
.resize(dimension, dimension)
.centerCrop()
.error(R.drawable.artistdefualt)
.into(artistAlbumArtView)
} else {
artistAlbumArtView.setImageResource(R.drawable.artistdefualt)
}
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
val artistId = artistList!![bindingAdapterPosition].id
val fragmentManager = (context as AppCompatActivity).supportFragmentManager
val transaction = fragmentManager.beginTransaction()
val fragment: Fragment
transaction.setCustomAnimations(R.anim.layout_fad_in, R.anim.layout_fad_out,
R.anim.layout_fad_in, R.anim.layout_fad_out)
fragment = ArtistDetailsFragment.newInstance(artistId)
transaction.hide(context.supportFragmentManager
.findFragmentById(R.id.main_container)!!)
transaction.add(R.id.main_container, fragment)
transaction.addToBackStack(null).commit()
}
}
}
Logcat Snippet
java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or it
s super classes (declaration of 'java.lang.invoke.LambdaMetafactory' appears in /apex/com.android.art/javalib/core-oj.jar)
at okhttp3.internal.Util.<clinit>(Util.java:87)
at okhttp3.internal.Util.skipLeadingAsciiWhitespace(Util.java:321)
at okhttp3.HttpUrl$Builder.parse(HttpUrl.java:1313)
at okhttp3.HttpUrl.get(HttpUrl.java:917)
at retrofit2.Retrofit$Builder.baseUrl(Retrofit.java:506)
at com.rvogl.androidaudioplayer.fragments.ArtistFragment.initWikiService(ArtistFragment.kt:103)
at com.rvogl.androidaudioplayer.fragments.ArtistFragment.onCreateView(ArtistFragment.kt:43)
It looks to me as a known bug with Picasso.
Try to load default image manually so it won't be replaced with cached one.
Update 14.10.20:
I think the main problem is that you load network content in adapter in rather ineffective way. I suggest to form a list of all urls at first, leaving only image load in adapter.
Also reccomend you to use rerofit2 for network calls and something for async work instead of AsyncTask: rxJava, courutines, flow etc.
I created a sample project to load data async using retrofit2+coroutines.
In activity:
private val viewModelScope = CoroutineScope(Dispatchers.Main)
private fun initWikiService() {
retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
wikiService = retrofit?.create(WikiService::class.java)
}
private fun initList() {
viewModelScope.launch {
val wikiPages = getWikiPages()
adapter?.items = wikiPages
}
}
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private suspend fun getWikiPages(): ArrayList<Item> {
val newItems = ArrayList<Item>()
withContext(IO) {
ArtistData.artists.map { artist ->
async { wikiService?.getWikiData(artist) }
}.awaitAll().forEach { response ->
val pages = response?.body()?.query?.pages
pages?.let {
for (page in pages) {
val value = page.value
val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
val title = value.title ?: "Unknown"
val url = value.thumbnail?.source
newItems.add(Item(id, title, url))
}
}
}
}
return newItems
}
In viewHolder:
fun onBind(item: Item) {
if (item.url != null) {
Picasso.get()
.load(item.url)
.resize(dimension, dimension)
.centerCrop()
.error(R.drawable.ic_baseline_broken_image_24)
.into(pictureView)
} else {
pictureView.setImageResource(R.drawable.ic_baseline_image_24)
}
}
In adapter: add hasStableIds() to constructor and override getItemId method:
init {
hasStableIds()
}
override fun getItemId(position: Int): Long = items[position].id
Retrofit Service:
interface WikiService {
#GET("/w/api.php?action=query&prop=pageimages&format=json&pithumbsize=250")
suspend fun getWikiData(#Query("titles") band: String): Response<WikipediaResponse?>
}

How to display a enum value using a user-entered int?

I need to do 3 things with the CardsDriver: using the default constructor Card() display a default card. Then, take a user's int and display a Card object using the parameterized constructor Card(int n) (Using mod%). Finally use showDeck() to display all 52 cards. (As you can tell I kinda did this using the enhanced for loops at the end, it works but I do not think it is the best way.)
Would really like some help with at least the second problem. I just need to understand what I need to implement..
package cards;
import java.util.Scanner;
public class CardsDriver
{
private static Scanner keyboard = new Scanner(System.in);
public static void main (String[] args)
{
boolean another;
int n;
Card card = new Card();
do
{
System.out.println("Type a non-negative integer. Type -1 to
stop.");
n = keyboard.nextInt( );
if (n == -1)
{
another = false;
}
else
{
another = true;
}
} while (another == true);
System.out.println("All 52 Cards Follow:");
showDeck( );
}
private static int getNextInt()
{
return keyboard.nextInt();
}
private static void showDeck()
{
for (Face face : Face.values())
{
for (Suit suit : Suit.values())
System.out.println("The " + face + " of " + suit);
}
}
}
and here is my Card class
package cards;
public class Card {
private Face face;
private Suit suit;
public Card() {
face = Face.ACE;
suit = Suit.CLUBS;
}
public Card(Card existingCard) {
this.face = existingCard.face;
this.suit = existingCard.suit;
}
public Card(int n) {
face = Face.values()[n % 13];
suit = Suit.values()[n % 4];
}
public String toString() {
return "the" + face + "of" + suit;
}
}

ANTLR 4 - Creating an AST from an existing ParseTree [duplicate]

I've been searching A LOT about this and I couldn't find anything useful that REALLY helps me build an AST. I already know that ANTLR4 doesn't build AST like ANTLR3 used to do. Everyone say: "Hey, use visitors!", but I couldn't find any example or more detailed explanation on HOW can I do this...
I have a grammar must like C, but with every commands written in Portuguese (portuga programming language). I can easily generate the parse tree using ANTLR4. My question is: What I need to do now to create an AST?
BTW, I'm using Java and IntelliJ...
EDIT1: The closest I could get was using the answer of this topic: Is there a simple example of using antlr4 to create an AST from java source code and extract methods, variables and comments?
But it only prints the name of the visited methods..
Since the first attempt didn't work for me as I expected, I tried to use this tutorial from ANTLR3, but I couldn't figure out how to use StringTamplate instead of ST...
Reading the book The Definitive ANTLR 4 Reference I also couldn't find anything related to ASTs.
EDIT2: Now I have one class to create the DOT file, I just need figure out on how to use visitors properly
Ok, let's build a simple math example. Building an AST is totally overkill for such a task but it's a nice way to show the principle.
I'll do it in C# but the Java version would be very similar.
The grammar
First, let's write a very basic math grammar to work with:
grammar Math;
compileUnit
: expr EOF
;
expr
: '(' expr ')' # parensExpr
| op=('+'|'-') expr # unaryExpr
| left=expr op=('*'|'/') right=expr # infixExpr
| left=expr op=('+'|'-') right=expr # infixExpr
| func=ID '(' expr ')' # funcExpr
| value=NUM # numberExpr
;
OP_ADD: '+';
OP_SUB: '-';
OP_MUL: '*';
OP_DIV: '/';
NUM : [0-9]+ ('.' [0-9]+)? ([eE] [+-]? [0-9]+)?;
ID : [a-zA-Z]+;
WS : [ \t\r\n] -> channel(HIDDEN);
Pretty basic stuff, we have a single expr rule that handles everything (precedence rules etc).
The AST nodes
Then, let's define some AST nodes we'll use. These are totally custom and you can define them in the way you want to.
Here are the nodes we'll be using for this example:
internal abstract class ExpressionNode
{
}
internal abstract class InfixExpressionNode : ExpressionNode
{
public ExpressionNode Left { get; set; }
public ExpressionNode Right { get; set; }
}
internal class AdditionNode : InfixExpressionNode
{
}
internal class SubtractionNode : InfixExpressionNode
{
}
internal class MultiplicationNode : InfixExpressionNode
{
}
internal class DivisionNode : InfixExpressionNode
{
}
internal class NegateNode : ExpressionNode
{
public ExpressionNode InnerNode { get; set; }
}
internal class FunctionNode : ExpressionNode
{
public Func<double, double> Function { get; set; }
public ExpressionNode Argument { get; set; }
}
internal class NumberNode : ExpressionNode
{
public double Value { get; set; }
}
Converting a CST to an AST
ANTLR generated the CST nodes for us (the MathParser.*Context classes). We now have to convert these to AST nodes.
This is easily done with a visitor, and ANTLR provides us with a MathBaseVisitor<T> class, so let's work with that.
internal class BuildAstVisitor : MathBaseVisitor<ExpressionNode>
{
public override ExpressionNode VisitCompileUnit(MathParser.CompileUnitContext context)
{
return Visit(context.expr());
}
public override ExpressionNode VisitNumberExpr(MathParser.NumberExprContext context)
{
return new NumberNode
{
Value = double.Parse(context.value.Text, NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent)
};
}
public override ExpressionNode VisitParensExpr(MathParser.ParensExprContext context)
{
return Visit(context.expr());
}
public override ExpressionNode VisitInfixExpr(MathParser.InfixExprContext context)
{
InfixExpressionNode node;
switch (context.op.Type)
{
case MathLexer.OP_ADD:
node = new AdditionNode();
break;
case MathLexer.OP_SUB:
node = new SubtractionNode();
break;
case MathLexer.OP_MUL:
node = new MultiplicationNode();
break;
case MathLexer.OP_DIV:
node = new DivisionNode();
break;
default:
throw new NotSupportedException();
}
node.Left = Visit(context.left);
node.Right = Visit(context.right);
return node;
}
public override ExpressionNode VisitUnaryExpr(MathParser.UnaryExprContext context)
{
switch (context.op.Type)
{
case MathLexer.OP_ADD:
return Visit(context.expr());
case MathLexer.OP_SUB:
return new NegateNode
{
InnerNode = Visit(context.expr())
};
default:
throw new NotSupportedException();
}
}
public override ExpressionNode VisitFuncExpr(MathParser.FuncExprContext context)
{
var functionName = context.func.Text;
var func = typeof(Math)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(m => m.ReturnType == typeof(double))
.Where(m => m.GetParameters().Select(p => p.ParameterType).SequenceEqual(new[] { typeof(double) }))
.FirstOrDefault(m => m.Name.Equals(functionName, StringComparison.OrdinalIgnoreCase));
if (func == null)
throw new NotSupportedException(string.Format("Function {0} is not supported", functionName));
return new FunctionNode
{
Function = (Func<double, double>)func.CreateDelegate(typeof(Func<double, double>)),
Argument = Visit(context.expr())
};
}
}
As you can see, it's just a matter of creating an AST node out of a CST node by using a visitor. The code should be pretty self-explanatory (well, maybe except for the VisitFuncExpr stuff, but it's just a quick way to wire up a delegate to a suitable method of the System.Math class).
And here you have the AST building stuff. That's all that's needed. Just extract the relevant information from the CST and keep it in the AST.
The AST visitor
Now, let's play a bit with the AST. We'll have to build an AST visitor base class to traverse it. Let's just do something similar to the AbstractParseTreeVisitor<T> provided by ANTLR.
internal abstract class AstVisitor<T>
{
public abstract T Visit(AdditionNode node);
public abstract T Visit(SubtractionNode node);
public abstract T Visit(MultiplicationNode node);
public abstract T Visit(DivisionNode node);
public abstract T Visit(NegateNode node);
public abstract T Visit(FunctionNode node);
public abstract T Visit(NumberNode node);
public T Visit(ExpressionNode node)
{
return Visit((dynamic)node);
}
}
Here, I took advantage of C#'s dynamic keyword to perform a double-dispatch in one line of code. In Java, you'll have to do the wiring yourself with a sequence of if statements like these:
if (node is AdditionNode) {
return Visit((AdditionNode)node);
} else if (node is SubtractionNode) {
return Visit((SubtractionNode)node);
} else if ...
But I just went for the shortcut for this example.
Work with the AST
So, what can we do with a math expression tree? Evaluate it, of course! Let's implement an expression evaluator:
internal class EvaluateExpressionVisitor : AstVisitor<double>
{
public override double Visit(AdditionNode node)
{
return Visit(node.Left) + Visit(node.Right);
}
public override double Visit(SubtractionNode node)
{
return Visit(node.Left) - Visit(node.Right);
}
public override double Visit(MultiplicationNode node)
{
return Visit(node.Left) * Visit(node.Right);
}
public override double Visit(DivisionNode node)
{
return Visit(node.Left) / Visit(node.Right);
}
public override double Visit(NegateNode node)
{
return -Visit(node.InnerNode);
}
public override double Visit(FunctionNode node)
{
return node.Function(Visit(node.Argument));
}
public override double Visit(NumberNode node)
{
return node.Value;
}
}
Pretty simple once we have an AST, isn't it?
Putting it all together
Last but not least, we have to actually write the main program:
internal class Program
{
private static void Main()
{
while (true)
{
Console.Write("> ");
var exprText = Console.ReadLine();
if (string.IsNullOrWhiteSpace(exprText))
break;
var inputStream = new AntlrInputStream(new StringReader(exprText));
var lexer = new MathLexer(inputStream);
var tokenStream = new CommonTokenStream(lexer);
var parser = new MathParser(tokenStream);
try
{
var cst = parser.compileUnit();
var ast = new BuildAstVisitor().VisitCompileUnit(cst);
var value = new EvaluateExpressionVisitor().Visit(ast);
Console.WriteLine("= {0}", value);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine();
}
}
}
And now we can finally play with it:
I have created a small Java project that allows you to test your ANTLR grammar instantly by compiling the lexer and parser generated by ANTLR in-memory. You can just parse a string by passing it to the parser, and it will automatically generate an AST from it which can then be used in your application.
For the purpose of reducing the size of the AST, you could use a NodeFilter to which you could add the production-rule names of the non-terminals that you would like to be considered when constructing the AST.
The code and some code examples can be found at
https://github.com/julianthome/inmemantlr
Hope the tool is useful ;-)
I have found two simple ways, focused on the functionality available in the TestRig.java file of antlr4.
Via terminal
This is my example for parsing C++ with the corresponding CPP14.g4 grammar file
java -cp .:antlr-4.9-complete.jar org.antlr.v4.gui.TestRig CPP14 translationunit -tree filename.cpp. If you omit the filename.cpp, the rig reads from stdin. "translationunit" is the start rule name of the CPP14.g4 grammar file I use.
Via Java
I used parts of code found in the TestRig.java file. Let's suppose again that we have a string of C++ source code from which we want to produce the AST (you can also read directly from a file).
String source_code = "...your cpp source code...";
CodePointCharStream stream_from_string = CharStreams.fromString(source_code);
CPP14Lexer lexer = new CPP14Lexer(new ANTLRInputStream(source_code));
CommonTokenStream tokens = new CommonTokenStream(lexer);
CPP14Parser parser = new CPP14Parser(tokens);
String parserName = "CPP14Parser";
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<? extends Parser> parserClass = null;
parserClass = cl.loadClass(parserName).asSubclass(Parser.class);
String startRuleName = "translationunit"; //as specified in my CPP14.g4 file
Method startRule = parserClass.getMethod(startRuleName);
ParserRuleContext tree = (ParserRuleContext)startRule.invoke(parser, (Object[])null);
System.out.println(tree.toStringTree(parser));
My imports are:
import java.lang.reflect.Method;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Parser;
All these require that you have produced the necessary files (lexer, parser etc) with the command java -jar yournaltrfile.jar yourgrammar.g4 and that you have then compiled all the *.java files.
I've converted the code of Terrence Parr's book "Language Implementation Pattern" for the pattern "Tree Grammar" which is based on antlr 3 (source-code under tpdsl-code/walking/tree-grammar) to antlr 4 using a Visitor and the "Homogeneous AST" pattern.
Here is the grammar:
VecMath.g4
// START: header
grammar VecMath;
tokens { VEC } // define imaginary token for vector literal
// END: header
// START: stat
prog: stat+ ; // build list of stat trees
stat: ID assign='=' expr #StatAssign // '=' is operator subtree root
| print='print' expr #StatPrint // 'print' is subtree root
;
// END: stat
// START: expr
expr: single=multExpr
| left=expr op='+' right=expr ; // '+' is root node
multExpr
: single=primary
| left=multExpr op=('*'|'.') right=multExpr // '*', '.' are roots
;
primary
: INT
| ID
| vec_start='[' expr (comma=',' expr)* ']'
;
// END: expr
ID : 'a'..'z'+ ;
INT : '0'..'9'+ ;
WS : (' '|'\r'|'\n')+ -> skip ;
The Visitor
package walking.v4.vecmath_ast.impl;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.Token;
import walking.v4.vecmath_ast.antlr.VecMathBaseVisitor;
import walking.v4.vecmath_ast.antlr.VecMathParser.ExprContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.MultExprContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.PrimaryContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.ProgContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.StatAssignContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.StatContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.StatPrintContext;
import walking.v4.vecmath_ast.antlr.VecMathParser;
public class VecMathBuildASTVisitor extends VecMathBaseVisitor<AST> {
#Override
public AST visitProg(ProgContext ctx) {
AST ast = new AST();
for (StatContext stmt : ctx.stat()) {
ast.addChild(visit(stmt));
}
return ast;
}
#Override
public AST visitStatAssign(StatAssignContext ctx) {
AST ast = new AST(ctx.assign);
ast.addChild(new AST(ctx.ID().getSymbol()));
ast.addChild(visit(ctx.expr()));
return ast;
}
#Override
public AST visitStatPrint(StatPrintContext ctx) {
AST ast = new AST(ctx.print);
ast.addChild(visit(ctx.expr()));
return ast;
}
#Override
public AST visitExpr(ExprContext ctx) {
AST ast = null;
if (ctx.single != null) {
ast = visit(ctx.single);
} else {
ast = new AST(ctx.op);
ast.addChild(visit(ctx.left));
ast.addChild(visit(ctx.right));
}
return ast;
}
#Override
public AST visitMultExpr(MultExprContext ctx) {
AST ast = null;
if (ctx.single != null) {
ast = visit(ctx.single);
} else {
ast = new AST(ctx.op);
ast.addChild(visit(ctx.left));
ast.addChild(visit(ctx.right));
}
return ast;
}
#Override
public AST visitPrimary(PrimaryContext ctx) {
AST ast = null;
if (ctx.vec_start != null) {
ast = new AST(new CommonToken(VecMathParser.VEC, "VEC"));
for (ExprContext expr : ctx.expr()) {
ast.addChild(visit(expr));
}
} else {
Token token = ctx.ID() != null ? ctx.ID().getSymbol() :
ctx.INT().getSymbol();
ast = new AST(token);
}
return ast;
}
}
The AST
Essentially just adjusted the Tokens compared to the original version in tpdsl-code/IR/Homo
package walking.v4.vecmath_ast.impl;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.Token;
import walking.v4.vecmath_ast.antlr.VecMathParser;
import java.util.ArrayList;
import java.util.List;
// Homogenous AST node type
public class AST {
Token token; // from which token do we create this node?
List<AST> children; // normalized list of children
public AST() { ; } // for making nil-rooted nodes
public AST(Token token) { this.token = token; }
/** Create node from token type; used mainly for imaginary tokens */
public AST(int tokenType) { this.token = new CommonToken(tokenType); }
/** external visitors execute the same action for all nodes
* with same node type while walking
*/
public int getNodeType() { return token.getType(); }
public void addChild(AST t) {
if (children == null) children = new ArrayList<>();
children.add(t);
}
public List<AST> getChildren() { return children; }
/** to represent flat lists. A list is a subtree w/o a root, which we simulate
* with a nil root node. A nil node is a node with token == null.
*/
public boolean isNil() { return token == null; }
/** Compute string for single node */
public String toString() {
String typeName = VecMathParser.VOCABULARY.getSymbolicName(getNodeType());
typeName = typeName == null ? token.getText() : typeName;
return token != null ? "<" +typeName +", '" + token.getText() +"'>": "nil";
}
/** Compute string for a whole tree */
public String toStringTree() {
if (children == null || children.size() == 0) return this.toString();
StringBuffer buf = new StringBuffer();
if (!isNil()) {
buf.append('(');
buf.append(this.toString());
buf.append(' ');
}
for (int i = 0; i < children.size(); i++) {
AST t = (AST) children.get(i); // normalized (unnamed) children
if (i>0) buf.append(' ');
buf.append(t.toStringTree());
}
if (!isNil()) buf.append(')');
return buf.toString();
}
}
The Test class
package walking.v4.vecmath_ast;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import walking.v4.vecmath_ast.antlr.VecMathLexer;
import walking.v4.vecmath_ast.antlr.VecMathParser;
import walking.v4.vecmath_ast.impl.AST;
import walking.v4.vecmath_ast.impl.VecMathBuildASTVisitor;
public class Test {
public static void main(String[] args) throws Exception {
CharStream input = CharStreams.fromFileName(args[0]);
VecMathLexer lexer = new VecMathLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
VecMathParser parser = new VecMathParser(tokens);
ParseTree tree = parser.prog();
for (AST ast : new VecMathBuildASTVisitor().visit(tree).getChildren()) {
System.out.println(ast.toStringTree());
}
}
}
Test input
x = 3 + 4
y = 3 + 4 + 5
a = 3 * 4
a = 3 * 4 * 5
c = 3 * 4 + 5
print x * [2, 3, 4]
print x * [2+5, 3, 4]
yields:
(<=, '='> <ID, 'x'> (<+, '+'> <INT, '3'> <INT, '4'>))
(<=, '='> <ID, 'y'> (<+, '+'> (<+, '+'> <INT, '3'> <INT, '4'>) <INT, '5'>))
(<=, '='> <ID, 'a'> (<*, '*'> <INT, '3'> <INT, '4'>))
(<=, '='> <ID, 'a'> (<*, '*'> (<*, '*'> <INT, '3'> <INT, '4'>) <INT, '5'>))
(<=, '='> <ID, 'c'> (<+, '+'> (<*, '*'> <INT, '3'> <INT, '4'>) <INT, '5'>))
(<print, 'print'> (<*, '*'> <ID, 'x'> (<VEC, 'VEC'> <INT, '2'> <INT, '3'> <INT, '4'>)))
(<print, 'print'> (<*, '*'> <ID, 'x'> (<VEC, 'VEC'> (<+, '+'> <INT, '2'> <INT, '5'>) <INT, '3'> <INT, '4'>)))

How to create dependant dropndown/selection cell In GWT celltable 2.3?

I am using gwt2.3 celltable.
In my celltable there will be multiple column out of that few columns are dependent.
ex. Name and address columns are dependent
Name and address columns contains my custom selection cells.
In Name column : 1 cell contains jon,tom,steve
when Name cell contains jon then I want to set US & UK in address cell
if user changes Name cell to tom then I want to set india & china in address cell
if user changes Name cell to steve then I want to set japana & bhutan in address cell
I want to change dependent data from address cell when name cells selection changes.
How I can achieve this thing? Any sample code or pointers to do this?
This solution is for GWT 2.5, but it should probably work in 2.3.
I think the best is that you modify your RecordInfo element when you change the selection on the first column. You can do it similar to this in your CustomSelectionCell:
#Override
public void onBrowserEvent(Context context, Element parent, C value, NativeEvent event, ValueUpdater<C> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if (BrowserEvents.CHANGE.equals(event.getType())) {
Xxxxx newValue = getSelectedValueXxxxx();
valueUpdater.update(newValue);
}
}
Then where you use your cell add a fieldUpdater like this, that will update the RecordInfo with the new value and ask to redraw the row:
column.setFieldUpdater(new FieldUpdater<.....>() {
....
recordInfo.setXxxxx(newValue);
cellTable.redrawRow(index);
....
});
This will call the render of the other CustomSelectionCell, in there you will be able to check if the value of the RecordInfo has changed and update the seletion values as needed. Example:
#Override
public void render(Context context, C value, SafeHtmlBuilder sb) {
if (!value.getXxxxx().equals(this.lastValue)) {
this.items = loadItemsForValueXxxx(value.getXxxxx());
}
.... // Your usual render.
}
Be careful when you changed the items to set a default selected item too.
This is my implementation of DynamicSelectionCell.
DynamicSelectionCell allows you to render different options for different rows in the same GWT table.
Use a single DynamicSelectionCell object and use the addOption method to add options for each row. Options are stored in a Map with the Key being the row number.
For each row $i in the table the options stored in the Map for key $i are rendered.
Works on DataGrid, CellTable.
CODE
public class DynamicSelectionCell extends AbstractInputCell<String, String> {
public TreeMap<Integer, List<String>> optionsMap = new TreeMap<Integer, List<String>>();
interface Template extends SafeHtmlTemplates {
#Template("<option value=\"{0}\">{0}</option>")
SafeHtml deselected(String option);
#Template("<option value=\"{0}\" selected=\"selected\">{0}</option>")
SafeHtml selected(String option);
}
private static Template template;
private TreeMap<Integer, HashMap<String, Integer>> indexForOption = new TreeMap<Integer, HashMap<String, Integer>>();
/**
* Construct a new {#link SelectionCell} with the specified options.
*
* #param options the options in the cell
*/
public DynamicSelectionCell() {
super("change");
if (template == null) {
template = GWT.create(Template.class);
}
}
public void addOption(List<String> newOps, int key){
optionsMap.put(key, newOps);
HashMap<String, Integer> localIndexForOption = new HashMap<String, Integer>();
indexForOption.put(ind, localIndexForOption);
refreshIndexes();
}
public void removeOption(int index){
optionsMap.remove(index);
refreshIndexes();
}
private void refreshIndexes(){
int ind=0;
for (List<String> options : optionsMap.values()){
HashMap<String, Integer> localIndexForOption = new HashMap<String, Integer>();
indexForOption.put(ind, localIndexForOption);
int index = 0;
for (String option : options) {
localIndexForOption.put(option, index++);
}
ind++;
}
}
#Override
public void onBrowserEvent(Context context, Element parent, String value,
NativeEvent event, ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
String type = event.getType();
if ("change".equals(type)) {
Object key = context.getKey();
SelectElement select = parent.getFirstChild().cast();
String newValue = optionsMap.get(context.getIndex()).get(select.getSelectedIndex());
setViewData(key, newValue);
finishEditing(parent, newValue, key, valueUpdater);
if (valueUpdater != null) {
valueUpdater.update(newValue);
}
}
}
#Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
// Get the view data.
Object key = context.getKey();
String viewData = getViewData(key);
if (viewData != null && viewData.equals(value)) {
clearViewData(key);
viewData = null;
}
int selectedIndex = getSelectedIndex(viewData == null ? value : viewData, context.getIndex());
sb.appendHtmlConstant("<select tabindex=\"-1\">");
int index = 0;
try{
for (String option : optionsMap.get(context.getIndex())) {
if (index++ == selectedIndex) {
sb.append(template.selected(option));
} else {
sb.append(template.deselected(option));
}
}
}catch(Exception e){
System.out.println("error");
}
sb.appendHtmlConstant("</select>");
}
private int getSelectedIndex(String value, int ind) {
Integer index = indexForOption.get(ind).get(value);
if (index == null) {
return -1;
}
return index.intValue();
}
}
Varun Tulsian's answer is very good, but the code is incomplete.
The DynamicSelectionCell stores each rows' options in a map. When the cell updates or renders itself, it matches the row index from your Context to its matching row list in your map.
For posterity, see the simplified and updated version below:
public class DynamicSelectionCell extends AbstractInputCell<String, String> {
interface Template extends SafeHtmlTemplates {
#Template("<option value=\"{0}\">{0}</option>")
SafeHtml deselected(String option);
#Template("<option value=\"{0}\" selected=\"selected\">{0}</option>")
SafeHtml selected(String option);
}
private static Template template;
/**
* key: rowIndex
* value: List of options to show for this row
*/
public TreeMap<Integer, List<String>> optionsMap = new TreeMap<Integer, List<String>>();
/**
* Construct a new {#link SelectionCell} with the specified options.
*
*/
public DynamicSelectionCell() {
super("change");
if (template == null) {
template = GWT.create(Template.class);
}
}
public void addOptions(List<String> newOps, int rowIndex) {
optionsMap.put(rowIndex, newOps);
}
public void removeOptions(int rowIndex) {
optionsMap.remove(rowIndex);
}
#Override
public void onBrowserEvent(Context context, Element parent, String value,
NativeEvent event, ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
String type = event.getType();
if ("change".equals(type)) {
Object key = context.getKey();
SelectElement select = parent.getFirstChild().cast();
String newValue = optionsMap.get(context.getIndex()).get(select.getSelectedIndex());
setViewData(key, newValue);
finishEditing(parent, newValue, key, valueUpdater);
if (valueUpdater != null) {
valueUpdater.update(newValue);
}
}
}
#Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
// Get the view data.
Object key = context.getKey();
String viewData = getViewData(key);
if (viewData != null && viewData.equals(value)) {
clearViewData(key);
viewData = null;
}
int selectedIndex = getSelectedIndex(viewData == null ? value : viewData, context.getIndex());
sb.appendHtmlConstant("<select tabindex=\"-1\">");
int index = 0;
try {
for (String option : optionsMap.get(context.getIndex())) {
if (index++ == selectedIndex) {
sb.append(template.selected(option));
} else {
sb.append(template.deselected(option));
}
}
} catch (Exception e) {
System.out.println("error");
}
sb.appendHtmlConstant("</select>");
}
private int getSelectedIndex(String value, int rowIndex) {
if (optionsMap.get(rowIndex) == null) {
return -1;
}
return optionsMap.get(rowIndex).indexOf(value);
}
}
Or you could do something like creating a custom cell, which has methods you can call in the getValue of that particular column
say
final DynamicSelectionCell selection = new DynamicSelectionCell("...");
Column<GraphFilterCondition, String> operandColumn=new Column<GraphFilterCondition, String>(selection) {
#Override
public String getValue(FilterCondition object) {
if(object.getWhereCol()!=null){
((DynamicSelectionCell)this.getCell()).addOptions(new String[]{">","<",">="});
}
if(object.getWhereCondition()!=null){
return object.getWhereCondition().getGuiName();
}
return "";
}
};
This should work I guess.
Also check this other question

Categories

Resources