summaryrefslogtreecommitdiffstats
path: root/src/gui/embedded/qmouse_qws.cpp
blob: f982988d58a547dbdc2d62606221c937ac9132b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qmouse_qws.h"
#include "qwindowsystem_qws.h"
#include "qscreen_qws.h"
#include "qapplication.h"
#include "qtextstream.h"
#include "qfile.h"
#include "qdebug.h"
#include "qscreen_qws.h"

QT_BEGIN_NAMESPACE

/*!
    \class QWSPointerCalibrationData
    \ingroup qws

    \brief The QWSPointerCalibrationData class is a container for
    mouse calibration data in Qt for Embedded Linux.

    Note that this class is only available in \l{Qt for Embedded Linux}.

    QWSPointerCalibrationData stores device and screen coordinates in
    the devPoints and screenPoints variables, respectively.

    A calibration program should create a QWSPointerCalibrationData
    object, fill the devPoints and screenPoints variables with its
    device and screen coordinates, and pass the object to the mouse
    driver using the QWSMouseHandler::calibrate() function.

    \sa QWSCalibratedMouseHandler, {Mouse Calibration Example}
*/

/*!
    \variable QWSPointerCalibrationData::devPoints
    \brief the raw device coordinates for each value of the Location enum.
*/

/*!
    \variable QWSPointerCalibrationData::screenPoints
    \brief the logical screen coordinates for each value of the Location enum.
*/

/*!
    \enum QWSPointerCalibrationData::Location

    This enum describes the various logical positions that can be
    specified by the devPoints and screenPoints variables.

    \value TopLeft           Index of the top left corner of the screen.
    \value BottomLeft     Index of the bottom left corner of the screen.
    \value BottomRight   Index of the bottom right corner of the screen.
    \value TopRight         Index of the top right corner of the screen.
    \value Center            Index of the center of the screen.
    \value LastLocation   Last index in the pointer arrays.
*/

class QWSMouseHandlerPrivate
{
public:
    QWSMouseHandlerPrivate() : screen(qt_screen) {}

    const QScreen *screen;
};

/*!
    \class QWSMouseHandler
    \ingroup qws

    \brief The QWSMouseHandler class is a base class for mouse drivers in
    Qt for Embedded Linux.

    Note that this class is only available in \l{Qt for Embedded Linux}.

    \l{Qt for Embedded Linux} provides ready-made drivers for several mouse
    protocols, see the \l{Qt for Embedded Linux Pointer Handling}{pointer
    handling} documentation for details. Custom mouse drivers can be
    implemented by subclassing the QWSMouseHandler class and creating
    a mouse driver plugin (derived from QMouseDriverPlugin).
    The default implementation of the QMouseDriverFactory class
    will automatically detect the plugin, and load the driver into the
    server application at run-time using Qt's \l {How to Create Qt
    Plugins}{plugin system}.

    The mouse driver receives mouse events from the system device and
    encapsulates each event with an instance of the QWSEvent class
    which it then passes to the server application (the server is
    responsible for propagating the event to the appropriate
    client). To receive mouse events, a QWSMouseHandler object will
    usually create a QSocketNotifier object for the given device. The
    QSocketNotifier class provides support for monitoring activity on
    a file descriptor. When the socket notifier receives data, it will
    call the mouse driver's mouseChanged() function to send the event
    to the \l{Qt for Embedded Linux} server application for relaying to
    clients.

    If you are creating a driver for a device that needs calibration
    or noise reduction, such as a touchscreen, use the
    QWSCalibratedMouseHandler subclass instead to take advantage of
    the calibrate() and clearCalibration() functions. The \l
    {qws/mousecalibration}{Mouse Calibration}
    demonstrates how to write a simple program using the mechanisms
    provided by the QWSMouseHandler class to calibrate a mouse driver.

    Note that when deriving from the QWSMouseHandler class, the
    resume() and suspend() functions must be reimplemented to control
    the flow of mouse input, i.e., the default implementation does
    nothing. Reimplementations of these functions typically call the
    QSocketNotifier::setEnabled() function to enable or disable the
    socket notifier, respectively.

    In addition, QWSMouseHandler provides the setScreen() function
    that allows you to specify a screen for your mouse driver and the
    limitToScreen() function that ensures that a given position is
    within this screen's boundaries (changing the position if
    necessary). Finally, QWSMouseHandler provides the pos() function
    returning the current mouse position.

    \sa QMouseDriverPlugin, QMouseDriverFactory, {Qt for Embedded Linux Pointer
    Handling}
*/


