Need help simulating "glass pane" functionality

Hi,

I am looking at simulating “glass pane” like functionality, so that I can disable input on part, or all of the browser user interface. I have thought of 2 approaches:

Approach 1 - Disable all user input

Bring up a “notification” modal dialog while the server side is busy processing background tasks. The dialog would need to be modal, to prevent user input during that time. I would like the dialog to:

  1. Have a single icon (animated gif like ajax-loader-big.gif from the toolkit), centered in the dialog.

  2. Remove the title bar, resizing corner, close button, and all lines from the dialog, to create a window that has only transparent background with the icon in the middle, no visible border, and no shadow behind, and is only sized to be large enough to fit the icon.

Can anyone assist in how to code this, and (in particular) detail the style changes needed on the Window, or point me to something that will already do this ?

Approach 2 - Disable input on part of the browser screen

Define a single style, which could be added/removed to/from any component, which would (temporarily) set the cursor style to a custom gif (like ajax-loader-big.gif from the toolkit), or even the standard cursor “wait” style. I thought maybe addStyleName() / removeStyleName() could be used to dynamically change the style. In addition, setEnabled(false) on the Window/(or modeless dialog) that I wanted to (temporarily) prevent input on while waiting for processing to complete. As I understand it, disabling the Window disables all the components inside it recursively.

Can anyone assist in how to code this, and say if a single style could be defined for this task, or point me to something that will already achieve this ?

Thanks
Andrew

Approach 1: I tested a few different cases, where I used a modal popup window a hid it with css
First try:


.hiddenwindow, .i-shadow-window {
visibility: hidden !important
}

The Window get’s hidden allright, but you can’t give .i-shadow-window a custom name so you lose the window shadow from all windows. not good. Additionally cursor:wait doesn’t apply when the window is not visible.

Second:


.hiddenwindow {
left: 100000px !important
}

Hides the field as it is a long way outside the screen. Don’t know if every browser likes the !important statement. Additionally some browser may give scrollbars because of this. Cursor can’t be changed as the cursor has to be on top of the object and the object is not on the screen.

Third try:
hiddenwindow.setSizeFull and:


.hiddenwindow .popupContent, .i-shadow-window{
display:none;
}
.hiddenwindow {
  cursor:wait;
}

You get full modality with nothing of the popup visible, and a “wait”-cursor. Again you lose the shadow on all windows. You get a quite different experience if you leave out hiding the .i-shadow-window :lol:

You can also user cursor: url(‘./images/cursor.gif’); to put ajax-loader-big.gif etc as cursor.

I’ll get back to this if I come up with another idea that wouldn’t break something else.

Java code I used for testing:


package testCase;

import com.itmill.toolkit.Application;
import com.itmill.toolkit.ui.Button;
import com.itmill.toolkit.ui.VerticalLayout;
import com.itmill.toolkit.ui.Window;
import com.itmill.toolkit.ui.Button.ClickEvent;
import com.itmill.toolkit.ui.Button.ClickListener;

public class TestCase extends Application implements ClickListener {

    private Button button;

