Respond to user input in views

The client APIs automatically capture mouse and keyboard input within views. Under the hood, the input is encapsulated into an event object that gets sent to the service application. The object contains all the relevant details: which key or button was pressed, the coordinates within the view, and so on.

Whenever the service receives an input event from the client, it automatically calls the corresponding built-in handler, PostMouseEvent or PostKeyEvent. The APIs do not define these handlers; it is up to you to implement them based on your application's requirements. This section provides the general guidelines to help you with this effort.

PureWeb views also support touchscreen input, which can be handled in a few different ways, also discussed in this section.

This section deals specifically with user input events that occur in views. For handling user input in other elements on the client user interface, you can use either commands or application state (see About the APIs).

How-to

Handle mouse and keyboard input

APIs:

(I)RenderedView.postMouseEvent() | C++ | .Net | Java
(I)RenderedView.postKeyEvent() | C++ | .Net | Java

The postMouseEvent and postKeyEvent handlers are part of IRenderView (called RenderView in Java), the interface used to stream graphics from the service. They are optional: if you only care to respond to mouse input for instance, you could stub out postKeyEvent, or vice versa.

The purpose of these handlers is to map PureWeb input events to their equivalents on the service framework (Windows Forms, Microsoft Foundation Class, Qt, etc.). For example if the service runs on Qt, the PureWeb MouseDown event would need to be mapped to its Qt equivalent (MouseButtonPress). As you can imagine, the implementation is therefore highly dependent on which framework is used.

The handlers take the PureWebMouseEventArgs and PureWebKeyboardEventArgs objects as arguments. You can read from these objects to get the event details.

Because the code must provide the necessary logic for all the input events that you care about for your application, it tends to be relatively long.

The sample applications provide complete working examples, and are a great starting point when you are ready to implement this functionality in your own applications.

The relevant code from the samples has been reproduced here for your convenience (the link will open in a new window).

The snippets below illustrate what the code might look like if you were mapping a single event.

Handling mouse events

C++ on Linux Qt

void MyViewClass::PostMouseEvent(const CSI::PureWeb::Ui::PureWebMouseEventArgs& mouseEvent)
{
    // Declare the variables needed by QMouseEvent
    QEvent::Type action = QEvent::None;
    Qt::MouseButton button;
    Qt::MouseButtons buttons;
    Qt::KeyboardModifiers keys = 0;
						
    // If the PureWeb event is MouseDown using the left mouse button, 
    // convert to the Qt equivalent
    if (mouseEvent.EventType == CSI::PureWeb::Ui::MouseEventType::MouseDown) action = QEvent::MouseButtonPress;
    if (mouseEvent.ChangedButton == CSI::PureWeb::Ui::MouseButtons::Left) button = Qt::LeftButton;

    // Declare the QMouseEvent based on earlier mapping
    QMouseEvent * m = new QMouseEvent(action, QPoint(mouseEvent.X,mouseEvent.Y), button, buttons, keys);

    // Dispatch the converted event to the service
    QCoreApplication::postEvent(this, m);
}

C++ on Windows

void MyViewClass::PostMouseEvent(const Ui::PureWebMouseEventArgs& mouseEvent)
{
    // Declare the necessary variables
    Ui::MouseEventType::Enum mouseEventType = mouseEvent.EventType;
    WPARAM wParam = 0;
    LPARAM lParam = 0;
    UINT message = 0;
						
    // Map the left mouse button to its equivalent on the service
    if (0 != (mouseEvent.Buttons & Ui::MouseButtons::Left))     wParam |= MK_LBUTTON;
						
    // If the PureWeb event is MouseDown using the left mouse button, 
    // convert to its equivalent on the service
    if ((mouseEventType == Ui::MouseEventType::MouseDown) && (0 != (mouseEvent.ChangedButton & Ui::MouseButtons::Left))) message = WM_LBUTTONDOWN;

    // Dispatch the converted event to the service
    if (message != 0)
    {
        ::PostMessage(m_hWnd,message, wParam, lParam);
    }
}

.Net

public void PostMouseEvent(PureWebMouseEventArgs mouseEvent)
{
    // Map the left mouse button to its equivalent on the service
    System.Windows.Forms.MouseButtons buttons = System.Windows.Forms.MouseButtons.None;
    if (0 != (mouseEvent.Buttons & PureWeb.Ui.MouseButtons.Left)) buttons |= System.Windows.Forms.MouseButtons.Left;

    // If PureWeb event is MouseDown (using left mouse button),
    // execute the OnMouseDown function
    if (mouseEvent.EventType == MouseEventType.MouseDown)
    {
        OnMouseDown(new MouseEventArgs(buttons, 0, (int)mouseEvent.X, (int)mouseEvent.Y, (int)mouseEvent.Delta));
    }
}		

Java