/*!
    \fn void QWSMouseHandler::suspend()

    Implement this function to suspend reading and handling of mouse
    events, e.g., call the QSocketNotifier::setEnabled() function to
    disable the socket notifier.

    \sa resume()
*/

/*!
    \fn void QWSMouseHandler::resume()

    Implement this function to resume reading and handling mouse
    events, e.g., call the QSocketNotifier::setEnabled() function to
    enable the socket notifier.

    \sa suspend()
*/

/*!
    \fn const QPoint &QWSMouseHandler::pos() const

    Returns the current mouse position.

    \sa mouseChanged(), limitToScreen()
*/

/*!
    Constructs a mouse driver. The \a driver and \a device arguments
    are passed by the QWS_MOUSE_PROTO environment variable.

    Call the QWSServer::setMouseHandler() function to make the newly
    created mouse driver, the primary driver. Note that the primary
    driver is controlled by the system, i.e., the system will delete
    it upon exit.
*/
QWSMouseHandler::QWSMouseHandler(const QString &, const QString &)
    : mousePos(QWSServer::mousePosition), d_ptr(new QWSMouseHandlerPrivate)
{
}

/*!
    Destroys this mouse driver.

    Do not call this function if this driver is the primary mouse
    driver, i.e., if QWSServer::setMouseHandler() function has been
    called passing this driver as argument. The primary mouse
    driver is deleted by the system.
*/
QWSMouseHandler::~QWSMouseHandler()
{
    delete d_ptr;
}

/*!
    Ensures that the given \a position is within the screen's
    boundaries, changing the \a position if necessary.

    \sa pos(), setScreen()
*/

void QWSMouseHandler::limitToScreen(QPoint &position)
{
    position.setX(qMin(d_ptr->screen->deviceWidth() - 1, qMax(0, position.x())));
    position.setY(qMin(d_ptr->screen->deviceHeight() - 1, qMax(0, position.y())));
}

/*!
    \since 4.2

    Sets the screen for this mouse driver to be the given \a screen.

    \sa limitToScreen()
*/
void QWSMouseHandler::setScreen(const QScreen *screen)
{
    d_ptr->screen = (screen ? screen : qt_screen);
}

/*!
    Notifies the system of a new mouse event.

    This function updates the current mouse position and sends the
    event to the \l{Qt for Embedded Linux} server application for
    delivery to the correct widget. Note that a custom mouse driver must call
    this function whenever it wants to deliver a new mouse event.

    The given \a position is the global position of the mouse cursor.
    The \a state parameter is a bitmask of the Qt::MouseButton enum's
    values, indicating which mouse buttons are pressed. The \a wheel
    parameter is the delta value of the mouse wheel as returned by
    QWheelEvent::delta().

    \sa pos()
*/
void QWSMouseHandler::mouseChanged(const QPoint &position, int state, int wheel)
{
    mousePos = position + d_ptr->screen->offset();
    QWSServer::sendMouseEvent(mousePos, state, wheel);
}

/*!
    \fn QWSMouseHandler::clearCalibration()

    This virtual function allows subclasses of QWSMouseHandler to
    clear the calibration information. Note that the default
    implementation does nothing.

    \sa QWSCalibratedMouseHandler::clearCalibration(), calibrate()
*/

/*!
    \fn QWSMouseHandler::calibrate(const QWSPointerCalibrationData *data)

    This virtual function allows subclasses of QWSMouseHandler to set
    the calibration information passed in the given \a data. Note that
    the default implementation does nothing.

    \sa QWSCalibratedMouseHandler::calibrate(), clearCalibration()
*/

/*! \fn QWSMouseHandler::getCalibration(QWSPointerCalibrationData *data) const
    This virtual function allows subclasses of QWSMouseHandler
    to fill in the device coordinates in \a data with values
    that correspond to screen coordinates that are already in
    \a data. Note that the default implementation does nothing.
 */

