Thursday, September 18, 2008

Widget selection

A JxCapture egy kereskedelmi capture library javahoz. Screenshotot persze alapbol is lehet kesziteni javaban thirdparty lib nelkul (java.awt.Robot-tal), de vannak olyan featureok amiket nem lehet megoldani csak nativ hivasokkal. Az ilyen oprendszer specifikus dolgokhoz ez a lib is JNI-t hasznal de tobbfele platforma letezik implementacioja. A webstartos demo app tartalmaz egy erdekes featuret ami a "Select Window/Object" menupont alatt erheto el.

Ezzel nem csak ablakokat tudunk kivalasztni a kepernyon hanem az azon levo komponenseket is. (Pl.: gombot, textfieldet, paneleket stb) Tehat minden olyat ami az opreracios rendszer szintjen kulon widgetkent van nyilvantartva. Amikor user raviszi az egeret egy ablakra akkor progi kijeloli vagy az egesz ablakot vagy az azon levo widgetet. A dolog annyira megtetszett hogy eldontottem hogy megcsinalom JShotba is.

Tisztan javaban sajnos ezt nem lehet megoldani mert az ablakok manageleset az oprendszer windowmanagere vegzi. A problema megoldasahoz fel kellett frissitenem a mar elegge megkopott c++ es winapi ismereteimet. Amire szuksegunk van az az hogy le tudjam kerdezni az eger kurzor alatt talalhato ablak/widget koordianatajat es meretet. A dolog ugyanugy fog mukodni mint a "region capture", azaz a user egy teljeskepernyos ablakot fog latni rajta a desktoprol keszitett keppel es eger mozgatasra ezen kijelolodik az aktualis widget.

Ehhez 2 native methodra lesz szukseg:

private native int[] getWidgetRectUnderCursor( int x, int y );

private native void takeWidgetStateSnapshot();

Az elso megadja az egerkurzor alatt talalhato widget elhelyezkedeset. A masodik pedig keszit egy "snapshotot" az ablakok elhelyezkedeserol. Erre csak azert van szukseg hogy ne kelljen minden eger mozgataskor ujra lekerdezni melyik ablak hol talalhato. Addig ugysem valtozik semmi mert a felhasznalo csak egy kepet fog latni a desktoprol.

JNI-nel megszokott modon a classbol legeneraljuk header filet. Ez nalam javah -jni hu.jshot.plugins.nativ.WindowUtils

Az alabbi 2 fuggvenyt kell megirunk cpp-ben.

JNIEXPORT void JNICALL Java_hu_jshot_plugins_nativ_WindowUtils_takeWidgetStateSnapshot
(JNIEnv *env, jobject obj) ;


JNIEXPORT jintArray JNICALL Java_hu_jshot_plugins_nativ_WindowUtils_getWidgetRectUnderCursor
(JNIEnv *env, jobject obj, jint px, jint py);

A konkret megvalositas pedig annyi hogy lekerdezzuk a lathato ablakokat megfelelo Z order szerint es az eger kurzor alatti legmagasabb Z orderrel rendelkezo ablakot vesszuk. Ha az ablakon belul tovabbi widgetre mutat a kurzor akkor azt adjuk (ill. ezek kozul a legkisebbet) vissza egyebkent magat az ablakot.

Azt hogy egy ablak lathato-e az IsWindowVisible fgv-vel kerdezhetjuk le. Ez viszont nem azt adja vissza hogy az adott ablak a felhasznalo szamara valoban lathato-e, hanem hogy az ablak vagy annak parentjenek van-e WS_VISIBLE styleja. Vagyis hogy explicit nem hideoltak-e. Ettol meg a felhasznalo szamara nem biztos hogy lathato mert eltakarhatja egy vagy tobb mas ablak.


BOOL IsWindowVisible(
HWND hWnd // handle of window
);

Az talcara letett ablakokat kiszurhetjuk az IsIconic winapi hivassal

BOOL IsIconic(
HWND hWnd // handle of window
);

A fentebb emlitett problema nem jelent igazan gondot ha ugyis az eger kurzor alatti legnagyobb Z orderrel rendelkezo ablakot valasztjuk. Akkor a takarasban levo
ugysem fog kivalasztodni mert ha van felette vmi akkor annak biztos nagyobb a Z ordere is. Ha csak felig van takarasban akkor ki lehet valasztani de az nem is baj.

Az ablakokat egyszeruen le lehet kerdezi az EnumWindows fgv-vel. Ez egy callbacket ker elso parameterkent ami minden ablak handlejevel meg fog hivodni. A masodik parameterben meg atadhatunk pl egy listat amibe eltaroljuk a hwnd-ket.

