/*
* Copyright (C) 2000-2012 InfoChamp System Corporation
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package org.gk.ui.client.com.tree.xml;
import java.util.List;
import java.util.Map;
import jfreecode.gwt.event.client.bus.EventBus;
import jfreecode.gwt.event.client.bus.EventObject;
import jfreecode.gwt.event.client.bus.EventProcess;
import org.gk.ui.client.com.CoreIC;
import org.gk.ui.client.com.IC;
import org.gk.ui.client.com.form.gkMap;
import org.gk.ui.client.com.utils.StringUtils;
import com.extjs.gxt.ui.client.core.XDOM;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.data.ModelIconProvider;
import com.extjs.gxt.ui.client.data.ModelKeyProvider;
import com.extjs.gxt.ui.client.dnd.DND.Operation;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.MenuEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.event.TreePanelEvent;
import com.extjs.gxt.ui.client.store.TreeStore;
import com.extjs.gxt.ui.client.util.IconHelper;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.menu.Menu;
import com.extjs.gxt.ui.client.widget.menu.MenuItem;
import com.extjs.gxt.ui.client.widget.treepanel.TreePanel;
import com.extjs.gxt.ui.client.widget.treepanel.TreePanel.TreeNode;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.NamedNodeMap;
import com.google.gwt.xml.client.Node;
import com.google.gwt.xml.client.NodeList;
import com.google.gwt.xml.client.XMLParser;
/**
* <title>XML樹控元件</title>
*
* <pre>
* 將XML轉成樹控元件的呈現方式,並支持本身節點拖拉,
* 接收外部拖拉進來的節點
* </pre>
*
* @author I21890
* @since 2010/08/14
*/
public abstract class gkXMLTreePanelIC extends ContentPanel implements IC {
protected CoreIC core;
protected TreePanel tree;
protected TreeStore<ModelData> store;
// XML's Document
protected Document doc;
public static final String NAME = "name"; // 顯示的節點名稱
public static final String TEXT = "text"; //
public static final String NODE = "node"; // XML節點屬性ID
public static final String ID = "id"; // 唯一的識別ID
public static final String ICON = "icon", PARENT_NODE = "parentNode",
NODE_DATA = "nodeData", ELEMENT_ID = "elementId";
protected static final int KeyCodeF2 = 113;
protected static final String EditTree = "editTree";
protected boolean inEdit = false; // 樹節點是否為正在修改名字狀態
protected boolean hasChildren = false; // 設定是否有子節點
public static interface Event {
public final static String getXMLInfo = ".getXMLInfo";
public final static String onClick = ".onClick";
public final static String onMouseOver = ".onMouseOver";
public final static String onDoubleClick = ".onDoubleClick";
public final static String afterLoad = ".afterLoad";
}
public String evtGetXMLInfo() {
return getId() + Event.getXMLInfo;
}
public String evtOnClick() {
return getId() + Event.onClick;
}
public String evtOnMouseOver() {
return getId() + Event.onMouseOver;
}
public String evtOnDoubleClick() {
return getId() + Event.onDoubleClick;
}
public String evtAfterLoad() {
return getId() + Event.afterLoad;
}
@Override
public CoreIC core() {
return core;
}
public gkXMLTreePanelIC(String id) {
this(id, NAME);
}
public gkXMLTreePanelIC(String id, String displayProperty) {
setId(id);
core = new CoreIC(this);
core.init();
createTree("");
// createTreeHandler();
setLayout(new FitLayout());
tree.setDisplayProperty(displayProperty);
createTreeNodeIconProvider(tree);
tree.setStateful(true);
add(tree);
setLayout(new FitLayout());
}
public gkXMLTreePanelIC(String id, String displayProperty, String treeType) {
setId(id);
core = new CoreIC(this);
core.init();
createTree(treeType);
createTreeHandler();
setLayout(new FitLayout());
tree.setDisplayProperty(displayProperty);
createTreeNodeIconProvider(tree);
tree.setStateful(true);
add(tree);
setLayout(new FitLayout());
}
public gkXMLTreePanelIC(String id, String displayProperty, String treeType,
String dragable) {
setId(id);
core = new CoreIC(this);
core.init();
createTree(treeType);
if (dragable.equals("true")) {
createTreeHandler();
}
setLayout(new FitLayout());
tree.setDisplayProperty(displayProperty);
createTreeNodeIconProvider(tree);
tree.setStateful(true);
tree.getStyle().setLeafIcon(iconProvider("file"));
add(tree);
setLayout(new FitLayout());
}
public TreePanel getTree() {
return tree;
}
@Override
public void setInfo(Object info) {
// 清掉所有節點
store.removeAll();
// 透過GWT API解析整個XML字串轉成doc物件
if (RegExp.compile("</?[rR][oO][oO][tT][a-z0-9]*[^<>]*>").test(
info.toString())) {
doc = XMLParser.parse(info.toString());
} else {
doc = XMLParser.parse("<ROOT id='" + getId() + "' name='"
+ getRootNodeInfo().get(NAME) + "'>" + info + "</ROOT>");
}
XMLParser.removeWhitespace(doc.getFirstChild());
// 解析doc's node資訊放到store裡面
parseXmlDoc2Store(doc, store);
// 重新繪製tree
tree.repaint();
tree.fireEvent(Events.Render);
}
/**
* 設定是否有子節點,預設為否
*
* @param hasChildren
*/
public void setHasChildren(boolean hasChildren) {
this.hasChildren = hasChildren;
}
@Override
public Object getInfo() {
return doc.toString();
}
@Override
public void bindEvent() {
}
/**
* 展開樹控元件所有節點
*/
protected void expandAll() {
// 必須在樹控元件繪製到畫面才能展開所有節點,因此必須透過監聽 Render事件
tree.addListener(Events.Render, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
((TreePanel) be.getSource()).expandAll();
}
});
}
/**
* 建立樹控元件,使用EditTreePanel才能編輯樹節點名稱
*
* @param treeType
*/
public void createTree(String treeType) {
store = new TreeStore<ModelData>();
store.setKeyProvider(new ModelKeyProvider<ModelData>() {
@Override
public String getKey(ModelData model) {
if (!model.getProperties().containsKey(ID))
model.set(ID, DOM.createUniqueId());
return model.get(ID) + "";
}
});
tree = new TreePanel(store) {
@Override
protected void onRender(Element target, int index) {
clearState();
super.onRender(target, index);
}
@Override
public String getId() {
return gkXMLTreePanelIC.this.getId();
}
@Override
public void onComponentEvent(ComponentEvent ce) {
TreePanelEvent tpe = (TreePanelEvent) ce;
int type = ce.getEventTypeInt();
switch (type) {
case com.google.gwt.user.client.Event.ONMOUSEOVER:
onMouseOver(tpe);
break;
case com.google.gwt.user.client.Event.ONCLICK:
onClick(tpe);
break;
case com.google.gwt.user.client.Event.ONDBLCLICK:
onDoubleClick(tpe);
break;
case com.google.gwt.user.client.Event.ONSCROLL:
onScroll(tpe);
break;
case com.google.gwt.user.client.Event.ONFOCUS:
onFocus(ce);
break;
}
view.onEvent(tpe);
}
protected void onMouseOver(TreePanelEvent tpe) {
if (tpe.getNode() != null) {
Map nodeInfo = (Map) tpe.getNode().getModel();
nodeInfo.put(ELEMENT_ID, tpe.getNode().getElement().getId());
// 滑鼠移到樹節點上,將該節點資訊發佈出去
core.getBus().publish(
new EventObject(evtOnMouseOver(), nodeInfo));
}
}
@Override
protected void onClick(TreePanelEvent tpe) {
super.onClick(tpe);
if (tpe.getNode() == null) {
return;
}
Map nodeInfo = (gkMap) tpe.getNode().getModel();
nodeInfo.put(ELEMENT_ID, tpe.getNode().getElement().getId());
// 點選樹節點,將該節點資訊發佈出去
core.getBus().publish(new EventObject(evtOnClick(), nodeInfo));
}
@Override
protected void onDoubleClick(TreePanelEvent tpe) {
super.onDoubleClick(tpe);
if (tpe.getNode() == null) {
return;
}
Map nodeInfo = (gkMap) tpe.getNode().getModel();
nodeInfo.put(ELEMENT_ID, tpe.getNode().getElement().getId());
// 雙擊樹節點,將該節點資訊發佈出去
core.getBus().publish(
new EventObject(evtOnDoubleClick(), nodeInfo));
}
@Override
protected boolean hasChildren(ModelData model) {
if (hasChildren) {
return super.hasChildren(model);
}
return true;
}
};
// 設定樹的類型為可編輯樹
if (treeType.equals(EditTree)) {
tree.disableTextSelection(false);
// 監聽到F2鍵時修改動作inEdit表示目前是否為編輯中的狀態
tree.addListener(Events.OnKeyDown, new Listener<TreePanelEvent>() {
@Override
public void handleEvent(TreePanelEvent be) {
if (be.getKeyCode() == KeyCodeF2 && !inEdit) {
inEdit = true;
Map modelData = tree.getSelectionModel()
.getSelectedItem().getProperties();
String treeNodeId = tree.getId() + "_"
+ modelData.get(ID);
Element el = DOM.getElementById(treeNodeId);
TreeNode tn = tree.findNode(el);
Map treeNode = new gkMap();
treeNode.put(modelData.get(ID), tn);
onEdit(treeNode);
}
}
});
}
}
private void onEdit(Map treeNode) {
TreeNode tn = (TreeNode) treeNode.get((tree.getSelectionModel()
.getSelectedItem().getProperties().get(ID) + ""));
// 取得當前的節點
com.google.gwt.dom.client.Node nd = tn.getElement().getChild(0);
// 生成一個input的element
final Element input = DOM.createElement("input");
// 取得當前節點的最後一個元素,也即時寫有名稱的那個span
final com.google.gwt.dom.client.Node childnd = nd.getLastChild();
// 保存span中的內容
final String oldString = childnd.getFirstChild().getNodeValue();
// 預設設定新的input為原來span中的內容
input.setAttribute("value", oldString);
// 設定新input的ID以便後面抓取
input.setAttribute("id", "newText");
// 保存原來span的element
final com.google.gwt.dom.client.Node oldNode = childnd.getFirstChild()
.cloneNode(true);
// 把原來span的位置替換成input以實現能編輯的功能
childnd.replaceChild(input, childnd.getFirstChild());
// 設定字符可以被選取
tree.disableTextSelection(false);
// 聚焦當前input
input.focus();
// 對新的input註冊失去焦點的動作和keydown的動作
DOM.sinkEvents(input, com.google.gwt.user.client.Event.ONBLUR
| com.google.gwt.user.client.Event.ONKEYDOWN
| com.google.gwt.user.client.Event.ONMOUSEOVER
| com.google.gwt.user.client.Event.ONMOUSEOUT);
// 監聽新的input,當失去焦點或者按下enter的時候做更新動作
DOM.setEventListener(input, new EventListener() {
// 判斷滑鼠是否在input框內, 防止點擊的時候觸發onblur動作
boolean mouseIn = true;
@Override
public void onBrowserEvent(com.google.gwt.user.client.Event event) {
if (event.getTypeInt() == com.google.gwt.user.client.Event.ONMOUSEOUT) {
mouseIn = false;
}
if (event.getTypeInt() == com.google.gwt.user.client.Event.ONMOUSEOVER) {
mouseIn = true;
}
if (event.getTypeInt() == com.google.gwt.user.client.Event.ONBLUR
|| event.getKeyCode() == KeyCodes.KEY_ENTER) {
if (event.getKeyCode() == KeyCodes.KEY_ENTER || !mouseIn) {
// 取得此時input內的內容
String newString = getValue();
// 如果為空則還原原來的內容
if (newString.equals("")) {
input.setAttribute("value", oldString);
}
// 把原span的內容換成input中的內容
oldNode.setNodeValue(newString);
// 再用span把input替換回來
childnd.replaceChild(oldNode, input);
// 對tree的modelData進行設定,其中會發布一個事件,ap可以根據此事件再做自己的調整
setModelData(newString, tree);
inEdit = false;
}
}
}
});
}
/**
* 建立根節點
*
* @param doc
* @param store
*/
protected void parseXmlDoc2Store(Document doc, TreeStore store) {
Node rootNode = doc.getFirstChild();
gkMap rootNodeMap = createTreeNode(rootNode);
// 將root node資料attribute放到 treeNode
store.add(rootNodeMap, false);
preprocessNode(store, rootNode, rootNodeMap);
}
/**
* 處理節點
*
* @param store
* @param xmlNode
* @param treeNode
*/
public void preprocessNode(TreeStore store, Node xmlNode, ModelData treeNode) {
NodeList xmlNodeList = xmlNode.getChildNodes();
// 將所有子節點加入
for (int i = 0; i < xmlNodeList.getLength(); i++) {
Node subXMLNode = xmlNodeList.item(i);
if (subXMLNode.getNodeType() == Node.COMMENT_NODE
|| subXMLNode.getNodeType() == Node.TEXT_NODE) {
continue;
}
gkMap subTreeNode = createTreeNode(subXMLNode);
store.add(treeNode, subTreeNode, true);
preprocessNode(store, subXMLNode, subTreeNode);
}
}
public void expandAllNode(boolean expand) {
getTree().getSelectionModel().deselectAll();
if (expand) {
getTree().disableEvents(true);
getTree().expandAll();
getTree().disableEvents(false);
} else {
getTree().disableEvents(true);
getTree().collapseAll();
getTree().disableEvents(false);
}
}
public void expandNode(String value, boolean expand) {
ModelData md = getTree().getStore().findModel(value);
assert (md != null);
getTree().getSelectionModel().deselectAll();
getTree().getSelectionModel().select(true, md);
getTree().setExpanded(md, expand);
}
public void expandNode(String key, String value, boolean expand) {
ModelData md = getTree().getStore().findModel(key, value);
assert (md != null);
getTree().getSelectionModel().deselectAll();
getTree().getSelectionModel().select(true, md);
getTree().setExpanded(md, expand);
}
/**
* <pre>
* 取得根節點資訊,讓子類別決定根節點的名稱和顯示的文字
* 例如
* new dejgMap("name", "ui").fill("text", "GUL元件庫");
* name 節點名稱、text 節點顯示文字
* </pre>
*
* @return gkMap
*/
protected abstract gkMap getRootNodeInfo();
/**
* 定義XML哪些Node Name是容器,可以拖放節點進去 null表示都可以,new dejgList()表示都不行
*
* @return List
*/
protected List getFolderNode() {
return null;
}
/**
* 準備放到TreeNode的資訊
*
* @param xmlNode
* @return gkMap
*/
public gkMap createTreeNode(Node xmlNode) {
gkMap info = new gkMap();
NamedNodeMap nameMap = xmlNode.getAttributes();
for (int i = 0; i < nameMap.getLength(); i++) {
Node attrNode = nameMap.item(i);
info.put(attrNode.getNodeName(), attrNode.getNodeValue());
}
Node name = xmlNode.getAttributes().getNamedItem(NAME);
Node id = xmlNode.getAttributes().getNamedItem(ID);
Node icon = xmlNode.getAttributes().getNamedItem(ICON);
info.put(NAME,
name == null ? xmlNode.getNodeName() : name.getNodeValue());
info.put(NODE, xmlNode);
info.put(ID, id == null ? DOM.createUniqueId() : id.getNodeValue());
info.put(ICON,
icon == null ? xmlNode.getNodeName() : icon.getNodeValue());
return info;
}
/**
* 幫忙處理拖拉Tree節點時,將樹節點裡面包含的XML Node也進行更新處理
*/
protected void createTreeHandler() {
new gkTreeHandler(tree, Operation.MOVE) {
@Override
public void update(Map info) {
publishXmlUpdate();
}
};
}
protected void publishXmlUpdate() {
// 拖拉後,將整個XML字串整理好,發佈出去
String xmlInfo = StringUtils.xmlPretty(doc.getFirstChild() + "");
core.getBus().publish(new EventObject(evtGetXMLInfo(), xmlInfo));
}
public gkXMLTreePanelIC() {
this(XDOM.getUniqueId());
}
public void createTreeNodeIconProvider(TreePanel tree) {
// 根據model裡面的icon屬性,設定圖示 (如果有的話)
tree.setIconProvider(new ModelIconProvider<ModelData>() {
@Override
public AbstractImagePrototype getIcon(ModelData model) {
String icon = model.get(ICON);
return icon == null ? null : iconProvider(icon);
}
});
}
/**
* 讓子類別改寫提供icon
*
* @param name
* @return AbstractImagePrototype
*/
protected abstract AbstractImagePrototype iconProvider(String name);
/**
* 建立滑鼠右鍵選單,將原本按左鍵會隱藏視窗功能Disable
*
* @return Menu
*/
protected Menu createMenu() {
return new Menu() {
{
sinkEvents(com.google.gwt.user.client.Event.KEYEVENTS);
}
@Override
public void onBrowserEvent(com.google.gwt.user.client.Event event) {
if (event.getKeyCode() == KeyCodes.KEY_LEFT) {
event.stopPropagation();
} else {
super.onBrowserEvent(event);
}
}
};
}
public void addMenuItem(String txt, ImageResource imgRes, EventProcess ep) {
addMenuItem(txt, AbstractImagePrototype.create(imgRes), ep);
}
/**
* 增加滑鼠右鍵選單項目
*
* @param txt
* @param iconStyle
* @param ep
*/
public void addMenuItem(String txt, String iconStyle, EventProcess ep) {
addMenuItem(txt, IconHelper.create(iconStyle), ep);
}
/**
* 增加滑鼠右鍵選單項目
*
* @param txt
* @param imgIcon
* @param ep
*/
protected void addMenuItem(final String txt,
AbstractImagePrototype imgIcon, final EventProcess ep) {
if (getContextMenu() == null) {
setContextMenu(createMenu());
}
Menu menu = getContextMenu();
MenuItem item = new MenuItem();
item.setText(txt);
item.setIcon(imgIcon);
item.addSelectionListener(new SelectionListener<MenuEvent>() {
@Override
public void componentSelected(MenuEvent ce) {
// 如果Tree沒有Item被點選,md將會是 null
ModelData md = getTree().getSelectionModel().getSelectedItem();
gkMap m = new gkMap();
if (md != null) {
m.putAll(md.getProperties());
}
ep.execute(txt, new EventObject(txt, m));
}
});
menu.add(item);
}
/**
* 取得input中的vaule GWT中取得的value不是實時變化的
*
* @return string
*/
private native String getValue()/*-{
var a = $wnd.document.getElementById('newText');
return a.value;
}-*/;
/**
* 修改名稱後的更改TreeModel
*
* @param value
* @param tp
*/
private void setModelData(String value, TreePanel tp) {
Map changeData = tp.getSelectionModel().getSelectedItem()
.getProperties();
changeData.put("newName", value);
EventBus.get().publish(new EventObject("changeNodeName", changeData));
}
@Override
public void linkInfo(Object info) {
throw new RuntimeException("not implemented yet!");
}
}