IDEMPIERE-1379:Info Windows should have their own icon/image

move manage imageCache to ManageImageCache
improve imageCache for
  + don't load local image to cache
  + handle exception when wrong link or disconnect network
  + when many MImage has same extenal url, just load one time
  + tab for "info window" has same icon when display in daskboad view
This commit is contained in:
hieplq 2015-08-09 02:31:13 +08:00
parent e99cff2f02
commit 517f0299e1
7 changed files with 357 additions and 49 deletions

View File

@ -17,7 +17,6 @@
package org.adempiere.webui.adwindow;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -35,9 +34,7 @@ import org.compiere.model.MToolBarButton;
import org.compiere.model.MToolBarButtonRestrict;
import org.compiere.model.MWindow;
import org.compiere.model.X_AD_ToolBarButton;
import org.compiere.util.CCache;
import org.compiere.util.Env;
import org.zkoss.image.AImage;
import org.zkoss.zk.ui.Component;
/**
@ -59,8 +56,6 @@ public class ADWindow extends AbstractUIPart
private Component windowPanelComponent;
private MImage image;
private static final CCache<Integer, AImage> imageCache = new CCache<Integer, AImage>(null, "WindowImageCache", 5, false);
private Map<Integer, List<String>> tabToolbarRestricMap = new HashMap<Integer, List<String>>();
@ -129,23 +124,6 @@ public class ADWindow extends AbstractUIPart
return image;
}
public AImage getAImage() throws IOException {
MImage image = getMImage();
AImage aImage = null;
if (image != null) {
synchronized (imageCache) {
aImage = imageCache.get(image.getAD_Image_ID());
}
if (aImage == null) {
aImage = new AImage(image.getName(), image.getData());
synchronized (imageCache) {
imageCache.put(image.getAD_Image_ID(), aImage);
}
}
}
return aImage;
}
protected Component doCreatePart(Component parent)
{
windowPanelComponent = windowContent.createPart(parent);

View File

@ -17,6 +17,13 @@
package org.adempiere.webui.component;
import org.adempiere.webui.adwindow.ADWindow;
import org.adempiere.webui.util.ManageImageCache;
import org.compiere.model.MImage;
import org.compiere.model.MInfoWindow;
import org.zkoss.image.Image;
import org.zkoss.zul.impl.LabelImageElement;
/**
*
* @author <a href="mailto:agramdass@gmail.com">Ashley G Ramdass</a>
@ -40,6 +47,11 @@ public class Tab extends org.zkoss.zul.Tab
}
public void setDecorateInfo (DecorateInfo decorateInfo){
if (decorateInfo != null)
decorateInfo.decorate(this);
}
@Override
public void onClose() {
Tabpanel tp = (Tabpanel) getLinkedPanel();
@ -48,4 +60,50 @@ public class Tab extends org.zkoss.zul.Tab
}
}
/**
* class contain decorate info
* at the moment, has only image info
* at the moment, it's use to transfer decorate info from info window, standard window, report, process,... to tab
* @author hieplq
*
*/
public static class DecorateInfo {
private String imageKey;
private String imageIntenalUrl;
public void decorate (LabelImageElement comp){
if (imageIntenalUrl != null)
comp.setImage(imageIntenalUrl);
else if (imageKey != null){
Image ico = ManageImageCache.instance().getImage(imageKey);
if (ico != null)
comp.setImageContent(ico);
}
}
public DecorateInfo (MImage imageData){
imageIntenalUrl = ManageImageCache.getImageInternalUrl(imageData);
if (imageIntenalUrl == null)
imageKey = ManageImageCache.instance().loadImage(imageData);
}
public DecorateInfo (String imagePath){
imageIntenalUrl = ManageImageCache.getImageInternalUrl(imagePath);
if (imageIntenalUrl == null)
imageKey = imagePath;
}
/**
* Helper method for create decorate info from adWindow info
* @param adWindow
* @return
*/
public static DecorateInfo get (ADWindow adWindow){
return adWindow == null?null:new DecorateInfo(adWindow.getMImage());
}
public static DecorateInfo get (MInfoWindow mInfo){
return mInfo==null?null:new DecorateInfo(mInfo.getImageURL());
}
}
}