/*!
    \class QWSCalibratedMouseHandler
    \ingroup qws

    \brief The QWSCalibratedMouseHandler class provides mouse
    calibration and noise reduction in Qt for Embedded Linux.

    Note that this class is only available in \l{Qt for Embedded Linux}.

    \l{Qt for Embedded Linux} provides ready-made drivers for several mouse
    protocols, see the \l{Qt for Embedded Linux Pointer Handling}{pointer
    handling} documentation for details. In general, custom mouse
    drivers can be implemented by subclassing the QWSMouseHandler
    class. But when the system device does not have a fixed mapping
    between device and screen coordinates and/or produces noisy events
    (e.g., a touchscreen), you should derive from the
    QWSCalibratedMouseHandler class instead to take advantage of its
    calibration functionality. As always, you must also create a mouse
    driver plugin (derived from QMouseDriverPlugin);
    the implementation of the QMouseDriverFactory class will then
    automatically detect the plugin, and load the driver into the
    server application at run-time using Qt's
    \l{How to Create Qt Plugins}{plugin system}.

    QWSCalibratedMouseHandler provides an implementation of the
    calibrate() function to update the calibration parameters based on
    coordinate mapping of the given calibration data. The calibration
    data is represented by an QWSPointerCalibrationData object. The
    linear transformation between device coordinates and screen
    coordinates is performed by calling the transform() function
    explicitly on the points passed to the
    QWSMouseHandler::mouseChanged() function. Use the
    clearCalibration() function to make the mouse driver return mouse
    events in raw device coordinates and not in screen coordinates.

    The calibration parameters are recalculated whenever calibrate()
    is called, and they can be stored using the writeCalibration()
    function. Previously written parameters can be retrieved at any
    time using the readCalibration() function (calibration parameters
    are always read when the class is instantiated). Note that the
    calibration parameters is written to and read from the file
    currently specified by the POINTERCAL_FILE environment variable;
    the default file is \c /etc/pointercal.

    To achieve noise reduction, QWSCalibratedMouseHandler provides the
    sendFiltered() function. Use this function instead of
    mouseChanged() whenever a mouse event occurs. The filter's size
    can be manipulated using the setFilterSize() function.

    \sa QWSMouseHandler, QWSPointerCalibrationData,
    {Mouse Calibration Example}
*/


/*!
    \internal
 */

QWSCalibratedMouseHandler::QWSCalibratedMouseHandler(const QString &, const QString &)
    : samples(5), currSample(0), numSamples(0)
{
    clearCalibration();
    readCalibration();
}

/*!
    Fills \a cd with the device coordinates corresponding to the given
    screen coordinates.

    \internal
*/
void QWSCalibratedMouseHandler::getCalibration(QWSPointerCalibrationData *cd) const
{
    const qint64 scale = qint64(a) * qint64(e) - qint64(b) * qint64(d);
    const qint64 xOff = qint64(b) * qint64(f) - qint64(c) * qint64(e);
    const qint64 yOff = qint64(c) * qint64(d) - qint64(a) * qint64(f);
    for (int i = 0; i <= QWSPointerCalibrationData::LastLocation; ++i) {
        const qint64 sX = cd->screenPoints[i].x();
        const qint64 sY = cd->screenPoints[i].y();
        const qint64 dX = (s*(e*sX - b*sY) + xOff) / scale;
        const qint64 dY = (s*(a*sY - d*sX) + yOff) / scale;
        cd->devPoints[i] = QPoint(dX, dY);
    }
}

/*!
    Clears the current calibration, i.e., makes the mouse
    driver return mouse events in raw device coordinates instead of
    screen coordinates.

    \sa calibrate()
*/
void QWSCalibratedMouseHandler::clearCalibration()
{
    a = 1;
    b = 0;
    c = 0;
    d = 0;
    e = 1;
    f = 0;
    s = 1;
}