// A preliminary function that converts a PureWeb event into an AWT event.
private MouseEvent getAWTMouseEvent(PureWebMouseEventArgs mouseEvent)
{
    int modifiers = getModifiers(mouseEvent.getModifiers());
    EnumSet<MouseButtons> buttons = mouseEvent.getButtons();
							
    if (buttons.contains(MouseButtons.Left)) modifiers |= InputEvent.BUTTON1_DOWN_MASK;
						
    int id = 0;
    int clickCount = 1;
    MouseEventType eventType = mouseEvent.getEventType();
						
    if (eventType == MouseEventType.MouseDown)
    {
        id = MouseEvent.MOUSE_PRESSED;
    }
						
    long when = System.currentTimeMillis();
    int x = (int)mouseEvent.getX();
    int y = (int)mouseEvent.getY();
						
    MouseButtons changed = mouseEvent.getChangedButton();
						
    int awtButton;
						
    if (changed == MouseButtons.Left) awtButton = MouseEvent.BUTTON1;
    return new MouseEvent(this, id, when, modifiers, x, y, clickCount, false, awtButton);
}

// Use postMouseEvent to dispatch the event (expressed as an AWT event) to the service
public void postMouseEvent(PureWebMouseEventArgs mouseEvent)
{
     MouseEvent awtMouseEvent = getAWTMouseEvent(mouseEvent);
     dispatchEvent(awtMouseEvent);
}

Handling keyboard events

C++ on Linux Qt

void ScribbleArea::PostKeyEvent(const CSI::PureWeb::Ui::PureWebKeyboardEventArgs& keyEvent)
{
    CSI::PureWeb::Ui::KeyboardEventType::Enum keyEventType = keyEvent.EventType;					
    bool isAltDown = 0 != (keyEvent.Modifiers & CSI::PureWeb::Ui::Modifiers::Alternate);
    WPARAM wParam = (int)keyEvent.KeyCode;
    LPARAM lParam = isAltDown ? (1 << 29) : 0; // "context code";
    UINT message;
							
    if (isAltDown || keyEvent.KeyCode == CSI::PureWeb::Ui::KeyCode::F10)
    {
        message = keyEventType == CSI::PureWeb::Ui::KeyboardEventType::KeyDown ? WM_SYSKEYDOWN : WM_SYSKEYUP; 
    }
    else
    { 
        message = keyEventType == CSI::PureWeb::Ui::KeyboardEventType::KeyDown ? WM_KEYDOWN : WM_KEYUP;
    }
							
    if (message != 0)
    {
        this->setFocus(); ::PostMessage(::WindowFromDC(this->getDC()), message, wParam, lParam);
    }
}

C++ on Windows

void MyViewClass::PostKeyEvent(const CSI::PureWeb::Ui::PureWebKeyboardEventArgs& keyEvent)
{
    // Get the key event type
    CSI::PureWeb::Ui::KeyboardEventType::Enum keyEventType = keyEvent.EventType;

    // Declare the necessary variables
    bool isAltDown = 0 != (keyEvent.Modifiers & CSI::PureWeb::Ui::Modifiers::Alternate); 
    WPARAM wParam = (int)keyEvent.KeyCode; // gets which key was pressed
    LPARAM lParam = isAltDown ? (1 << 29) : 0; // "context code";
    UINT message;

    // Convert the PureWeb KeyDown event to its equivalent on the service
    if
    {
        message = keyEventType == CSI::PureWeb::Ui::KeyboardEventType::KeyDown ? WM_KEYDOWN : WM_KEYUP;
    }

    // Dispatch the converted event to the service
    if (message != 0)
    {
        ::PostMessage(message, wParam, lParam);
    }
}

.Net

// Preliminary global declarations
const int WM_KEYDOWN = 0x100;
static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

public void postKeyEvent(PureWebKeyboardEventArgs keyEvent)
{
    // local variable declarations
    bool isAltDown = 0 != (keyEvent.Modifiers & Modifiers.Alternate);
    int wParam = (int)keyEvent.KeyCode; // gets which was pressed
    int lParam = isAltDown ? (1 << 29) : 0; // "context code";
    int message;

    // Convert the PureWeb KeyDown event to its equivalent on the service
    if (isAltDown || keyEvent.KeyCode == KeyCode.F10)
    { 
        message = keyEvent.EventType == KeyboardEventType.KeyDown ? WM_SYSKEYDOWN : WM_SYSKEYUP;
    }
    else
    { 
        message = keyEvent.EventType == KeyboardEventType.KeyDown ? WM_KEYDOWN : WM_KEYUP; 
    }
   
    // Dispatch the converted event to the service
    PostMessage(this.Handle, (UInt32)message, new IntPtr(wParam), new IntPtr(lParam));
}   

Java

public void postKeyEvent(PureWebKeyboardEventArgs keyEvent)
{
    // Get the necessary variables for the AWT key event by reading from the PureWeb event
    int id = keyEvent.getEventType() == KeyboardEventType.KeyDown ? KeyEvent.KEY_PRESSED : KeyEvent.KEY_RELEASED;
    long when = System.currentTimeMillis();
    int modifiers = getModifiers(keyEvent.getModifiers());
    char keyChar = (char)keyEvent.getCharacterCode();
    int keyCode = keyEvent.getKeyCode().getVirtualKeyCode();

    // Dispatch the key event (expressed as AWT event) to the service
    dispatchEvent(new KeyEvent(this, id, when, modifiers, keyCode, keyChar));
}