View File

@ -243,7 +243,7 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
windowContainer.createPart(windowArea);
homeTab = new Tabpanel();
windowContainer.addWindow(homeTab, Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Home")), false);
windowContainer.addWindow(homeTab, Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Home")), false, null);
homeTab.getLinkedTab().setSclass("desktop-hometab");
homeTab.setSclass("desktop-home-tabpanel");
BusyDialog busyDialog = new BusyDialog();

View File

@ -13,7 +13,6 @@
*****************************************************************************/
package org.adempiere.webui.desktop;
import java.io.IOException;
import java.util.List;
import org.adempiere.util.Callback;
@ -22,6 +21,7 @@ import org.adempiere.webui.adwindow.ADWindow;
import org.adempiere.webui.apps.ProcessDialog;
import org.adempiere.webui.apps.wf.WFPanel;
import org.adempiere.webui.component.DesktopTabpanel;
import org.adempiere.webui.component.Tab.DecorateInfo;
import org.adempiere.webui.component.Tabbox;
import org.adempiere.webui.component.Tabpanel;
import org.adempiere.webui.component.Window;
@ -32,11 +32,11 @@ import org.adempiere.webui.panel.InfoPanel;
import org.adempiere.webui.part.WindowContainer;
import org.adempiere.webui.window.FDialog;
import org.adempiere.webui.window.WTask;
import org.compiere.model.MInfoWindow;
import org.compiere.model.MQuery;
import org.compiere.model.MTask;
import org.compiere.util.Env;
import org.compiere.wf.MWorkflow;
import org.zkoss.image.AImage;
import org.zkoss.util.media.AMedia;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
@ -73,7 +73,7 @@ public abstract class TabbedDesktop extends AbstractDesktop {
String title = pd.getTitle();
pd.setTitle(null);
preOpenNewTab();
windowContainer.addWindow(tabPanel, title, true);
windowContainer.addWindow(tabPanel, title, true, null);
Events.postEvent(ProcessDialog.ON_INITIAL_FOCUS_EVENT, pd, null);
}
return pd;
@ -93,7 +93,7 @@ public abstract class TabbedDesktop extends AbstractDesktop {
//do not show window title when open as tab
form.setTitle(null);
preOpenNewTab();
windowContainer.addWindow(tabPanel, form.getFormName(), true);
windowContainer.addWindow(tabPanel, form.getFormName(), true, null);
form.focus();
} else {
form.setAttribute(Window.MODE_KEY, form.getWindowMode());
@ -117,7 +117,7 @@ public abstract class TabbedDesktop extends AbstractDesktop {
String title = infoPanel.getTitle();
infoPanel.setTitle(null);
preOpenNewTab();
windowContainer.addWindow(tabPanel, title, true);
windowContainer.addWindow(tabPanel, title, true, DecorateInfo.get(MInfoWindow.get(infoId, null)));
infoPanel.focus();
} else {
FDialog.error(0, "NotValid");
@ -135,7 +135,7 @@ public abstract class TabbedDesktop extends AbstractDesktop {
DesktopTabpanel tabPanel = new DesktopTabpanel();
p.setParent(tabPanel);
preOpenNewTab();
windowContainer.addWindow(tabPanel, p.getWorkflow().get_Translation(MWorkflow.COLUMNNAME_Name), true);
windowContainer.addWindow(tabPanel, p.getWorkflow().get_Translation(MWorkflow.COLUMNNAME_Name), true, null);
}
/**
@ -160,7 +160,8 @@ public abstract class TabbedDesktop extends AbstractDesktop {
final DesktopTabpanel tabPanel = new DesktopTabpanel();
String id = AdempiereIdGenerator.escapeId(adWindow.getTitle());
tabPanel.setId(id+"_"+adWindow.getADWindowContent().getWindowNo());
final Tab tab = windowContainer.addWindow(tabPanel, adWindow.getTitle(), true);
final Tab tab = windowContainer.addWindow(tabPanel, adWindow.getTitle(), true, DecorateInfo.get(adWindow));
tab.setClosable(false);
final OpenWindowRunnable runnable = new OpenWindowRunnable(adWindow, tab, tabPanel, callback);
preOpenNewTab();
@ -233,7 +234,7 @@ public abstract class TabbedDesktop extends AbstractDesktop {
Tabpanel tabPanel = new Tabpanel();
window.setParent(tabPanel);
preOpenNewTab();
windowContainer.addWindow(tabPanel, title, closeable);
windowContainer.addWindow(tabPanel, title, closeable, null);
}
/**
@ -245,7 +246,7 @@ public abstract class TabbedDesktop extends AbstractDesktop {
final ADWindow wnd = new ADWindow(Env.getCtx(), AD_Window_ID, query);
final DesktopTabpanel tabPanel = new DesktopTabpanel();
final Tab tab = windowContainer.insertAfter(windowContainer.getSelectedTab(), tabPanel, wnd.getTitle(), true, true);
final Tab tab = windowContainer.insertAfter(windowContainer.getSelectedTab(), tabPanel, wnd.getTitle(), true, true, DecorateInfo.get(wnd));
tab.setClosable(false);
final OpenWindowRunnable runnable = new OpenWindowRunnable(wnd, tab, tabPanel, null);
preOpenNewTab();
@ -277,9 +278,9 @@ public abstract class TabbedDesktop extends AbstractDesktop {
window.setTitle(null);
preOpenNewTab();
if (Window.INSERT_NEXT.equals(window.getAttribute(Window.INSERT_POSITION_KEY)))
windowContainer.insertAfter(windowContainer.getSelectedTab(), tabPanel, title, true, true);
windowContainer.insertAfter(windowContainer.getSelectedTab(), tabPanel, title, true, true, null);
else
windowContainer.addWindow(tabPanel, title, true);
windowContainer.addWindow(tabPanel, title, true, null);
if (window instanceof IHelpContext)
Events.sendEvent(new Event(WindowContainer.ON_WINDOW_CONTAINER_SELECTION_CHANGED_EVENT, window));
}
@ -381,13 +382,6 @@ public abstract class TabbedDesktop extends AbstractDesktop {
public void run() {
if (adWindow.createPart(tabPanel) != null ) {
tab.setClosable(true);
if (adWindow.getMImage() != null) {
try {
AImage aImage = adWindow.getAImage();
tab.setImageContent(aImage);
} catch (IOException e) {
}
}
if (callback != null) {
callback.onCallback(adWindow);
}

View File

@ -17,6 +17,7 @@ import java.util.List;
import org.adempiere.webui.component.Menupopup;
import org.adempiere.webui.component.Tab;
import org.adempiere.webui.component.Tab.DecorateInfo;
import org.adempiere.webui.component.Tabbox;
import org.adempiere.webui.component.Tabpanel;
import org.adempiere.webui.component.Tabpanels;
@ -101,15 +102,63 @@ public class WindowContainer extends AbstractUIPart
return tabbox;
}
/**
* @deprecated keep for compatible, replace by {@link #addWindow(Component, String, boolean, DecorateInfo)}
* @param comp
* @param title
* @param closeable
* @return
*/
public Tab addWindow(Component comp, String title, boolean closeable){
return addWindow(comp, title, closeable, true, null);
}
/**
* @deprecated keep for compatible, replace by {@link #addWindow(Component, String, boolean, boolean, DecorateInfo)}
* @param comp
* @param title
* @param closeable
* @param enable
* @return
*/
public Tab addWindow(Component comp, String title, boolean closeable, boolean enable) {
return addWindow(comp, title, closeable, true, null);
}
/**
* @deprecated keep for compatible, replace by {@link #insertBefore(Tab, Component, String, boolean, boolean, DecorateInfo)}
* @param refTab
* @param comp
* @param title
* @param closeable
* @param enable
* @return
*/
public Tab insertBefore(Tab refTab, Component comp, String title, boolean closeable, boolean enable){
return insertBefore(refTab, comp, title, closeable, enable, null);
}
/**
* @deprecated keep for compatible, replace by {@link #insertAfter(Component, String, boolean, boolean, DecorateInfo)}
* @param refTab
* @param comp
* @param title
* @param closeable
* @param enable
* @return
*/
public Tab insertAfter(Tab refTab, Component comp, String title, boolean closeable, boolean enable){
return insertAfter(refTab, comp, title, closeable, enable, null);
}
/**
*
* @param comp
* @param title
* @param closeable
*/
public Tab addWindow(Component comp, String title, boolean closeable)
public Tab addWindow(Component comp, String title, boolean closeable, DecorateInfo decorateInfo)
{
return addWindow(comp, title, closeable, true);
return addWindow(comp, title, closeable, true, decorateInfo);
}
/**
@ -119,9 +168,9 @@ public class WindowContainer extends AbstractUIPart
* @param closeable
* @param enable
*/
public Tab addWindow(Component comp, String title, boolean closeable, boolean enable)
public Tab addWindow(Component comp, String title, boolean closeable, boolean enable, DecorateInfo decorateInfo)
{
return insertBefore(null, comp, title, closeable, enable);
return insertBefore(null, comp, title, closeable, enable, decorateInfo);
}
/**
@ -132,9 +181,10 @@ public class WindowContainer extends AbstractUIPart
* @param closeable
* @param enable
*/
public Tab insertBefore(Tab refTab, Component comp, String title, boolean closeable, boolean enable)
public Tab insertBefore(Tab refTab, Component comp, String title, boolean closeable, boolean enable, DecorateInfo decorateInfo)
{
final Tab tab = new Tab();
tab.setDecorateInfo(decorateInfo);
if (title != null)
{
setTabTitle(title, tab);
@ -318,12 +368,12 @@ public class WindowContainer extends AbstractUIPart
* @param closeable
* @param enable
*/
public Tab insertAfter(Tab refTab, Component comp, String title, boolean closeable, boolean enable)
public Tab insertAfter(Tab refTab, Component comp, String title, boolean closeable, boolean enable, DecorateInfo decorateInfo)
{
if (refTab == null)
return addWindow(comp, title, closeable, enable);
return addWindow(comp, title, closeable, enable, decorateInfo);
else
return insertBefore((Tab)refTab.getNextSibling(), comp, title, closeable, enable);
return insertBefore((Tab)refTab.getNextSibling(), comp, title, closeable, enable, decorateInfo);
}
/**

View File

@ -0,0 +1,223 @@
/**********************************************************************
* This file is part of iDempiere ERP Open Source *
* http://www.idempiere.org *
* *
* Copyright (C) Contributors *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301, USA. *
**********************************************************************/
package org.adempiere.webui.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Level;
import org.adempiere.base.Core;
import org.compiere.model.MImage;
import org.compiere.util.CCache;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.zkoss.image.AImage;
import org.zkoss.image.Image;
/**
* Normal image can come from inside system or from outside system.
* with image from outside for performance we will cache it.
* this class for manage image cache and provide help function relate
* @author hieplq
*
*/
public class ManageImageCache {
protected static transient CLogger log = CLogger.getCLogger (ManageImageCache.class);
/**
* this cache is don't expire, if must restart cache when has update image.
* better use a timer, example reset cache after 10 minute has update. it help user can change a batch of image and reset one time.
*/
private final CCache<String, Image> imageCache = new CCache<String, Image>(null, "WindowImageCache", 50, 0, false);
private static ManageImageCache instance;
/**
* get instance
* @return
*/
public static ManageImageCache instance(){
if (instance == null){
synchronized (ManageImageCache.class){
if (instance == null)
instance = new ManageImageCache();
}
}
return instance;
}
/**
* investigate image path of MImage, if path is a internal return internal url other return null
* @param image
* @return
*/
public static String getImageInternalUrl (MImage image){
if (image == null)
return null;
return getImageInternalUrl(image.getImageURL());
}
/**
* investigate image path, if path is a internal return internal url other return null
* @param url
* @return
*/
public static String getImageInternalUrl (String url){
if (url == null || url.trim().length() == 0 || url.indexOf("://") > 0)
return null;
URL urlRsource = Core.getResourceFinder().getResource(url);
return urlRsource == null?null:urlRsource.getPath();
}
/**
* Load image from url
* @param imagePath
* @return
*/
protected static byte [] loadImageData (String imagePath){
byte [] data = null;
URLConnection conn;
try {
URL url = new URL(imagePath);
conn = url.openConnection();
conn.setUseCaches(false);
InputStream is = conn.getInputStream();
byte[] buffer = new byte[1024*8]; // 8kB
ByteArrayOutputStream os = new ByteArrayOutputStream();
int length = -1;
while ((length = is.read(buffer)) != -1)
os.write(buffer, 0, length);
is.close();
data = os.toByteArray();
os.close();
} catch (IOException e) {
if (log.isLoggable(Level.CONFIG)) log.config (e.toString());
}
return data;
}
/**
* if image is don't in cache, load it (imagePath can id of MImage)
* @param imagePath
* @return image load from path. null when has any exception
*/
public Image getImage(String imagePath){
if (imagePath == null || imagePath.trim().length() == 0)
return null;
Image aImage = null;
boolean hasCache = false;
synchronized (imageCache) {
hasCache = imageCache.containsKey(imagePath);
}
if (!hasCache) {
try{
int mImageId = Integer.parseInt(imagePath);
loadImage(MImage.get(Env.getCtx(), mImageId));
}catch (NumberFormatException ex){
loadExtend(imagePath);
}
}
synchronized (imageCache) {
aImage = imageCache.get(imagePath);
}
return aImage;
}
/**
* if MImage contain extend image or binary image data, load it into cache and return key
* other return null
* @param mImage
* @return
*/
public String loadImage (MImage mImage){
if (mImage == null)
return null;
boolean hasCache = false;
String strId = String.valueOf(mImage.get_ID());
synchronized (imageCache) {
hasCache = imageCache.containsKey(strId);
}
if(hasCache){
return strId;
}
if (mImage.getBinaryData() != null){
synchronized (imageCache) {
Image loadImage = null;
try {
loadImage = new AImage (mImage.getName(), mImage.getBinaryData());
} catch (IOException e) {
// do nothing treat image as null
}
imageCache.put(String.valueOf(mImage.get_ID()), loadImage);
}
return strId;
}else if (mImage.getImageURL() != null && mImage.getImageURL().trim().length() > 0 && getImageInternalUrl(mImage.getImageURL()) == null){
synchronized (imageCache) {
hasCache = imageCache.containsKey(mImage.getImageURL());
}
if (!hasCache){
loadExtend (mImage.getImageURL());
}
return mImage.getImageURL();
}
return null;
}
/**
* load extend image into cache
* @param imagePath
*/
protected void loadExtend (String imagePath){
byte[] data = loadImageData(imagePath);
AImage aImage = null;
// when can't load image (by incorrect url or disconnect or any exception, just set image as null
if (data != null)
try {
aImage = new AImage(imagePath, data);
} catch (IOException e) {
aImage = null;
}
synchronized (imageCache) {
imageCache.put(imagePath, aImage);
}
}
}

View File

@ -15,6 +15,7 @@ package org.adempiere.webui.util;
import java.net.URL;
import java.util.Enumeration;
import java.util.regex.Pattern;
import org.adempiere.base.IResourceFinder;
import org.adempiere.webui.WebUIActivator;
@ -40,6 +41,7 @@ public class WebUIResourceFinder implements IResourceFinder {
return WebUIActivator.getBundleContext().getBundle().findEntries(path, pattern, false);
}
protected Pattern patternOnlyName = Pattern.compile("\\w+\\.\\w+");
@Override
public URL getResource(String name) {
if ("images/iDempiereHR.png".equals(name) || "images/iDempiere.png".equals(name)) {
@ -47,6 +49,9 @@ public class WebUIResourceFinder implements IResourceFinder {
}
Enumeration<URL> e = find(name);
URL url = e != null && e.hasMoreElements() ? e.nextElement() : null;
if (url == null && patternOnlyName.matcher(name).matches()){
name = "images/" + name;
}
if (url == null && name.startsWith("org/compiere/images")) {
String t = name.substring("org/compiere/".length());
t = ThemeManager.getThemeResource(t);