/*
* Copyright 2014, Synthuse.org
* Released under the Apache Version 2.0 License.
*
* last modified by ejakubowski
*/
package org.synthuse;
import java.awt.EventQueue;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import org.synthuse.Api.User32Ex;
import com.sun.jna.platform.win32.WinDef.HWND;
public class XpathManager implements Runnable{
private HWND hwnd = null;
private String enumProperties = null;
private JTextPane windowsXmlTextPane = null;
public static interface Events {
void statusChanged(String status);
void executionCompleted(Object input, String results);
}
public Events events = new Events() {
public void statusChanged(String status){
System.out.println(status);
}
public void executionCompleted(Object input, String results){
}
};
public XpathManager(HWND hwnd, JTextPane windowsXmlTextPane, Events events) {
this.events = events;
this.hwnd = hwnd;
this.windowsXmlTextPane = windowsXmlTextPane;
}
public XpathManager(HWND hwnd, String enumProperties, JTextPane windowsXmlTextPane, Events events) {
this.events = events;
this.hwnd = hwnd;
this.enumProperties = enumProperties;
this.windowsXmlTextPane = windowsXmlTextPane;
}
@Override
public void run() {
String results = SynthuseDlg.config.isUseStrongTextMatching()?buildXpathStatement(true,50,50):buildXpathStatement();
events.executionCompleted(hwnd, results);
}
public static void buildXpathStatementThreaded(HWND hwnd, JTextPane windowsXmlTextPane, Events events) {
Thread t = new Thread(new XpathManager(hwnd, windowsXmlTextPane, events));
t.start();
}
public static void buildXpathStatementThreaded(HWND hwnd, String runtimeId, JTextPane windowsXmlTextPane, Events events) {
Thread t = new Thread(new XpathManager(hwnd, runtimeId, windowsXmlTextPane, events));
t.start();
}
public String compareLongTextString(String rawText) {
String escapedTxtStr = WindowsEnumeratedXml.escapeXmlAttributeValue(rawText);
if (!escapedTxtStr.isEmpty()) {
if (rawText.length() > 20) {// if the raw text is too long only test the first 20 characters
escapedTxtStr = WindowsEnumeratedXml.escapeXmlAttributeValue(rawText.substring(0, 20));
}
}
return escapedTxtStr;
}
public String buildUiaXpathStatement() {
if (enumProperties == null)
return "";
if (enumProperties.isEmpty())
return "";
String builtXpath = "";
String xml = this.windowsXmlTextPane.getText();
WindowInfo wi = new WindowInfo(enumProperties, true);
String onlyRuntimeIdXpath = "//*[@hwnd='" + wi.runtimeId + "']";
List<String> wpfResultList = WindowsEnumeratedXml.evaluateXpathGetValues(xml, onlyRuntimeIdXpath);
//System.out.println("evaluateXpathGetValues1: " + onlyRuntimeIdXpath + " = " + wpfResultList.size());
if (wpfResultList.size() == 0)
return"";
//System.out.println("enumProperties: " + enumProperties);
String typeStr = wi.controlType;
String txtOrig = wi.text;
//String winValueOrig = wpf.getWindowValue(runtimeId);
//System.out.println("text: " + txtOrig);
String txtStr = compareLongTextString(txtOrig);
builtXpath = "//*[@type='" + typeStr + "' and starts-with(@text,'" + txtStr + "')" + "]";
//builtXpath = "//*[@hwnd='" + runtimeId + "']";
wpfResultList = WindowsEnumeratedXml.evaluateXpathGetValues(xml, builtXpath);
//System.out.println("evaluateXpathGetValues2: " + builtXpath + " = " + wpfResultList.size());
if (wpfResultList.size() == 1)
return builtXpath;
return onlyRuntimeIdXpath;
}
public String buildXpathStatement() {
return buildXpathStatement(false, 20, 30);
}
public String buildXpathStatement(boolean useFullTextMatching, int maxParentTextLength, int maxTextLength) {
String builtXpath = "";
try {
String xml = this.windowsXmlTextPane.getText();
if (enumProperties != null && !SynthuseDlg.config.isUiaBridgeDisabled()) {
if (!enumProperties.isEmpty()) {
builtXpath = buildUiaXpathStatement();
}
}
if (builtXpath != "")
return builtXpath;
String classStr = WindowsEnumeratedXml.escapeXmlAttributeValue(Api.getWindowClassName(hwnd));
String handleStr = Api.GetHandleAsString(hwnd);
String txtOrig = Api.getWindowText(hwnd);
String txtStr = WindowsEnumeratedXml.escapeXmlAttributeValue(txtOrig);
builtXpath = "//win[@class='" + classStr + "']";
List<String> resultList = WindowsEnumeratedXml.evaluateXpathGetValues(xml, builtXpath);
//int matches = nextXpathMatch(builtXpath, textPane, true);
if (resultList.size() > 1) { // if there are multiple results with the simple xpath then include parent class and text with the xpath statement.
HWND parent = User32Ex.instance.GetParent(hwnd);
String parentClassStr = WindowsEnumeratedXml.escapeXmlAttributeValue(Api.getWindowClassName(parent));
String parentTxtOrig = Api.getWindowText(parent);
String parentTxtStr = WindowsEnumeratedXml.escapeXmlAttributeValue(parentTxtOrig);
if (!parentTxtStr.isEmpty()) {
if (parentTxtOrig.length() > maxParentTextLength) {// if the parent text is too long only test the first maxParentTextLength characters
parentTxtStr = WindowsEnumeratedXml.escapeXmlAttributeValue(parentTxtOrig.substring(0, maxParentTextLength));
parentTxtStr = " and starts-with(@text,'" + parentTxtStr + "')";
}
else
parentTxtStr = " and @text='" + parentTxtStr + "'";
}
if (!parentClassStr.isEmpty())
{
if (!txtStr.isEmpty()&&useFullTextMatching) {
String copyOfTxtStr = txtStr;
if (copyOfTxtStr.length() > maxTextLength) {// if the text is too long only test the first maxTextLength characters
copyOfTxtStr = WindowsEnumeratedXml.escapeXmlAttributeValue(copyOfTxtStr.substring(0, maxTextLength));
copyOfTxtStr = " and starts-with(@text,'" + copyOfTxtStr + "')";
}
else
copyOfTxtStr = " and @text='" + copyOfTxtStr + "'";
builtXpath = "//win[@class='" + parentClassStr + "'" + parentTxtStr + "]/win[@class='" + classStr + "'" + copyOfTxtStr + "]";
}
else
{
builtXpath = "//win[@class='" + parentClassStr + "'" + parentTxtStr + "]/win[@class='" + classStr + "']";
}
}
System.out.println(builtXpath);
resultList = WindowsEnumeratedXml.evaluateXpathGetValues(xml, builtXpath);
if (resultList.size() > 1) { // if there are still multiple results add position to the xpath
int position = 1;
for (String result : resultList) {
if (result.contains(handleStr)) {
builtXpath += "[" + position + "]";
break;
}
++position;
}
}
if (resultList.size() == 0) { //some reason a window might have a parent window that is not associated with it's child (orphans!!)
if (!txtStr.isEmpty()) {
if (txtStr.length() > maxTextLength) {// if the text is too long only test the first maxTextLength characters
txtStr = WindowsEnumeratedXml.escapeXmlAttributeValue(txtStr.substring(0, maxTextLength));
txtStr = " and starts-with(@text,'" + txtStr + "')";
}
else
txtStr = " and @text='" + txtStr + "'";
}
builtXpath = "//win[@class='" + classStr + "'" + txtStr + "]";
}
resultList = WindowsEnumeratedXml.evaluateXpathGetValues(xml, builtXpath);
if (resultList.size() > 1) //still too many matched, only use hwnd
builtXpath = "//win[@hwnd='" + handleStr + "']";
}
} catch (Exception e) {
e.printStackTrace();
}
return builtXpath;
}
public static int nextXpathMatch(String xpathExpr, JTextPane targetText, JLabel lblStatus, boolean alwaysFromTop) {
int results = 0;
try {
if (xpathExpr.length() == 0)
return results;
if (targetText instanceof JTextPane) {
final JTextPane target = (JTextPane)targetText;
target.requestFocus();
int cPos = 0;
try {
cPos = target.getCaretPosition();
} catch(Exception ex) {
//return 0;//something is throwing nullpointer exception
}
if (alwaysFromTop)
cPos = 0;
int len = target.getStyledDocument().getLength();
String xml = target.getText(0, len);
WindowsEnumeratedXml.lastException = null;
List<String> resultList = WindowsEnumeratedXml.evaluateXpathGetValues(xml, xpathExpr);
if (resultList.size() == 0 && WindowsEnumeratedXml.lastException != null) {
String errMsg = WindowsEnumeratedXml.lastException.getCause().getMessage();
JOptionPane.showMessageDialog(target.getTopLevelAncestor(), "Exception: " + errMsg, "Error", JOptionPane.ERROR_MESSAGE);
return -1;
}
results = resultList.size();
String txt = "";
String targetStr = target.getText(cPos, (len - cPos));
int matches = 0;
int mPos = 0;
target.select(cPos, cPos); //clear selection
for (int i = 0; i < resultList.size(); i++) {
txt = resultList.get(i).trim();
if (txt.length() == 0)
continue;
//if (txt.endsWith("\r\n"))
// txt = txt.substring(0, txt.length() - 2).trim();
txt = txt.replaceAll("\r\n", "\n").trim();
while ((mPos = targetStr.indexOf(txt)) != -1) {
if (matches == 0){
if (!alwaysFromTop)
flashMatchingWindow(txt);
//target.setCaretPosition(cPos + mPos);
//target.select(cPos + mPos, cPos + mPos + txt.length());
XmlEditorKit.HIGHLIGHTED_START = cPos + mPos;
XmlEditorKit.HIGHLIGHTED_END = cPos + mPos + txt.length();
//System.out.println("HIGHLIGHTED_START = " + (cPos + mPos));
//System.out.println("HIGHLIGHTED_END = " + (cPos + mPos + txt.length()));
final int cpos = cPos + mPos +2;
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
target.updateUI();
target.setCaretPosition(cpos);
}
});
}
targetStr = targetStr.substring(mPos + txt.length());
++matches;
}
}
lblStatus.setText(results + " match(es)");
if (cPos > 0 && matches == 0 && !alwaysFromTop) { //ask if user wants to search from top
int result = JOptionPane.showConfirmDialog(target.getTopLevelAncestor(), "No more matches found. Do you want to search from the top of the document?", "Find", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
target.setCaretPosition(0);
nextXpathMatch(xpathExpr, targetText, lblStatus, alwaysFromTop);
}
}
}
} catch (Exception e) {
XmlEditorKit.HIGHLIGHTED_START = 0;
XmlEditorKit.HIGHLIGHTED_END = 0;
e.printStackTrace();
}
return results;
}
public static void flashMatchingWindow(String txtXml) {
if (txtXml.contains("hwnd")) {
List<String> hwndList = WindowsEnumeratedXml.evaluateXpathGetValues(txtXml, "//@hwnd");
if (hwndList.size() > 0) {
String hwndStr = hwndList.get(0);
HWND tHwnd = Api.GetHandleFromString(hwndStr);
Api.highlightWindow(tHwnd);
try { Thread.sleep(100); } catch (Exception e) {e.printStackTrace();}
Api.refreshWindow(tHwnd);
try { Thread.sleep(100); } catch (Exception e) {e.printStackTrace();}
Api.highlightWindow(tHwnd);
try { Thread.sleep(100); } catch (Exception e) {e.printStackTrace();}
Api.refreshWindow(tHwnd);
}
}
}
}