BOOL EnumWindows(
WNDENUMPROC lpEnumFunc, // pointer to callback function
LPARAM lParam // application-defined value
);


BOOL CALLBACK EnumWindowsProc(

HWND hwnd, // handle to parent window
LPARAM lParam // application-defined value
);

A child windowkat teljesen hasonloan kerdezhetjuk le az EnumChildWindows fgv-vel. Csak a fenti paremetereken kivul a parent hwndjet is meg kell adnunk.

BOOL EnumChildWindows(

HWND hWndParent, // handle to parent window
WNDENUMPROC lpEnumFunc, // pointer to callback function
LPARAM lParam // application-defined value
);


Az EnumWindows csokkeno Z orderben kapjuk az ablakokat
Az ablak meretet es koordinatait a GetWindowRect fuggvennyel kerdezhetjuk le.

BOOL GetWindowRect(

HWND hWnd, // handle of window
LPRECT lpRect // address of structure for window coordinates
);

Igy mar minden megvan a megvalositashoz amit most nem fogok egy az egyben bepastelni csak par reszletet.

A StateSnapshot tartalmaz egy vector<widget>-et. Tegyuk fel hogy az ablakok mar ki vannak gyujtve ebben. Ahol a widget tartalmazza a handlet, RECT-et es a child komponenskere mutato listat valamint meg tudja mondani egy tetszoleges pontrol hogy az az ablakon belul helyezkedik-e el, es hogy milyen child komponensen van eppen a kurzor.

A lathato ablakokbol kivalasztjuk azt ami az eger kurzor alatt van.

Widget* StateSnapshot::getWindowUnderPoint( int x, int y ) {

Widget::WidgetList *lst = getVisible();
Widget *widget = NULL;

for ( int i = 0; i Widget *w = lst->at(i);
if ( w->pointInside( x, y) ) {
widget = w;
break;
}
}
delete lst;

return widget;
}

A legkisebb teruletu child komponens kivalasztasa:

Widget* Widget::getChildUnderPoint( int x, int y ) {
int area = -1;
Widget *widget = NULL;

for ( int i = 0; i < childList.size(); ++i ) {

Widget *w = childList[ i ];

if ( w->pointInside( x, y ) ) {
if ( area == -1 || w->area() < area ) {
area = w->area();
widget = w;
}
}
}

return widget;
}

Ez pedig az amit a JNI fuggvenyunk ( getWidgetRectUnderCursor ) hivni fog. Eloszor megkeresi melyik ablakra mutat a kurzor. Majd megnezni hogy milyen widget van az ablakon belul azon a helyen. Ha semmi akkor visszater az ablakkal egyebkent a child widgettel.

Widget* StateSnapshot::getWidgetUnderPoint( int x, int y) {
Widget *w = getWindowUnderPoint( x, y );

if ( w == NULL ) {
return NULL;
}

Widget *child = w->getChildUnderPoint( x, y );

if ( child == NULL ) {
return w;
}

return child;
}

Erdemes kiprobalni tobbfele ablakkal a featuret. Ha megnezzuk pl NB-vel (vagy barmilyen mas swinges alkalmazassal) erdekes eredmenyre jutunk. Semmilyen child widgetet nem fogunk tudni kivalasztani csak magat az ablakot, mivel a swing a komponenseit sajat maga rendereli le. Eclipsenel viszont siman megy a gombok es panelek kivalasztasa mivel SWT mar az OS widgetjeit hasznalja. Vannak persze nativ alkalmazasok is ahol nem tudunk mindent kivalasztani egyenkent mert sajat komponenseket hasznalnak. De az ezeket magabafoglalo widget mar kijelolheto.

Remelhetoleg xlibbel is hasonloan egyszeruen megoldhato. A kovetkezo (wines) JShot verzio mar tartalmazni fogja a featuret ami hamarosan letoltheto lesz.

2 comments:

kack said...

Az a bajom az ilyen postokkal, hogy amikor szukseg van egy ilyenre, a leirason kivul nincs egy hasznalhato,letoltheto, kiprobalhato cucc. Lesz?

Zeroflag said...

Mintha neked naponta szukseged lenne ilyenre :). Majd lehet osszedobok egy kis demo appot forrassal es kirakom. De a post lenyege az lenne h osszegyujti a megvalositashoz szukseges fgvket es leirja a megvalositas menetet. Teljes src-t nem fogok bepastelni mert annak csak kulon letolthetokent van ertelme. Azt majd majd akkor ha lesz vmi demo app hozza.