    @Override
    public void init() {
        try {
            Window main = new Window("Table header Test");
            setMainWindow(main);
            main.setSizeFull();
            setTheme("testtheme");

            VerticalLayout baseLayout = new VerticalLayout();
            baseLayout.setSizeFull();
            button = new Button("Click Me!", this);
            baseLayout.addComponent(button);

            main.setLayout(baseLayout);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void buttonClick(ClickEvent event) {
        Window window = new Window();
        window.setStyleName("hiddenwindow");
        window.setModal(true);
        window.setSizeFull();
        getMainWindow().addWindow(window);
    }
}

[quote=Jens Jansson]

[code]

.hiddenwindow {
left: 100000px !important
}
[/code]Hides the field as it is a long way outside the screen. Don’t know if every browser likes the !important statement. Additionally some browser may give scrollbars because of this. Cursor can’t be changed as the cursor has to be on top of the object and the object is not on the screen.

[/quote]You can use negative values with the offset-properties, so just use

left: -9999px !important; and your window is nicely hidden, and no scrollbars will appear.

[quote=Jens Jansson]
You can also user cursor: url(‘./images/cursor.gif’); to put ajax-loader-big.gif etc as cursor.
[/quote]Please note, that custom cursors might not work in every browser, so use the following to provide some fallback options

cursor : url(first.cur), url(second.cur), pointer;

I may have been a little unclear about the scrollbars. I did not get any scrollbars, but I do not guarantee that it works every time.

A fallback is always good, thanks!

Jens/Jouni,

Many thanks for the replies :slight_smile: [sorry for not replying sooner :frowning: ]

I also tried approach 2. To do so, I added a style
for every style that overrode cursor:

.busy,
.busy *,

.busy .i-popupmenu-submenu .menuitem,
.busy .i-accordion-item-caption,
.busy .i-accordion-item-open .i-accordion-item-caption,
.busy .i-button,

.busy .i-disabled *,

.busy .i-tooltip,
.busy .i-contextmenu .gwt-MenuItem,
.busy .i-contextmenu .gwt-MenuItem div,
.busy .i-datefield-button,
.busy .i-datefield-calendarpanel-day,
.busy .i-datefield-calendarpanel-day-today,
.busy .i-disabled .i-datefield-calendarpanel-day,
.busy .i-disabled .i-datefield-calendarpanel-day-today,
.busy .i-datefield-calendarpanel-day-disabled,
.busy .i-datefield-calendarpanel-day-selected,
.busy .i-disabled a,
.busy .i-menubar .menuitem,
.busy .i-menubar-submenu .menuitem,
.busy .i-Notification,
.busy .i-popupview,
.busy .i-filterselect-button,
.busy .i-filterselect-suggestmenu .gwt-MenuItem,
.busy .i-filterselect-nextpage span,
.busy .i-filterselect-nextpage-off span,
.busy .i-filterselect-prevpage-off span,
.busy .i-filterselect-prevpage span,
.busy .i-filterselect-nextpage-off span,
.busy .i-filterselect-prevpage-off span,
.busy .i-slider-handle,
.busy .i-slider-vertical .i-slider-handle,
.busy .i-splitpanel-horizontal .i-splitpanel-hsplitter div,
.busy .i-splitpanel-vertical .i-splitpanel-vsplitter div,
.busy .i-table-header td,
.busy .i-table-resizer,
.busy .i-table-row,
.busy .i-table-row-odd,
.busy .i-tabsheet-tabitem,
.busy .i-tabsheet-tabitem-selected,

.busy * html .i-tabsheet-tabitem-selected,
.busy *+html .i-tabsheet-tabitem-selected,

.busy .i-tabsheet-light .i-tabsheet-tabitem,
.busy .i-tabsheet-light .i-tabsheet-tabitem-selected,
.busy .i-tree-node span,
.busy .i-tree-node-selected span,
.busy .i-window-outerheader,
.busy .i-window-resizebox,
.busy .i-window-resizebox-disabled,
.busy .i-window-closebox {
cursor: wait;
}

and used window.add/removeStyleName(“busy”);

AND:

window.setEnabled()

and importantly, followed by a:

window.requestRepaint();

to ensure both effects were applied/removed at almost the same time,

to simulate the required behavior. This was “near” glass pane functionality.

The only drawback of this approach was that for the case where I had many components/layouts on the screen, IE (in particular) took a long
time (ie 5 sec +) to render the style changes.

Any thoughts on how the 5 sec could be reduced ?

Thanks
Andrew

I don’t got an answer about the 5 sec delay, but wouldn’t
.busy{
cursor: wait !important
}

be enough to overwrite them all at once? Not sure, just checking.

[quote=Jens Jansson]

.busy{
cursor: wait !important
}
[/quote]Make that

[code]

.busy * {
cursor: wait !important;
}
[/code]and it might work.

Jens/Jouni,

Many thanks for the super-fast replies !!! :slight_smile:

I tried:

.busy * {
cursor: wait !important;
}

as Jouni suggested, and that appears to work correctly.

I am not exactly sure what causes the 5 sec delay (sometimes longer too), but Task Manager shows approx 50% cpu for firefox.exe or iexplorer.exe for most of that 5 sec, so I think it all has to do with the javascript / html client side rendering in the web browser.

Just a thought - is there any way (other than via style), of setting the cursor to the “wait” cursor, that might be less expensive ? , along the lines of window.setCursor(“wait”), that could avoid a style change based approach ?

I did a test - where I removed the add/removeStyleName(“busy”) calls, and the wait time was about half as long. When I took out the setEnabled() call, the remaining (approx) 50% of the (approx.) 5 sec wait time went away as well - so with all these calls removed, the browser updated almost instantaneously.

The server seems to process these add/removeStyleName() and setEnabled(true / false) calls quickly.

Any ideas on what I could do to improve this ?

Thanks Again,
Andrew

Hey!

To which component are you giving the style name “busy”? I guess you’re adding it to your main layout. You won’t have to add and remove the style if you do it with the invisible fullscreen popup (presented above), which could shave off half the time(?). It could also be directly disabled so you just add it or remove it when ever you need. I guess the biggest problem with modifying the main layout is that it affects literally every single component in your view - making those disabled and changing their style too.

This is not codewise as elegant (more or less hackish) but i’d guess it’d be better performancewise

Jens,

Again, thanks for your response :slight_smile:

I am operating on a specific window (Dialog, Root window, etc) via:

window.getLayout().add/removeStyleName(“busy”);
and
window.setEnabled(true/false);

The reason I am doing this on the specific window, and not the fullscreen, is because I would like to block input on that specific window - but not other windows. This is important if a particular window does some processing that takes a while, but I still want to be able to interact with other windows in the same application. This is an attempt at simulating “glass pane” support.

Yes, approach 1 will block input - but it will block all input on all windows if I block input on the root window. Another option (if it were possible) would be to popup a modal dialog and setEnabled(false) on it, but this could only be useful for Approach 2, if I knew the exact size / position of the window underneath it, so that I could create a model dialog of exactly the same size as the one i wanted to block user input to, and position it right on top of the window I want to block input to. The problem is I do not know of a way to get the exact size / position of an existing window - is there any way I can query that ?

Thanks
Andrew

Okay, I’m not following anymore, what it is you want to accomplish.

You would want to position a modal dialog in the view that covers the whole application, and disable that modal window. Now wouldn’t that in effect disable the whole application, since you cannot see anything beneath the modal dialog, and the dialog itself would be disabled?

Why isn’t the basic Window.setModal(true) enough? What do you need more? At least you wanted to remove all styles from the window, which can be done, but the shadow is another problem (and the modality curtain as well, if you want to style it).

I got mixed up along the way, sorry.

Jouni,

Sorry if I was unclear :frowning:

I am looking at simulating “glass pane” like functionality, so that I can disable input on part OR all of the browser user interface. Ideally, I would like to block input on ONE window at a time and indicate that with a “busy” cursor - but still allow input on other windows.

Example: I have a popup modal dialog window - dialog1, and I press “Ok”, and start some back end processing. While that processing is going on, I want to block any further input to dialog1, so I use dialog1.setEnabled(false) and show a “busy” cursor - via dialog1.getLayout().addStyleName(“busy”) - but ONLY on dialog1. I still want to be able to input mouse/keyboard on OTHER windows. This solution does work ok, but the screen updating is slow if dialog1 contains many components (as my dialogs and windows often do) - Jens noted this earlier:

If I could put ANOTHER modal dialog2 on top of dialog1 (instead of using dialog1.setEnabled(false) and dialog1.getLayout().addStyleName(“busy”) on it), I could also block input to dialog1. This would run much faster, because dialog2 would be empty, but the problem is that I do not know how to calculate the size and position of dialog1 - if there is a way to calculate the exact size and position of dialog1 that would solve my problem. This solution implements disabling only PART of the screen (ie. ONE window only) - it is the “Approach 2” I originally described.

Unless I know the position and size of dialog1, all I could do is popup a modal dialog2 that covers the entire application screen. Covering the entire screen means I cannot enter any input on other windows while dialog2 is modal and covering the entire application screen. If that is the only choice, then that may be what I will have to do - that is “Approach 1” (Disable ALL user input), which you and Jens helped explain the style details for in earlier posts.

Just one other little thing I noticed - if I set the cursor via

cursor: wait; the on screen browser cursor does not change to an hourglass, unless I move it - is there any way to get the cursor to change without having to move the mouse ?

I hope that better explains what I am trying to do.

Thanks
Andrew

Okay, now I’m beginning to grasp the issue again.

You should be able to get the position and the dimension of the window with getPositionX/Y and getWidth/Height methods. If these return invalid values, then I would consider it a bug.


Another option that I would try, is to use a Tabsheet inside the window, with hidden tab-captions (there’s a method in TabSheet that allows you to hide the tabbar, “hideTabs”). Then you could swith to an empty/loading tab when doing the background processing, and switch back when done.

This should be much more faster than actually disabling the user input, although it will leave the components enabled in the DOM, so someone could perform malicious actions even when the actual elements are hidden from the view.

I created a simple prototype for you: http://sami.virtuallypreinstalled.com/scripter/run/backroundprocess/?restartApplication

What do you think, is this solution enough for you to get forward?

Jouni,

Many thanks for again taking the time to help me :slight_smile:

Yes - the Window position / size looks like it is returning the correct values - great !! :slight_smile:

The TabSheet idea sounds like a good one - but I too would be concerned about the security issue, so this might not be the best approach for me. By the way, I was not able to get your demo to work via the link you provided (I tried from FF3 3.0.5) - would you be able to post the source for my reference ?

The only problem i am having now, is setting the style of the hiddenwindow. The main problem I am having with the solution you and Jens gave earlier, is that although the window is hidden (which is ideal), it still casts a shadow on the window underneath, and the “wait” cursor is not showing, and it is always fullscreen in size.

Is there a way to style the hiddenwindow so that I can make it invisible (ie no caption/title, no close button, no resizing corner, no shadow, etc), AND have the “wait” cursor, AND possibly be a size less than the fullscreen ?

My latest try at the hiddenwindow style is below (Note: I have very little knowledge of CSS - I am just using firebug and the style.css provided in ITMill, + your help to try to guess what changes i should make).

Thanks
Andrew

.hiddenwindow .popupContent .i-shadow-window {
display:none;
}

.hiddenwindow * {
/left: -9999px !important;/
/* left: 10000px !important; /
/size: 1px 1px;/
/
height: 1px;
width: 1px;
left: 100px !important;
*/

/* Try to load custom image, but fallback to wait then pointer if not supported /
/
cursor: pointer; /
/
cursor: wait; */
cursor: wait !important;
cursor: url(common/img/ajax-loader-big.gif) !important;
display: none;
background: transparent;
}

.hiddenwindow .i-window-header {
/font-size: 1px;/
/text-shadow: 0 1px 0 #fff;/
display: none;
background: transparent;
}

.hiddenwindow .i-window * {
display: none;
background: transparent;
}

.hiddenwindow .i-window-footer {
display: none;
height: 0px;
}

.hiddenwindow .i-window-closebox {
top: 0;
right: 0;
width: 0;
height: 0;
}

.hiddenwindow .i-window-closebox:hover {
display: none;
}

.hiddenwindow .i-window-modalitycurtain {
background: transparent;
opacity: 1.0;
filter: alpha(opacity=100);
}

.hiddenwindow .i-shadow-window * {
display: none;
background: transparent;
}

.hiddenwindow .i-window .i-window-header {
border: none;
border-top: 0px;
background: transparent;
height: 0px;
display: none;
}
.hiddenwindow .i-window .i-window-outerheader {
display: none;
height: 1px;
margin-left: 1px;
padding: 0px 0px 0px 0px;
background: none;
}

}
.hiddenwindow .i-window .i-window-resizebox {
display: none;
}

.hiddenwindow .i-generated-body {
background: transparent;
}

I think the best way to go is to use Jouni’s TabSheet -example. It is quite ingenious IMO. You’ll get the source by clicking ‘show source’ in the application itself.

about the css, if you still want to use the hidden window:

  1. I don’t think you can use

.hiddenwindow .popupContent .i-shadow-window { 

to modify the shadow. If I recall correctly the shadow window is in the DOM tree on the same level as .hiddenwindow, and not inside it (which I think is wrong by the way). Therefore, you can’t refer to only one window’s shadow (in other words, use only .i-shadow-window {} )

  1. You can’t define height, width, left or top if you want to position the window on top of another layout. You have to do it on the Java side with something like this:

hiddenWindow.setWidth(theLayoutToBeHidden.getWidth())

same for height, top and left. I couldn’t find the API for getPositionX() and getPositionY() so I can’t really help with those (for top and left).

  1. Your cursor definitions are in a little wrong order, and I don’t in fact know what two different !important definitions affecting the same property will do. The only one row you’ll need is cursor is:

url(common/img/ajax-loader-big.gif), wait !important;

Jens,

Many thanks for replying :slight_smile:

I tried the URL Jouni published, but all I get is the spinning cursor, and nothing ever appears for me - I am running FF3.05. Could you post the source for the tab solution here or put it in the incubator area ?

  1. Ok, if that is a limitation with the shadow, then I may remove all shadowing style effects I think.

  2. I was able to get the size of a Window that had already been
    shown (based on Jouni’s instructions), via:

Window window = …;
Rectangle rectangle = new Rectangle(window.getPositionX(), window.getPositionY(),
(int) window.getWidth(),
(int) window.getHeight());

  1. Thanks for the cursor style tip. I don’t know why, but I am not getting any wait or custom gif cursor, even if I try a simplified cursor definition. Any ideas what might be causing this ?

My current style code is below:

Thanks again,
Andrew


.i-shadow-window {
   display:none;
   background: transparent;
}

.hiddenwindow  * {
    cursor: wait !important;
   display: none;
   background: transparent;
}

.hiddenwindow .i-window-header {
    display: none;
    background: transparent;
    cursor: wait !important;
}


.hiddenwindow .i-window,
.hiddenwindow body,
.hiddenwindow html,
	.hiddenwindow .i-window * {
    display: none;
    background: transparent;
    cursor: wait !important;
}

.hiddenwindow .i-window-footer {
    display: none;
    height: 0px;
    cursor: wait !important;
}

.hiddenwindow .i-window-closebox {
    top: 0;
    right: 0;
    width: 0;
    height: 0;
    cursor: wait !important;
}

.hiddenwindow .i-window-closebox:hover {
    display: none;
    cursor: wait !important;
}

.hiddenwindow .i-window-modalitycurtain {
    background: transparent;
	display: none;
    cursor: wait !important;
}

.i-shadow-window * {
    display: none;
    background: transparent;
    cursor: wait !important;
}

.hiddenwindow .i-window .i-window-header {
    border: none;
    border-top: 0px;
    background: transparent;
	height: 0px;
	display: none;
    cursor: wait !important;
}

.hiddenwindow .i-window .i-window-outerheader {
	display: none;
	height: 1px;
    margin-left: 1px;
    padding: 0px 0px 0px 0px;
	background: none;

    cursor: wait !important;
}
.hiddenwindow .i-window .i-window-resizebox {
	display: none;
    cursor: wait !important;
}

.hiddenwindow .i-generated-body * {
    background: transparent;
	display: none;
    cursor: wait !important;
}

Hmm, odd that the URL doesn’t work for you. Just checked that it worked at least from inside our internal network (the server is located in our office).

Anyway, here’s the source:

Window w2 = new Window("Back end processing");
w2.center();
w2.getLayout().setSizeUndefined();

final TabSheet ts = new TabSheet();
ts.setWidth("500px");
ts.setHeight("300px");
ts.hideTabs(true);

VerticalLayout vl = new VerticalLayout();
final Button b = new Button("Start heavy back end process");
vl.addComponent(b);
vl.setMargin(true);
vl.setSizeFull();

ts.addTab(vl);

final VerticalLayout vl2 = new VerticalLayout();
Label loading = new Label("<div style=\"cursor:wait; width:498px; padding:140px 0; text-align:center;\">Processing, please wait...</div>", Label.CONTENT_XHTML);
vl2.addComponent(loading);
vl2.setComponentAlignment(loading,"middle center");
vl2.setSizeFull();

ts.addTab(vl2);

b.addListener(new Button.ClickListener() {
        public void buttonClick(Button.ClickEvent event) {
             ts.setSelectedTab(vl2);
        }
    });

w2.addComponent(ts);
getWindow().addWindow(w2);
  1. I guess it should be possible to remove the shadow from individual windows (and other overlays), but this is currently not possible. I suggest you create a ticket for this if you really need it.

  2. So it worked? I was under the impression that the size might be a bit different from what is shown in the client. But if it worked, great!

  3. I’m pretty sure you can’t use GIF images as cursors. The URL you specify should point to an actual cursor file (.CUR). Maybe that causes the cursor-property to fail, because the browser doesn’t want to support GIFs for cursors.

And about your other CSS: if you specify “display:none”, you don’t need to specify any other properties, since the whole element wont show up in the browser’s view.

This is total gibberish:

.hiddenwindow .i-window, .hiddenwindow body, .hiddenwindow html, .hiddenwindow .i-window * it won’t affect anything. First, .i-window is always inside the BODY element and you can’t change the classname of the BODY element, unless you use a custom index-page to start up you application. The “.hiddenwindow” style is applied to the same element as the “.i-window” classname, so the latter can’t be a child of the former, as you’ve specified here. Second, .i-window will never contain neither BODY or HTML elements, it’s actually always the opposite.

The above is true for this as well:

.hiddenwindow .i-generated-body *

This won’t also affect anything. The modality curtain is much like the shadow, it is not contained within the window’s HTML structure (the modality curtain is the element that covers the whole browser view with a white translucent element.

.hiddenwindow .i-window-modalitycurtain

Below is a reformat of your CSS, that should have the same effect than your original one.

[code]

/* Hides the shadow from all windows */
.i-shadow-window {
display: none;
}

/* Hides all elements within the hidden window */
.hiddenwindow * {
display: none;
}
[/code] Quite a lot of the original is redundant. I doubt that the above will be the solution you need, but I’m just trying to narrow the amount of code here :slight_smile:

Jouni/Jens,

Many thanks for your replies and taking the time to explain things :slight_smile:

Ok, I have make good progress, by taking a slightly different approach:

  1. Make a zero sized modal hiddenwindow to popup on top:

Window window = ...;
window.setStyleName("hiddenwindow");
window.setBorder(Window.BORDER_NONE);
window.setResizable(false);
window.setCaption(null);
window.setModal(true);
window.setScrollable(false);
window.setSizeUndefined();
window.setWidth(0, Sizeable.UNITS_PIXELS);
window.setHeight(0, Sizeable.UNITS_PIXELS);
window.getLayout().setWidth(100, Sizeable.UNITS_PERCENTAGE);
window.getLayout().setHeight(100, Sizeable.UNITS_PERCENTAGE);
  1. Set the style of the hiddenwindow as Jouni suggested:

.hiddenwindow, .hiddenwindow * {
	display: none;
}
  1. Eliminate the “ghosting” effect of the modality window underneath the popup (ie. make the curtain disappear) AND give it a simple, clean wait cursor:

.i-window-modalitycurtain,
.i-window-modalitycurtain * {
    opacity: 1 ! important;
    filter: none ! important;
	background: transparent;
	cursor: url(window/img/ajax-loader-medium.gif), url(ajax-loader-medium.gif), url(window/img/ajax-loader-medium.ani), url(ajax-loader-medium.ani), wait ! important;
}

This gives a simple, clear busy indicator - by changing the cursor when it is over the “curtained” parent window to the wait or custom cursor. The modality curtain is providing a place for the wait cursor - instead of putting the wait cursor on the modal window on top of the parent !

This works nicely under FF3.05 and IE7, but I can’t get the .gif custom cursor to show up under FF3.05 or the .ani to show up under IE7. IE7 shows the standard wait cursor. FF3.05 shows one frame of the .gif - any thoughts on why that might be ? As I understand it FF3 supports animated .gif and IE7 supports animated .ani files for cursors.

Many thanks again for your assistance,
Andrew

Jouni/Jens,

Sorry, I forgot, there was one more change I made so that I would have only one busy indicator throughout the interface:


/* Remove progress indicator from tabs, etc */
.i-tabsheet-loading .i-tabsheet-tabitem-selected .i-captiontext,
.i-app-loading,
.i-loading-indicator,
.i-loading-indicator-delay,
.i-loading-indicator-wait,
.i-progressindicator-indicator,
.i-progressindicator-indeterminate {
    background-image: none;
}

Thanks
Andrew

Jouni/Jens,

Sorry, I forgot, there was one more change I made so that I would have only one busy indicator throughout the interface:


/* Remove progress indicator from tabs, etc */
.i-tabsheet-loading .i-tabsheet-tabitem-selected .i-captiontext,
.i-app-loading,
.i-loading-indicator,
.i-loading-indicator-delay,
.i-loading-indicator-wait,
.i-progressindicator-indicator,
.i-progressindicator-indeterminate {
    background-image: none;
}

Thanks
Andrew