/*!
    Saves the current calibration parameters in \c /etc/pointercal
    (separated by whitespace and in alphabetical order).

    You can override the default \c /etc/pointercal by specifying
    another file using the POINTERCAL_FILE environment variable.

    \sa readCalibration()
*/
void QWSCalibratedMouseHandler::writeCalibration()
{
    QString calFile;
    calFile = QString::fromLocal8Bit(qgetenv("POINTERCAL_FILE"));
    if (calFile.isEmpty())
        calFile = QLatin1String("/etc/pointercal");

#ifndef QT_NO_TEXTSTREAM
    QFile file(calFile);
    if (file.open(QIODevice::WriteOnly)) {
        QTextStream t(&file);
        t << a << ' ' << b << ' ' << c << ' ';
        t << d << ' ' << e << ' ' << f << ' ' << s << endl;
    } else
#endif
    {
        qCritical("QWSCalibratedMouseHandler::writeCalibration: "
                  "Could not save calibration into %s", qPrintable(calFile));
    }
}

/*!
    Reads previously written calibration parameters which are stored
    in \c /etc/pointercal (separated by whitespace and in alphabetical
    order).

    You can override the default \c /etc/pointercal by specifying
    another file using the POINTERCAL_FILE environment variable.


    \sa writeCalibration()
*/
void QWSCalibratedMouseHandler::readCalibration()
{
    QString calFile = QString::fromLocal8Bit(qgetenv("POINTERCAL_FILE"));
    if (calFile.isEmpty())
        calFile = QLatin1String("/etc/pointercal");

#ifndef QT_NO_TEXTSTREAM
    QFile file(calFile);
    if (file.open(QIODevice::ReadOnly)) {
        QTextStream t(&file);
        t >> a >> b >> c >> d >> e >> f >> s;
        if (s == 0 || t.status() != QTextStream::Ok) {
            qCritical("Corrupt calibration data");
            clearCalibration();
        }
    } else
#endif
    {
        qDebug() << "Could not read calibration:" <<calFile;
    }
}

static int ilog2(quint32 n)
{
    int result = 0;

    if (n & 0xffff0000) {
        n >>= 16;
        result += 16;
    }
    if (n & 0xff00) {
        n >>= 8;
        result += 8;}
    if (n & 0xf0) {
        n >>= 4;
        result += 4;
    }
    if (n & 0xc) {
        n >>= 2;
        result += 2;
    }
    if (n & 0x2)
        result += 1;

    return result;
}

/*!
    Updates the calibration parameters based on coordinate mapping of
    the given \a data.

    Create an instance of the QWSPointerCalibrationData class, fill in
    the device and screen coordinates and pass that object to the mouse
    driver using this function.

    \sa clearCalibration(), transform()
*/
void QWSCalibratedMouseHandler::calibrate(const QWSPointerCalibrationData *data)
{
    // Algorithm derived from
    // "How To Calibrate Touch Screens" by Carlos E. Vidales,
    // printed in Embedded Systems Programming, Vol. 15 no 6, June 2002
    // URL: http://www.embedded.com/showArticle.jhtml?articleID=9900629

    const QPoint pd0 = data->devPoints[QWSPointerCalibrationData::TopLeft];
    const QPoint pd1 = data->devPoints[QWSPointerCalibrationData::TopRight];
    const QPoint pd2 = data->devPoints[QWSPointerCalibrationData::BottomRight];
    const QPoint p0 = data->screenPoints[QWSPointerCalibrationData::TopLeft];
    const QPoint p1 = data->screenPoints[QWSPointerCalibrationData::TopRight];
    const QPoint p2 = data->screenPoints[QWSPointerCalibrationData::BottomRight];

    const qint64 xd0 = pd0.x();
    const qint64 xd1 = pd1.x();
    const qint64 xd2 = pd2.x();
    const qint64 yd0 = pd0.y();
    const qint64 yd1 = pd1.y();
    const qint64 yd2 = pd2.y();
    const qint64 x0 = p0.x();
    const qint64 x1 = p1.x();
    const qint64 x2 = p2.x();
    const qint64 y0 = p0.y();
    const qint64 y1 = p1.y();
    const qint64 y2 = p2.y();

    qint64 scale = ((xd0 - xd2)*(yd1 - yd2) - (xd1 - xd2)*(yd0 - yd2));
    int shift = 0;
    qint64 absScale = qAbs(scale);
    // use maximum 16 bit precision to reduce risk of integer overflow
    if (absScale > (1 << 16)) {
        shift = ilog2(absScale >> 16) + 1;
        scale >>= shift;
    }

    s = scale;
    a = ((x0 - x2)*(yd1 - yd2) - (x1 - x2)*(yd0 - yd2)) >> shift;
    b = ((xd0 - xd2)*(x1 - x2) - (x0 - x2)*(xd1 - xd2)) >> shift;
    c = (yd0*(xd2*x1 - xd1*x2) + yd1*(xd0*x2 - xd2*x0) + yd2*(xd1*x0 - xd0*x1)) >> shift;
    d = ((y0 - y2)*(yd1 - yd2) - (y1 - y2)*(yd0 - yd2)) >> shift;
    e = ((xd0 - xd2)*(y1 - y2) - (y0 - y2)*(xd1 - xd2)) >> shift;
    f = (yd0*(xd2*y1 - xd1*y2) + yd1*(xd0*y2 - xd2*y0) + yd2*(xd1*y0 - xd0*y1)) >> shift;

    writeCalibration();
}

