You wouldn’t think that having a standard edit menü with Cut, Copy and Paste buttons would be much of a problem in the J2SE world, especially regarding the fact that most standard Swing components have TransferHandlers that support the 3 operations with the standard keyboard shortcuts.
First try was to user TransferHandler.getCopyAction() etc. and create menuitems from the actions. The actions will actually be called but they use the source of the action event to determine the component whose contents should be transfered.
After some searching i found this tutorial. The idea is to have a global property change handler on the global KeyboardFocusManager that tracks the current component that has the focus. The handler than updates the 3 actions with the new target.
I have changed the code a little bit. I removed the methods for adding special client parameters to determine whether cut/copy/paste should be enabled completely. Instead i check whether the component has a TransferHandler installed and if so, whether that handler supports the current action. By doing so my actions have the same behavior like the ones that are the swing components have.
The following code can be used to create menuitems or buttons that mimic the default cut/copy/paste actions on Swing components:
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.TransferHandler;
public final class CutCopyPasteHelper {
private static final FocusedAction CUT_INSTANCE;
private static final FocusedAction COPY_INSTANCE;
private static final FocusedAction PASTE_INSTANCE;
private static final Clipboard CLIPBOARD;
private static final List<FocusedAction> FOCUSED_ACTIONS = new ArrayList<FocusedAction>();
static {
Clipboard clipboard;
try {
clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
} catch (SecurityException e) {
// Don't have access to the clipboard, create a new one
clipboard = new Clipboard("Sandboxed Clipboard");
}
CLIPBOARD = clipboard;
FOCUSED_ACTIONS.add(CUT_INSTANCE = new CutAction());
FOCUSED_ACTIONS.add(COPY_INSTANCE = new CopyAction());
FOCUSED_ACTIONS.add(PASTE_INSTANCE = new PasteAction());
KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(new PropertyChangeHandler());
}
private static Clipboard getClipboard() {
return CLIPBOARD;
}
/**
* Returns an action to perform a cut operation.
*
* @return the cut action
*/
public static Action getCutAction() {
return CUT_INSTANCE;
}
/**
* Returns an action to perform a copy operation.
*
* @return the copy action
*/
public static Action getCopyAction() {
return COPY_INSTANCE;
}
/**
* Returns an action to perform a paste operation.
*
* @return the paste action
*/
public static Action getPasteAction() {
return PASTE_INSTANCE;
}
private static void updateActions(Component focusedComponent) {
for(FocusedAction action : FOCUSED_ACTIONS)
action.update(focusedComponent);
}
private static final class PropertyChangeHandler implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
if (e.getPropertyName() == "permanentFocusOwner") {
updateActions((Component)e.getNewValue());
}
}
}
private CutCopyPasteHelper() {
}
private static abstract class FocusedAction extends AbstractAction {
private static final long serialVersionUID = 9134168335741527777L;
protected JComponent focusedComponent;
public FocusedAction(String name) {
super(name);
}
protected void update(Component permanentFocusOwner) {
this.focusedComponent = (permanentFocusOwner instanceof JComponent) ? (JComponent)permanentFocusOwner : null;
this.checkTarget();
}
public JComponent getFocusedComponent() {
return focusedComponent;
}
public boolean actionSupported(int actionMap, int action) {
return (actionMap & action) != 0;
}
abstract protected void checkTarget();
}
protected static void export(final int exportAction, final JComponent component) {
final Clipboard clipboard = getClipboard();
component.getTransferHandler().exportToClipboard(component, clipboard, exportAction);
}
private static final class CopyAction extends FocusedAction {
private static final long serialVersionUID = 1765740238724009255L;
public CopyAction() {
super("Kopieren");
super.setEnabled(false);
super.putValue(ACTION_COMMAND_KEY, "a6f9f030-c408-4531-857f-dd3aad7b43f6");
super.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
}
@Override
public void actionPerformed(ActionEvent e) {
export(TransferHandler.COPY, focusedComponent);
}
@Override
public void checkTarget() {
super.setEnabled(focusedComponent != null && focusedComponent.getTransferHandler()!= null && actionSupported(focusedComponent.getTransferHandler().getSourceActions(focusedComponent), TransferHandler.COPY));
}
}
private static final class CutAction extends FocusedAction {
private static final long serialVersionUID = 1765740238724009255L;
public CutAction() {
super("Ausschneiden");
super.setEnabled(false);
super.putValue(ACTION_COMMAND_KEY, "aa33f95a-c741-407a-a0ca-0c3aedd33c4d");
super.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK));
}
@Override
public void actionPerformed(ActionEvent e) {
export(TransferHandler.MOVE, focusedComponent);
}
@Override
public void checkTarget() {
super.setEnabled(focusedComponent != null && focusedComponent.getTransferHandler()!= null && actionSupported(focusedComponent.getTransferHandler().getSourceActions(focusedComponent), TransferHandler.MOVE));
}
}
private static final class PasteAction extends FocusedAction {
private static final long serialVersionUID = -2117467682323608417L;
public PasteAction() {
super("Einfügen");
super.setEnabled(false);
super.putValue(ACTION_COMMAND_KEY, "2676413d-8b9e-4d64-b59a-800db6747d80");
super.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK));
}
public void checkTarget() {
super.setEnabled(focusedComponent != null && focusedComponent.getTransferHandler() != null && focusedComponent.getTransferHandler().canImport(focusedComponent, getClipboard().getAvailableDataFlavors()));
}
public void actionPerformed(ActionEvent e) {
Clipboard clipboard = getClipboard();
JComponent target = getFocusedComponent();
target.getTransferHandler().importData(target, clipboard.getContents(null));
}
}
}
What’s missing: The actions aren’t localized and they use Ctrl+? on every system. One could use the default Swing bundles to determine the correct modifier.
Share This
Mysql has a nice “if exists” addition to the drop table statement. If the table to be dropped does not exists, it doesn’t raise an exception but only creates a warning.
In Oracle RDMBS you can emulate this behavior like so:
BEGIN execute immediate 'drop table INSERT_TABLE_NAME_HERE'; EXCEPTION WHEN others THEN IF SQLCODE != -942 THEN RAISE; END IF; END;
/
Ugly, but it works very well.
Share This
Java has the nice Iterable interface (since Java 5, i guess) that allows object oriented loops like
List<String> strings = new ArrayList<String>();
for(String string : strings)
System.out.println(string);
but guess what, a simple array is not iterable…
In case you need one, feel free to use this one:
package ac.simons;
import java.util.Iterator;
public class IterableArray<T> implements Iterable<T> {
private class ArrayIteratorImpl implements Iterator<T> {
private int position = 0;
@Override
public boolean hasNext() {
return data != null && this.position < data.length;
}
@Override
public T next() {
return data[position++];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
private final T[] data;
public IterableArray(T[] data) {
this.data = data;
}
@Override
public Iterator<T> iterator() {
return new ArrayIteratorImpl();
}
}
Share This
Some would say, i have 3 problems
private final static Pattern placeholder = Pattern.compile("#\\{(\\w+?)\\}");
won’t match “Mot#{ö}rhead” for example.
To replace the word character \w you either need the list of possible unicodeblocks like [\p{InLatin}|\p{InEtc}] (you get the codes for the blocks through “Character.UnicodeBlock.forName” or you’re lazy like me and just use the dot:
private final static Pattern placeholder = Pattern.compile("#\\{(.+?)\\}");
Oh what a day… :/
Share This
The default behavior for quite a long time was :hard_breaks in the textilize helper method in rails.
Hard Breaks means: All line breaks are turned into <br />’s.
Somebody changed the textilize helper in “actionpack-2.3.4/lib/action_view/helpers/text_helper.rb” in 2.3.4 and added the ability to pass some options but broke the default behavior here:
def textilize(text, *options)
options ||= [:hard_breaks]
# ...
Options will never be null.
I fixed this by monkey patching the module through the following code in config/initializers/textilizepatch.rb
module ActionView
module Helpers
module TextHelper
def textilize(text, *options)
options = [:hard_breaks] if options == nil || options.size == 0
if text.blank?
""
else
textilized = RedCloth.new(text, options)
textilized.to_html
end
end
end
end
end
Such changes should be tested… *grml*
Share This
You can use
RAILS_DEFAULT_LOGGER.log "foobar"
# or
Rails.logger.log "blah"
outside a controller for logging.
Share This
By default, Rubys heredocs are interpreted as double-quoted strings, that is #{something} is evaluated, \r turns into newline and so forth.
You can change this behaviour by single quoting the heredoc identifier like so:
s = <<-'SINGLE_QUOTED'
#{i'm not interpreted}
SINGLE_QUOTED
Share This
Regarding my question on twitter about completely disabling the creation of stacktrace.log from a Grails application in production mode, here is my answer:
environments {
production {
log4j = {
appenders {
null name:'stacktrace'
}
}
}
development {
}
}
Be careful: This overwrites all other log4j closures that are made outside the environment specific settings. I found no solution for that.
Background: One of my Grails 1.1 app fails to start on a Tomcat application container deployed on an Oracle Enterprise Linux server because it fails to create the stacktrace.log.
What annoyed me is that i needed to dig around in the Grails source code to find the answer to my question in “src/groovy/org/codehaus/groovy/grails/plugins/logging/Log4jConfig.groovy” within the method “createFullstackTraceAppender()”. Should be stated in the documentation (perfect place here).
Edit: If your Grails 1.1 still fails to start(*) look out for xercesImpl.jar and xml-apis.jar or similar somewhere in your Tomcat 5 directory. These are some kind of Java 1.4 compatibility layers which break things in Grails 1.1. Oracle puts these into common/endorsed. After removing them, everything went fine.
(*) Exception was:
java.lang.RuntimeException: XPathFactory#newInstance() failed to create an XPathFactory for the default object model:
http://java.sun.com/jaxp/xpath/dom with the XPathFactoryConfigurationException: javax.xml.xpath.XPathFactoryConfigurationException:
No XPathFctory implementation found for the object model: http://java.sun.com/jaxp/xpath/dom
Share This
Ruby on Rails I18n infrastructure did a great job to internationalization in Rails applications. Most things work right out of the box.
Daily Fratze is fully internationalized and i wanted to use ordinal day numbers in the English version. Pity, there is no template for strftime that works that way.
As i already hat monkey patched a “t” method to all date and time related classes, i came up with the following solution:
Parallel to “en.yml” i now have “en.rb” in my locales folder which gets loaded in environment.rb via
config.i18n.load_path += Dir[File.join(RAILS_ROOT, 'app', 'locales', '*.{yml,rb}')]
The en.rb files defines some procs as translation values like so:
{
:'en' => {
:date => {
:formats => {
:dmy_with_long_month => lambda { |date| "%B #{date.day.ordinalize}, %Y" },
}
},
:time => {
:formats => {
:dmy_with_long_month => lambda { |date| "%B #{date.day.ordinalize}, %Y" },
:dmy_with_full_day_and_long_month_and_time => lambda { |date| "%A, %B #{date.day.ordinalize}, %Y at %H:%M" }
}
}
}
}
Used with the standard I18n tools you’ll end up with the string representation of the proc object. Useless
So time for monkeypatching like so:
module DateTimeSupport
def t(format = nil)
type = self.respond_to?(:sec) ? 'time' : 'date'
formats = I18n.translate(:"#{type}.formats")
format = formats[format.to_sym] if formats && formats[format.to_sym]
I18n.localize(self, :format => format.respond_to?(:call) ? format.call(self) : format)
end
end
class Time
include DateTimeSupport
end
class Date
include DateTimeSupport
end
class DateTime
include DateTimeSupport
end
class ActiveSupport::TimeWithZone
include DateTimeSupport
end
Code resides in a file in config/initializers and gets loaded automatically. It adds a t method to all date and time related classes. The method tries to look up the translation of format just like I18n/Simple does.
If it is proc, it gets called and then passed to I18n, otherwise it the original parameter is used.
That way the t method can use “dmy_with_long_month”, :dmy_with_long_month and any other arbitrary format like “%B %Y” that is not defined in any language file.
Share This
Just some snippets from the doku that I tend to forget:
Run grails on a different port:
grails -Dserver.port=9090 run-app
Run grails with a different environment:
grails prod run-app // production
grails -Dgrails.env=mycustomenv run-app // mycustomenv
Share This