I am building a YAML editor in a RichTextFX CodeArea (which I believe is a type of TextArea). I'd like to maintain the current level of indentation when I hit Enter. So for example:
- item1: foo
item1A:
subElement: A
<there should be 4 spaces here after I hit enter>
Alternatively, if somebody already has built a yaml editor in RichTextFX or JavaFX, I'd love a link to it.
When I hit enter after this, to add a subelement unde
You can try the following:
Pattern whiteSpace = Pattern.compile( "^\\s+" );
codeArea.addEventFilter( KeyEvent.KEY_PRESSED, KE ->
{
if ( KE.getCode() == KeyCode.ENTER )
{
Matcher m = whiteSpace.matcher( codeArea.getParagraph( codeArea.getCurrentParagraph() ).getSegments().get( 0 ) );
if ( m.find() ) Platform.runLater( () -> codeArea.insertText( codeArea.getCaretPosition(), m.group() ) );
}
});
Here's a little helper class I wrote that will keep indentation or even increase indentation one level, if the current line ends in a given suffix. It uses the InputMap-API of RichTextFX.
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.fxmisc.richtext.GenericStyledArea;
import org.fxmisc.wellbehaved.event.EventPattern;
import org.fxmisc.wellbehaved.event.InputMap;
import org.fxmisc.wellbehaved.event.Nodes;
import javafx.scene.input.KeyCode;
public class RichTextFXAutoIndenter {
private static final Pattern LEADING_WHITESPACE = Pattern.compile( "^\\s+" );
public static void install(GenericStyledArea<?, String, ?> genericStyledArea, String ... indentSuffixes) {
Nodes.addInputMap(genericStyledArea, InputMap.consume(EventPattern.keyPressed(KeyCode.ENTER), event -> enterPressed(genericStyledArea, indentSuffixes)));
}
private static void enterPressed(GenericStyledArea<?, String, ?> genericStyledArea, String[] indentSuffixes) {
genericStyledArea.replaceSelection("\n" + getIndentation(genericStyledArea, indentSuffixes));
}
private static String getIndentation(GenericStyledArea<?, String, ?> genericStyledArea, String[] indentSuffixes) {
String currentLine = genericStyledArea.getParagraph(genericStyledArea.getCurrentParagraph()).getSegments().get(0);
Matcher m = LEADING_WHITESPACE.matcher(currentLine);
String indent = "";
if (m.find()) {
indent = m.group();
}
if (Arrays.stream(indentSuffixes).anyMatch(suffix -> currentLine.trim().endsWith(suffix))) {
indent += "\t";
}
return indent;
}
}
Related
Say I have an enum
public enum E {A,B,C}
Is it possible to add another value, say D, by AspectJ?
After googling around, it seems that there used to be a way to hack the private static field $VALUES, then call the constructor(String, int) by reflection, but seems not working with 1.7 anymore.
Here are several links:
http://www.javaspecialists.eu/archive/Issue161.html (provided by #WimDeblauwe )
and this: http://www.jroller.com/VelkaVrana/entry/modify_enum_with_reflection
Actually, I recommend you to refactor the source code, maybe adding a collection of valid region IDs to each enumeration value. This should be straightforward enough for subsequent merging if you use Git and not some old-school SCM tool like SVN.
Maybe it would even make sense to use a dynamic data structure altogether instead of an enum if it is clear that in the future the list of commands is dynamic. But that should go into the upstream code base. I am sure the devs will accept a good patch or pull request if prepared cleanly.
Remember: Trying to avoid refactoring is usually a bad smell, a symptom of an illness, not a solution. I prefer solutions to symptomatic workarounds. Clean code rules and software craftsmanship attitude demand that.
Having said the above, now here is what you can do. It should work under JDK 7/8 and I found it on Jérôme Kehrli's blog (please be sure to add the bugfix mentioned in one of the comments below the article).
Enum extender utility:
package de.scrum_master.util;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;
public class DynamicEnumExtender {
private static ReflectionFactory reflectionFactory =
ReflectionFactory.getReflectionFactory();
private static void setFailsafeFieldValue(Field field, Object target, Object value)
throws NoSuchFieldException, IllegalAccessException
{
// let's make the field accessible
field.setAccessible(true);
// next we change the modifier in the Field instance to
// not be final anymore, thus tricking reflection into
// letting us modify the static final field
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
int modifiers = modifiersField.getInt(field);
// blank out the final bit in the modifiers int
modifiers &= ~Modifier.FINAL;
modifiersField.setInt(field, modifiers);
FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
fa.set(target, value);
}
private static void blankField(Class<?> enumClass, String fieldName)
throws NoSuchFieldException, IllegalAccessException
{
for (Field field : Class.class.getDeclaredFields()) {
if (field.getName().contains(fieldName)) {
AccessibleObject.setAccessible(new Field[] { field }, true);
setFailsafeFieldValue(field, enumClass, null);
break;
}
}
}
private static void cleanEnumCache(Class<?> enumClass)
throws NoSuchFieldException, IllegalAccessException
{
blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
blankField(enumClass, "enumConstants"); // IBM JDK
}
private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes)
throws NoSuchMethodException
{
Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
parameterTypes[0] = String.class;
parameterTypes[1] = int.class;
System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
return reflectionFactory.newConstructorAccessor(enumClass .getDeclaredConstructor(parameterTypes));
}
private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues)
throws Exception
{
Object[] parms = new Object[additionalValues.length + 2];
parms[0] = value;
parms[1] = Integer.valueOf(ordinal);
System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
}
/**
* Add an enum instance to the enum class given as argument
*
* #param <T> the type of the enum (implicit)
* #param enumType the class of the enum to be modified
* #param enumName the name of the new enum instance to be added to the class
*/
#SuppressWarnings("unchecked")
public static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName) {
// 0. Sanity checks
if (!Enum.class.isAssignableFrom(enumType))
throw new RuntimeException("class " + enumType + " is not an instance of Enum");
// 1. Lookup "$VALUES" holder in enum class and get previous enum
// instances
Field valuesField = null;
Field[] fields = enumType.getDeclaredFields();
for (Field field : fields) {
if (field.getName().contains("$VALUES")) {
valuesField = field;
break;
}
}
AccessibleObject.setAccessible(new Field[] { valuesField }, true);
try {
// 2. Copy it
T[] previousValues = (T[]) valuesField.get(enumType);
List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
// 3. build new enum
T newValue = (T) makeEnum(
enumType, // The target enum class
enumName, // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED
values.size(), new Class<?>[] {}, // could be used to pass values to the enum constuctor if needed
new Object[] {} // could be used to pass values to the enum constuctor if needed
);
// 4. add new value
values.add(newValue);
// 5. Set new values field
setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));
// 6. Clean enum cache
cleanEnumCache(enumType);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage(), e);
}
}
}
Sample application & enum:
package de.scrum_master.app;
/** In honour of "The Secret of Monkey Island"... ;-) */
public enum Command {
OPEN, CLOSE, PUSH, PULL, WALK_TO, PICK_UP, TALK_TO, GIVE, USE, LOOK_AT, TURN_ON, TURN_OFF
}
package de.scrum_master.app;
public class Server {
public void executeCommand(Command command) {
System.out.println("Executing command " + command);
}
}
package de.scrum_master.app;
public class Client {
private Server server;
public Client(Server server) {
this.server = server;
}
public void issueCommand(String command) {
server.executeCommand(
Command.valueOf(
command.toUpperCase().replace(' ', '_')
)
);
}
public static void main(String[] args) {
Client client = new Client(new Server());
client.issueCommand("use");
client.issueCommand("walk to");
client.issueCommand("undress");
client.issueCommand("sleep");
}
}
Console output with original enum:
Executing command USE
Executing command WALK_TO
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant de.scrum_master.app.Command.UNDRESS
at java.lang.Enum.valueOf(Enum.java:236)
at de.scrum_master.app.Command.valueOf(Command.java:1)
at de.scrum_master.app.Client.issueCommand(Client.java:12)
at de.scrum_master.app.Client.main(Client.java:22)
Now you can either add an aspect with an advice executed after the enum class was loaded or just call this manually in your application before extended enum values are to be used for the first time. Here I am showing how it can be done in an aspect.
Enum extender aspect:
package de.scrum_master.aspect;
import de.scrum_master.app.Command;
import de.scrum_master.util.DynamicEnumExtender;
public aspect CommandExtender {
after() : staticinitialization(Command) {
System.out.println(thisJoinPoint);
DynamicEnumExtender.addEnum(Command.class, "UNDRESS");
DynamicEnumExtender.addEnum(Command.class, "SLEEP");
DynamicEnumExtender.addEnum(Command.class, "WAKE_UP");
DynamicEnumExtender.addEnum(Command.class, "DRESS");
}
}
Console output with extended enum:
staticinitialization(de.scrum_master.app.Command.<clinit>)
Executing command USE
Executing command WALK_TO
Executing command UNDRESS
Executing command SLEEP
Et voilà! ;-)
I am trying to use Rome for parsing some rss feeds. One of the rss feeds says
specifies 0.91 as the version and no custom xml namespace is defined but the entries still have a custom element in them. Can I use Rome to parse such custom tags without any defined namespace?
Thanks.
Yes. You need to write a custom parser to do it.
Let's say you want to handle the elements customString and customDate. Start by extending the Item class to store the custom elements.
package com.example;
import com.sun.syndication.feed.rss.Item;
import java.util.Date;
public class CustomItem extends Item {
private String _customString;
private Date _customDate;
public String getCustomString() {
return _customString;
}
public void setCustomString(String customString) {
_customString = customString;
}
public Date getCustomDate() {
return _customDate;
}
public void setCustomDate(Date customDate) {
_customDate = customDate;
}
}
Then write the parser. You also need to handle any standard elements you want to parse.
package com.example;
import com.example.CustomItem;
import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.io.WireFeedParser;
import com.sun.syndication.io.impl.DateParser;
import com.sun.syndication.io.impl.RSS091UserlandParser;
import org.jdom.Element;
public class CustomParser extends RSS091UserlandParser implements WireFeedParser {
public CustomItem parseItem(Element rssRoot, Element eItem) {
CustomItem customItem = new CustomItem();
// Standard elements
Item standardItem = super.parseItem(rssRoot, eItem);
customItem.setTitle(standardItem.getTitle());
customItem.setDescription(standardItem.getDescription());
// Non-standard elements
Element e = eItem.getChild("customString", getRSSNamespace());
if (e != null) {
customItem.setCustomString(e.getText());
}
e = eItem.getChild("customDate", getRSSNamespace());
if (e != null) {
customItem.setCustomDate(DateParser.parseDate(e.getText()));
}
return customItem;
}
}
Finally you need to define your parser in a rome.properties file along with parsers for any other type of feed you want to handle.
# Feed Parser implementation classes
#
WireFeedParser.classes=com.example.CustomParser
You need to write a custom converter to get the data.
part of code same to above.
Start by extending the Item class to store the custom elements.
package com.example;
import com.sun.syndication.feed.rss.Item;
import java.util.Date;
public class CustomItem extends Item {
private String _customString;
private Date _customDate;
public String getCustomString() {
return _customString;
}
public void setCustomString(String customString) {
_customString = customString;
}
public Date getCustomDate() {
return _customDate;
}
public void setCustomDate(Date customDate) {
_customDate = customDate;
}
}
Then write the parser. You also need to handle any standard elements you want to parse.
package com.example;
import com.example.CustomItem;
import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.io.WireFeedParser;
import com.sun.syndication.io.impl.DateParser;
import com.sun.syndication.io.impl.RSS091UserlandParser;
import org.jdom.Element;
public class CustomParser extends RSS091UserlandParser implements WireFeedParser {
public CustomItem parseItem(Element rssRoot, Element eItem) {
CustomItem customItem = new CustomItem();
// Standard elements
Item standardItem = super.parseItem(rssRoot, eItem);
customItem.setTitle(standardItem.getTitle());
customItem.setDescription(standardItem.getDescription());
// Non-standard elements
Element e = eItem.getChild("customString", getRSSNamespace());
if (e != null) {
customItem.setCustomString(e.getText());
}
e = eItem.getChild("customDate", getRSSNamespace());
if (e != null) {
customItem.setCustomDate(DateParser.parseDate(e.getText()));
}
return customItem;
}
}
Then write the converter.
public class CustomConverter extends ConverterForRSS20 {
protected SyndEntry createSyndEntry(Item item) {
List<HashMap<String,String>> temp = new ArrayList<HashMap<String,String>>();
SyndEntry syndEntry = super.createSyndEntry(item);
customItem customItem = (customItem)item;
List<String> customList = new ArrayList<String>();
customList.add( customItem.getCustomString() );
//set to empty attribute ex foreignmarkup
syndEntry.setForeignMarkup( customList );
return syndEntry;
}
}
Finally you need to define your parser in a rome.properties file along with parsers for any other type of feed you want to handle.
# Feed Parser implementation classes
#
WireFeedParser.classes=com.example.CustomParser
# Feed Converter implementation classes
#
Converter.classes=com.example.CustomConverter
Then you can get value.
SyndFeed feed = input.build(new XmlReader(feedUrl));
List<SyndEntryImpl> entrys = feed.getEntries();
for(SyndEntryImpl entry:entrys ){
System.out.println( entry.getForeignMarkup() );
}
I have been trying to play a video that was converted using http://www.mirovideoconverter.com/ to mp4 file , it is woking fine on the simulator but on the ipad i don't see the video.
How can I fix??
attaching Video code :
package com.view.generic
{
import com.constants.Dimentions;
import com.view.AbstractScreen;
import com.view.IScreen;
import com.view.gui.Btn;
import flash.errors.IOError;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.NetStatusEvent;
import flash.media.Video;
import flash.net.NetConnection;
import flash.net.NetStream;
import org.osflash.signals.natives.NativeSignal;
public class VideoMode extends AbstractScreen implements IScreen
{
private var _player:Video;
private var _stream:NetStream;
public function VideoMode()
{
}
override public function start():void{
super.start();
var conn:NetConnection = new NetConnection();
conn.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler)
conn.addEventListener(IOErrorEvent.NETWORK_ERROR, netStatusError)
conn.connect(null);
layoutPlayer();
layoutMenu();
}
override public function stop():void{
_stream.pause();
}
private function layoutMenu():void{
var playBtn:Btn = new Btn("video_play_button.png");
addChild(playBtn);
playBtn.x = (Dimentions.HEIGHT -playBtn.width)/2;
playBtn.y = _player.y+_player.height+20;
var clickSignal:NativeSignal = new NativeSignal(playBtn,MouseEvent.CLICK);
clickSignal.add(play);
var fullScrBtn:Btn = new Btn("full_screen.png");
addChild(fullScrBtn);
fullScrBtn.x = _player.width -fullScrBtn.width+_player.x;;
fullScrBtn.y = _player.y+_player.height+20;
var fullScrSignal:NativeSignal = new NativeSignal(fullScrBtn,MouseEvent.CLICK);
fullScrSignal.add(goFullScreen);
}
private function layoutPlayer():void{
_player.width = 400;
_player.height = 300;
_player.x = (Dimentions.HEIGHT -_player.width)/2;
_player.y = 200;
_stream.play("../../../assets/drum_ny.flv");
_stream.pause();
}
private function goFullScreen(e:MouseEvent):void{
if(_player.x == 0){
layoutPlayer()
}else{
_player.x = 0;
_player.y = 0;
_player.width = stage.fullScreenWidth;
_player.height = stage.fullScreenHeight;
}
}
private function play(e:MouseEvent):void{
_stream.resume()
}
private function netStatusHandler(e:NetStatusEvent):void{
if(e.info.code=="NetConnection.Connect.Success"){
_stream = new NetStream(NetConnection(e.target));
_stream.client = this;
_player = new Video();
addChild(_player);
_player.attachNetStream(_stream)
}
}
private function netStatusError(e:IOError):void{
trace(e)
}
override public function destroy():void{
}
public function onMetaData(info:Object):void {
}
}
}
Thank you!
This is likely your problem :
_stream.play("../../../assets/drum_ny.flv");
That file doesn't exist once you compile your app into a .ipa file. Try changing that to a web address of somewhere you can upload it to and if it works, then that's your prob.
The issue was resolved by replacing the FLV file, however I am still not sure why one flv file works and the other does not.
If you encounter such a problem (video works on simulator but not on device) your first bet should be replacing the video source.
Let's say I have a DropDownList with the 50 states in it. I would like to type in the letters "C + O + L" to jump to Colorado, like Firefox and most application do.
Right now, it's jumping from California to Ohio to end with Louisiana... Anybody know an easy way to do that?
Thanks a lot!
You could try to create a custom component that extends the DropDownList and override the offending function to add your own functionality that you want. It's the only way I can think of changing the default functionality.
Like #J_A_X proposed, I modified the DropDownList class, adding a timer that keeps the string that the user is typing for ¾ seconds and then, reset it. Here's my solution :
package MyComps
{
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.utils.setTimeout;
import mx.core.mx_internal;
import spark.components.DropDownList;
use namespace mx_internal;
public class DropDownListKeyboardSelection extends DropDownList
{
private var _duration:Number = 750; // Time in milliseconds before the _str is resetted
private var _timer:Timer;
private var _str:String = '';
public function DropDownListKeyboardSelection()
{
super();
}
override mx_internal function findKey(eventCode:int):Boolean
{
if (!dataProvider || dataProvider.length == 0)
return false;
if (eventCode >= 33 && eventCode <= 126)
{
var matchingIndex:Number;
var keyString:String = String.fromCharCode(eventCode);
// Freshly instantiated or resetted by timerEnded(). In that case, we start the timer
if (_str == '') {
startTimer();
} else {
_timer.reset();
startTimer();
}
// Building the string to find
_str += keyString;
matchingIndex = findStringLoop(_str, 0, dataProvider.length);
// We didn't find the item, loop back to the top
if (matchingIndex == -1)
{
matchingIndex = findStringLoop(keyString, 0, 0);
}
if (matchingIndex != -1)
{
if (isDropDownOpen)
changeHighlightedSelection(matchingIndex);
else
setSelectedIndex(matchingIndex, true);
return true;
}
}
return false;
}
// Let's start the _timer
private function startTimer():void
{
_timer = new Timer(_duration);
_timer.addEventListener(TimerEvent.TIMER, timerEnded);
_timer.start();
}
// Timer ended, let's reset the _str variable
private function timerEnded(event:TimerEvent):void
{
_str = '';
_timer.reset();
}
}
}
I believe this is dependant on the browser (if using the standard list element). You could create an autocomplete field through jquery (although this isn't the same as a drop down list).
I'm attempting to search a combobox based on text entered via a keyboard event. The search is working and the correct result is being selected but I can't seem to get the scrollToIndex to find the correct item which should be the found result (i). It's scrolling to the last letter entered which I believe is the default behavior of a combobox. I think I'm referring to the event target incorrectly. Newbie tearing my hair out. Can you help? Thank you. Here's the function:
private function textin(event:KeyboardEvent):void
{
var combo:ComboBox = event.target as ComboBox;
var source:XMLListCollection = combo.dataProvider as XMLListCollection;
str += String.fromCharCode(event.charCode);
if (str=="") {
combo.selectedIndex = 0;
}
for (var i:int=0; i<source.length; i++) {
if ( source[i].#name.match(new RegExp("^" + str, "i")) ) {
combo.selectedIndex = i;
event.target.scrollToIndex(i);
break;
}
}
}
Control:
<mx:ComboBox keyDown="textin(event);" id="thislist" change="processForm();" dataProvider="{xmllist}"/>
If event.target is a mx.control.ComboBox then it doesn't have a scrollToIndex method, which is a method defined in mx.controls.ListBase, which the ComboBox doesn't inherit from. Check the api reference for the ComboBox. What exactly is the result you a you are trying to achieve here? If you set the selected index of a ComboBox it should display the item at that index.
EDIT: Try getting replacing event.target.scrollToIndex(i) (which should throw an error anyway) and replace it with event.stopImmediatePropagation(). This should prevent whatever the default key handler is from firing and overriding your event handler.
Here is a solution, based on Kerri's code and Ryan Lynch's suggestions. The credit goes to then.
It's working for me, so I will leave the complete code here for the future generations. :)
import com.utils.StringUtils;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.utils.Timer;
import mx.collections.ArrayCollection;
import mx.controls.ComboBox;
public class ExtendedComboBox extends ComboBox
{
private var mSearchText : String = "";
private var mResetStringTimer : Timer;
public function ExtendedComboBox()
{
super();
mResetStringTimer = new Timer( 1000 );
mResetStringTimer.addEventListener( TimerEvent.TIMER, function() : void { mResetStringTimer.stop(); mSearchText = ""; } );
}
override protected function keyDownHandler( aEvent : KeyboardEvent ):void
{
if( aEvent.charCode < 32 )
{
super.keyDownHandler( aEvent );
return;
}
var lComboBox : ComboBox = aEvent.target as ComboBox;
var lDataProvider : ArrayCollection = lComboBox.dataProvider as ArrayCollection;
mSearchText += String.fromCharCode( aEvent.charCode );
if ( StringUtils.IsNullOrEmpty( mSearchText ) )
{
lComboBox.selectedIndex = 0;
aEvent.stopImmediatePropagation();
return;
}
if( mResetStringTimer.running )
mResetStringTimer.reset();
mResetStringTimer.start();
for ( var i : int = 0; i < lDataProvider.length; i++ )
{
if ( lDataProvider[i].label.match( new RegExp( "^" + mSearchText, "i") ) )
{
lComboBox.selectedIndex = i;
aEvent.stopImmediatePropagation();
break;
}
}
}
}
This solution expects an ArrayCollection as the dataProvider and a field named "label" to do the searching. You can create a variable to store the name of the field, and use it like this:
lDataProvider[i][FIELD_NAME_HERE].match( new RegExp( "^" + mSearchText, "i") )
Have fun!