/*!
    Transforms the given \a position from device coordinates to screen
    coordinates, and returns the transformed position.

    This function is typically called explicitly on the points passed
    to the QWSMouseHandler::mouseChanged() function.

    This implementation is a linear transformation using 7 parameters
    (\c a, \c b, \c c, \c d, \c e, \c f and \c s) to transform the
    device coordinates (\c Xd, \c Yd) into screen coordinates (\c Xs,
    \c Ys) using the following equations:

    \snippet doc/src/snippets/code/src_gui_embedded_qmouse_qws.cpp 0

    \sa mouseChanged()
*/
QPoint QWSCalibratedMouseHandler::transform(const QPoint &position)
{
    QPoint tp;

    tp.setX((a * position.x() + b * position.y() + c) / s);
    tp.setY((d * position.x() + e * position.y() + f) / s);

    return tp;
}

/*!
    Sets the size of the filter used in noise reduction to the given
    \a size.

    The sendFiltered() function reduces noice by calculating an
    average position from a collection of mouse event positions. The
    filter size determines the number of positions that forms the
    basis for these calculations.

    \sa sendFiltered()
*/
void QWSCalibratedMouseHandler::setFilterSize(int size)
{
    samples.resize(qMax(1, size));
    numSamples = 0;
    currSample = 0;
}

/*!
    \fn bool QWSCalibratedMouseHandler::sendFiltered(const QPoint &position, int state)

    Notifies the system of a new mouse event \e after applying a noise
    reduction filter. Returns true if the filtering process is
    successful; otherwise returns false. Note that if the filtering
    process failes, the system is not notified about the event.

    The given \a position is the global position of the mouse. The \a
    state parameter is a bitmask of the Qt::MouseButton enum's values
    indicating which mouse buttons are pressed.

    The noice is reduced by calculating an average position from a
    collection of mouse event positions and then calling the
    mouseChanged() function with the new position. The number of
    positions that is used is determined by the filter size.

    \sa mouseChanged(), setFilterSize()
*/
bool QWSCalibratedMouseHandler::sendFiltered(const QPoint &position, int button)
{
    if (!button) {
        if (numSamples >= samples.count())
            mouseChanged(transform(position), 0);
        currSample = 0;
        numSamples = 0;
        return true;
    }

    bool sent = false;
    samples[currSample] = position;
    numSamples++;
    if (numSamples >= samples.count()) {

        int ignore = -1;
        if (samples.count() > 2) { // throw away the "worst" sample
            int maxd = 0;
            for (int i = 0; i < samples.count(); i++) {
                int d = (mousePos - samples[i]).manhattanLength();
                if (d > maxd) {
                    maxd = d;
                    ignore = i;
                }
            }
        }

        // average the rest
        QPoint pos(0, 0);
        int numAveraged = 0;
        for (int i = 0; i < samples.count(); i++) {
            if (ignore == i)
                continue;
            pos += samples[i];
            ++numAveraged;
        }
        if (numAveraged)
            pos /= numAveraged;

        mouseChanged(transform(pos), button);
        sent = true;
    }
    currSample++;
    if (currSample >= samples.count())
        currSample = 0;

    return sent;
}

QT_END_NAMESPACE