Handle touchscreen input

Views automatically handle basic touchscreen input: when you click and drag with a single finger on a touch device, this will raise a series of mouse move events in the postMouseEvent handler (the same events that would be generated if you clicked the left mouse button and dragged).

To handle more complex gestures on touch device clients, you would use PureWeb commands instead. If you are not yet familiar with commands, jump to the Commands section of this guide to learn more. The most common approach is to send a basic command, as illustrated in the touch gesture example further down this page.

Another, less common, way to handle touchscreen input is to simulate keyboard events on the client. This also works using commands, but is more involved. For details, see Map touch gestures to keyboard events.


Suppress input transmission

APIs:

View.setInputTransmissionEnabled() | HTML5 | Android
PWView.inputTransmissionEnabled() | iOS

Occasionally, it may be necessary to prevent the client application from sending input events to the service for a particular view. For example, you may want to temporarily suppress input events while a user is drawing acetate markup.

There is a client-side method that you can use to toggle the transmission of input events in a view: setInputTransmissionEnabled (called inputTransmissionEnabled in iOS).

Note that this has no impact on all other non-input communications, including commands and application state; these will continue to be transmitted.

In the example below, we disable input transmission for a view called Spectacular3D.

HTML5

Spectacular3D.setInputTransmissionEnabled(false);

iOS (Obj-C)

Spectacular3D.inputTransmissionEnabled = disabled;

Android

Spectacular3D.setInputTransmissionEnabled(false);

Example

Erasing scribbles with touch gestures

This example, taken from the Scribble sample application, illustrates how to support touch gestures using simple commands.

On the client

HMTL5

A third-party library has been added to the sample, so that the client can recognize shake gestures.

<script src="./bower_components/shake.js/shake.js"></script>

A JavaScript event listener is set up to listen for the shake gesture. This listener calls a handler named clearCanvas.

window.addEventListener('shake', clearCanvas, false);

The clearCanvas event handler uses PureWeb's queueCommand method to send the "Clear" command to the service application.

function clearCanvas() {
    pureweb.getClient().queueCommand("Clear");
}

iOS (Obj-C)

A UITapGestureRecognizer is set up to support double-tap gestures. The recognizer calls the doubleTapGesture function.

UITapGestureRecognizer *doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapGesture:)];
doubleTapGestureRecognizer.numberOfTapsRequired = 2; //double tap
[self.scribbleView addGestureRecognizer:doubleTapGestureRecognizer];

The doubleTapGesture function uses PureWeb's queueCommand method to send the "Clear" command to the service application.

- (void)doubleTapGesture:(UIPanGestureRecognizer *)gesture
{
    [self.scribbleView.framework.client queueCommand:@"Clear"];
}

Android

// The Scribble sample application doesn't currently provide 
// the code to illustrate erasing scribbles with touch gestures.

On the service

C++

A command handler is registered for the "Clear" command. The handler is set up to call "OnExecuteClear".

CScribbleApp::StateManager().CommandManager().AddUiHandler("Clear", Bind(this, &CScribbleView::OnExecuteClear));

The OnExecuteClear function is defined to clear the screen. In this instance, the necessary code is implemented directly in the function:

void CScribbleView::OnExecuteClear(Guid sessionId, Typeless command, Typeless responses)
{
    CBrush br(RGB(255,255,255));
    m_dcOffscreen.SelectObject(&br);
    CRect rect(0,0,m_Width,m_Height);
    CBrush *pbr = &br;
    m_dcOffscreen.FillRect(&rect,pbr);
    m_startpt = -1;
}			

.Net

A command handler is registered for the "Clear" command. The handler is set up to call "OnExecuteClear".

Program.StateManager.CommandManager.AddUiHandler("Clear", OnExecuteClear);

The OnExecuteClear function is defined to clear the screen. In this instance, there is already an existing ClearStrokes function for this purpose elsewhere in the code, and so the command handler simply calls it:

private void OnExecuteClear(Guid sessionId, XElement command, XElement responses)
{
    ClearStrokes();
}

The ClearStrokes function is what actually clears the screen:

private void ClearStrokes()
{
    EndStroke();
    m_strokes.Clear();
    if (m_offscreen != null)
    {
        Graphics.FromImage(m_offscreen).Clear(Color.Black);
    }
}

Java

A command handler is registered for the "Clear" command. The handler is set up to call "OnExecuteClear".

stateManager.getCommandManager().addUiHandler("Clear", new OnExecuteClear());

The OnExecuteClear function is defined to clear the screen.

private class OnExecuteClear implements CommandHandler
{
    public void invoke(UUID sessionID, Element command, Element responses)
    {
        erase();
        repaint();
    }
}