001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.action; 020 021import icy.gui.frame.progress.ProgressFrame; 022import icy.main.Icy; 023import icy.resource.icon.IcyIcon; 024import icy.system.thread.ThreadUtil; 025import icy.util.StringUtil; 026 027import java.awt.event.ActionEvent; 028 029import javax.swing.AbstractAction; 030import javax.swing.Action; 031import javax.swing.JComponent; 032import javax.swing.JLabel; 033import javax.swing.KeyStroke; 034 035import org.pushingpixels.flamingo.api.common.RichTooltip; 036 037/** 038 * Icy basic AbstractAction class. 039 * 040 * @author Stephane 041 */ 042public abstract class IcyAbstractAction extends AbstractAction 043{ 044 /** 045 * Sets the tooltip text of a component from an Action. 046 * 047 * @param c 048 * the Component to set the tooltip text on 049 * @param a 050 * the Action to set the tooltip text from, may be null 051 */ 052 public static void setToolTipTextFromAction(JComponent c, Action a) 053 { 054 if (a != null) 055 { 056 final String longDesc = (String) a.getValue(Action.LONG_DESCRIPTION); 057 final String shortDesc = (String) a.getValue(Action.SHORT_DESCRIPTION); 058 059 if (StringUtil.isEmpty(longDesc)) 060 c.setToolTipText(shortDesc); 061 else 062 c.setToolTipText(longDesc); 063 } 064 } 065 066 /** 067 * 068 */ 069 private static final long serialVersionUID = -8544059445777661407L; 070 071 private static final int DEFAULT_ICON_SIZE = 20; 072 073 /** 074 * The "enabled" property key. 075 */ 076 public static final String ENABLED_KEY = "enabled"; 077 078 /** 079 * internals 080 */ 081 protected boolean bgProcess; 082 protected boolean processing; 083 protected String processMessage; 084 protected ProgressFrame progressFrame; 085 086 public IcyAbstractAction(String name, IcyIcon icon, String description, String longDescription, int keyCode, 087 int modifiers, boolean bgProcess, String processMessage) 088 { 089 super(name, icon); 090 091 // by default we use the name as Action Command 092 putValue(ACTION_COMMAND_KEY, name); 093 if (keyCode != 0) 094 putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(keyCode, modifiers)); 095 if (!StringUtil.isEmpty(description)) 096 putValue(SHORT_DESCRIPTION, description); 097 if (!StringUtil.isEmpty(longDescription)) 098 putValue(LONG_DESCRIPTION, longDescription); 099 100 this.bgProcess = bgProcess; 101 this.processMessage = processMessage; 102 progressFrame = null; 103 processing = false; 104 } 105 106 public IcyAbstractAction(String name, IcyIcon icon, String description, String longDescription, int keyCode, 107 int modifiers) 108 { 109 this(name, icon, description, longDescription, keyCode, modifiers, false, null); 110 } 111 112 public IcyAbstractAction(String name, IcyIcon icon, String description, String longDescription, boolean bgProcess, 113 String processMessage) 114 { 115 this(name, icon, description, longDescription, 0, 0, bgProcess, processMessage); 116 } 117 118 public IcyAbstractAction(String name, IcyIcon icon, String description, boolean bgProcess, String processMessage) 119 { 120 this(name, icon, description, null, 0, 0, bgProcess, processMessage); 121 } 122 123 public IcyAbstractAction(String name, IcyIcon icon, String description, int keyCode, int modifiers) 124 { 125 this(name, icon, description, null, keyCode, modifiers, false, null); 126 } 127 128 public IcyAbstractAction(String name, IcyIcon icon, String description, int keyCode) 129 { 130 this(name, icon, description, null, keyCode, 0, false, null); 131 } 132 133 public IcyAbstractAction(String name, IcyIcon icon, String description, String longDescription) 134 { 135 this(name, icon, description, longDescription, 0, 0, false, null); 136 } 137 138 public IcyAbstractAction(String name, IcyIcon icon, String description) 139 { 140 this(name, icon, description, null, 0, 0, false, null); 141 } 142 143 public IcyAbstractAction(String name, IcyIcon icon) 144 { 145 this(name, icon, null, null, 0, 0, false, null); 146 } 147 148 public IcyAbstractAction(String name) 149 { 150 this(name, null, null, null, 0, 0, false, null); 151 } 152 153 /** 154 * @deprecated Use {@link #IcyAbstractAction(String, IcyIcon, String, int, int)} instead. 155 */ 156 @Deprecated 157 public IcyAbstractAction(String name, String iconName, String description, int keyCode, int modifiers) 158 { 159 this(name, new IcyIcon(iconName, DEFAULT_ICON_SIZE), description, null, keyCode, modifiers, false, null); 160 } 161 162 /** 163 * @deprecated Use {@link #IcyAbstractAction(String, IcyIcon, String, int)} instead. 164 */ 165 @Deprecated 166 public IcyAbstractAction(String name, String iconName, String description, int keyCode) 167 { 168 this(name, new IcyIcon(iconName, DEFAULT_ICON_SIZE), description, null, keyCode, 0, false, null); 169 } 170 171 /** 172 * @deprecated Use {@link #IcyAbstractAction(String, IcyIcon, String)} instead. 173 */ 174 @Deprecated 175 public IcyAbstractAction(String name, String iconName, String description) 176 { 177 this(name, new IcyIcon(iconName, DEFAULT_ICON_SIZE), description, null, 0, 0, false, null); 178 } 179 180 /** 181 * @deprecated Use {@link #IcyAbstractAction(String, IcyIcon)} instead. 182 */ 183 @Deprecated 184 public IcyAbstractAction(String name, String iconName) 185 { 186 this(name, new IcyIcon(iconName, DEFAULT_ICON_SIZE), null, null, 0, 0, false, null); 187 } 188 189 /** 190 * @return true if this action process is done in a background thread. 191 */ 192 public boolean isBgProcess() 193 { 194 return bgProcess; 195 } 196 197 /** 198 * Set to true if you want to action to be processed in a background thread. 199 * 200 * @see #isBgProcess() 201 * @see #setProcessMessage(String) 202 */ 203 public void setBgProcess(boolean bgProcess) 204 { 205 this.bgProcess = bgProcess; 206 } 207 208 /** 209 * @return the process message to display for background action process. 210 * @see #setProcessMessage(String) 211 * @see #isBgProcess() 212 */ 213 public String getProcessMessage() 214 { 215 return processMessage; 216 } 217 218 public RichTooltip getRichToolTip() 219 { 220 final String desc = getDescription(); 221 final String longDesc = getLongDescription(); 222 final IcyIcon icon = getIcon(); 223 224 if (StringUtil.isEmpty(desc) && StringUtil.isEmpty(longDesc)) 225 return null; 226 227 final RichTooltip result = new RichTooltip(); 228 229 if (!StringUtil.isEmpty(desc)) 230 result.setTitle(desc); 231 232 if (!StringUtil.isEmpty(longDesc)) 233 { 234 for (String ld : longDesc.split("\n")) 235 result.addDescriptionSection(ld); 236 } 237 238 if (icon != null) 239 result.setMainImage(icon.getImage()); 240 241 return result; 242 } 243 244 /** 245 * Set the process message to display for background action process.<br> 246 * If set to null then no message is displayed (default). 247 * 248 * @see #setBgProcess(boolean) 249 */ 250 public void setProcessMessage(String processMessage) 251 { 252 this.processMessage = processMessage; 253 } 254 255 public void setName(String value) 256 { 257 putValue(Action.NAME, value); 258 } 259 260 public String getName() 261 { 262 return (String) getValue(Action.NAME); 263 } 264 265 public void setIcon(IcyIcon value) 266 { 267 putValue(Action.SMALL_ICON, value); 268 } 269 270 public IcyIcon getIcon() 271 { 272 return (IcyIcon) getValue(Action.SMALL_ICON); 273 } 274 275 public void setDescription(String value) 276 { 277 putValue(Action.SHORT_DESCRIPTION, value); 278 } 279 280 public String getDescription() 281 { 282 return (String) getValue(Action.SHORT_DESCRIPTION); 283 } 284 285 public void setLongDescription(String value) 286 { 287 putValue(Action.LONG_DESCRIPTION, value); 288 } 289 290 public String getLongDescription() 291 { 292 return (String) getValue(Action.LONG_DESCRIPTION); 293 } 294 295 public void setAccelerator(int keyCode, int modifiers) 296 { 297 if (keyCode != 0) 298 putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(keyCode, modifiers)); 299 else 300 putValue(ACCELERATOR_KEY, null); 301 } 302 303 public void setAccelerator(int keyCode) 304 { 305 setAccelerator(keyCode, 0); 306 } 307 308 /** 309 * Returns the selected state (for toggle button type). 310 */ 311 public boolean isSelected() 312 { 313 return Boolean.TRUE.equals(getValue(SELECTED_KEY)); 314 } 315 316 /** 317 * Sets the selected state (for toggle button type). 318 */ 319 public void setSelected(boolean value) 320 { 321 putValue(SELECTED_KEY, Boolean.valueOf(value)); 322 } 323 324 /** 325 * Returns the {@link KeyStroke} for this action (can be null). 326 */ 327 public KeyStroke getKeyStroke() 328 { 329 return (KeyStroke) getValue(ACCELERATOR_KEY); 330 } 331 332 /** 333 * @return true if action is currently processing.<br> 334 * Meaningful only when {@link #setBgProcess(boolean)} is set to true) 335 */ 336 public boolean isProcessing() 337 { 338 return processing; 339 } 340 341 @Override 342 public boolean isEnabled() 343 { 344 return enabled && !processing; 345 } 346 347 @Override 348 public void setEnabled(boolean value) 349 { 350 if (enabled != value) 351 { 352 final boolean wasEnabled = isEnabled(); 353 enabled = value; 354 final boolean isEnabled = isEnabled(); 355 356 // notify enabled change 357 if (wasEnabled != isEnabled) 358 firePropertyChange(ENABLED_KEY, Boolean.valueOf(wasEnabled), Boolean.valueOf(isEnabled)); 359 } 360 } 361 362 protected void setProcessing(boolean value) 363 { 364 if (processing != value) 365 { 366 final boolean wasEnabled = isEnabled(); 367 processing = value; 368 final boolean isEnabled = isEnabled(); 369 370 // notify enabled change 371 if (wasEnabled != isEnabled) 372 firePropertyChange(ENABLED_KEY, Boolean.valueOf(wasEnabled), Boolean.valueOf(isEnabled)); 373 } 374 } 375 376 /** 377 * Helper method to fire enabled changed event (this force component refresh) 378 */ 379 public void enabledChanged() 380 { 381 final boolean enabledState = isEnabled(); 382 383 // notify enabled change 384 firePropertyChange(ENABLED_KEY, Boolean.valueOf(!enabledState), Boolean.valueOf(enabledState)); 385 } 386 387 /** 388 * Returns a {@link JLabel} component representing the action. 389 */ 390 public JLabel getLabelComponent(boolean wantIcon, boolean wantText) 391 { 392 final JLabel result = new JLabel(); 393 394 if (wantIcon) 395 result.setIcon(getIcon()); 396 if (wantText) 397 result.setText(getName()); 398 399 final String desc = getDescription(); 400 401 if (StringUtil.isEmpty(desc)) 402 result.setToolTipText(getLongDescription()); 403 else 404 result.setToolTipText(getDescription()); 405 406 return result; 407 } 408 409 /** 410 * Returns a {@link JLabel} component representing the action. 411 */ 412 public JLabel getLabelComponent() 413 { 414 return getLabelComponent(true, true); 415 } 416 417 @Override 418 public void actionPerformed(ActionEvent e) 419 { 420 setProcessing(true); 421 422 // background execution ? 423 if (isBgProcess()) 424 { 425 final ActionEvent event = e; 426 427 ThreadUtil.bgRun(new Runnable() 428 { 429 @Override 430 public void run() 431 { 432 final String mess = getProcessMessage(); 433 434 if (!StringUtil.isEmpty(mess) && !Icy.getMainInterface().isHeadLess()) 435 progressFrame = new ProgressFrame(mess); 436 else 437 progressFrame = null; 438 439 try 440 { 441 doAction(event); 442 } 443 finally 444 { 445 if (progressFrame != null) 446 progressFrame.close(); 447 448 // need to be done on the EDT (can change the enabled state) 449 ThreadUtil.invokeLater(new Runnable() 450 { 451 @Override 452 public void run() 453 { 454 setProcessing(false); 455 } 456 }); 457 } 458 } 459 }); 460 } 461 else 462 { 463 try 464 { 465 doAction(e); 466 } 467 finally 468 { 469 setProcessing(false); 470 } 471 } 472 } 473 474 /** 475 * Execute action (delayed execution if action requires it) 476 */ 477 public void execute() 478 { 479 actionPerformed(new ActionEvent(this, 0, "")); 480 } 481 482 /** 483 * @deprecated Use {@link #executeNow()} instead 484 */ 485 @Deprecated 486 public boolean doAction() 487 { 488 return doAction(new ActionEvent(this, 0, "")); 489 } 490 491 /** 492 * Execute action now (wait for execution to complete) 493 */ 494 public boolean executeNow() 495 { 496 return doAction(new ActionEvent(this, 0, "")); 497 } 498 499 /** 500 * Action implementation 501 */ 502 protected abstract boolean doAction(ActionEvent e); 503}