NxWidgets/NxWM updates from Petteri Aimonen (Patches 0007-0013)
git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@5689 42af7a65-404d-4744-a932-0658087f49c3
This commit is contained in:
parent
a96a12751a
commit
804bd95e05
@ -280,4 +280,50 @@
|
||||
it works with indexed input images.
|
||||
* NxWidgets::CLabel: Fix backward conditional compilation in the
|
||||
"flicker free" logic.
|
||||
|
||||
* NxWidgets::CNxTimer: Previously repeated timers were re-enabled after
|
||||
the timer action event. Consequently, if the action event handler tried
|
||||
to stop the timer, the request would be ignored. Changes the order
|
||||
so that the timer is re-enabled before the callback. There is still
|
||||
no risk of re-entrancy, because everything executes on the USRWORK work
|
||||
queue. From Petteri Aimonen.
|
||||
* NxWidgets::CMultiLineTestBox: Fix text placement error. From Petteri
|
||||
Aimonen.
|
||||
* NxWidgets::CWidgetControl: Added another semaphore, boundssem, which
|
||||
is set as soon as the screen bounds are known. This corrects two
|
||||
problems:
|
||||
1) Due to the way nxgl_rectsize computes the size, it will never
|
||||
be 0,0 like CWidgetControl expects. Therefore the size is considered
|
||||
valid even though it has not been set yet.
|
||||
2) After the check is fixed to test for > 1, NxWM window creation will
|
||||
hang. This is due to the fact that it uses the screen bounds for
|
||||
determining window size. This was being blocked on geosem, which
|
||||
is only posted after the size has been set.
|
||||
From Petteri Aimonen.
|
||||
* NxWidgets::CImage: Two enhancements:
|
||||
1) Allow changing the bitmap even after the control has been created.
|
||||
2) Allow giving 'null' to have the control draw no image at all.
|
||||
From Petteri Aimonen.
|
||||
* NxWM::CTaskBar: Allow windows with null icon. This makes sense for e.g.
|
||||
full screen windows. From Petteri Aimonen.
|
||||
* NxWM::CApplicationWindow: Add config options to override NxWM
|
||||
stop/minimize icons. From Petteri Aimonen.
|
||||
* NwWM::CStartWindow, NxWM::CWindowMessenger: Get rid of the start window
|
||||
thread. Instead, handle all events through the USRWORK work queue.
|
||||
For me, this was necessary because I would open some files in button
|
||||
handlers and close them in NxTimer handlers. If these belonged to
|
||||
different tasks, the close operation would fail. Further benefits:
|
||||
+ Gets rid of one task and message queue.
|
||||
+ Reduces the amount of code required
|
||||
+ Decouples CStartWindow from everything else - now it is just a window
|
||||
with application icons, not an integral part of the event logic.
|
||||
+ All events come from the same thread, which reduces the possibility of
|
||||
multithreading errors in user code.
|
||||
+ The user code can also send events to USRWORK, so that everything gets
|
||||
serialized nicely without having to use so many mutexes.
|
||||
Drawbacks:
|
||||
- Currently the work state structure is malloc()ed, causing one allocation
|
||||
and free per each input event. Could add a memory pool for these later, but
|
||||
the speed difference doesn't seem noticeable.
|
||||
- The work queue will add ~50 ms latency to input events. This is however
|
||||
configurable, and the delay is anyway short enough that it is unnoticeable.
|
||||
From Petteri Aimonen.
|
||||
|
24
Kconfig
24
Kconfig
@ -463,6 +463,30 @@ config NXWM_BACKGROUND_IMAGE
|
||||
The name of the image to use in the background window. Default:
|
||||
NXWidgets::g_nuttxBitmap
|
||||
|
||||
comment "Application Window Configuration"
|
||||
|
||||
config NXWM_CUSTOM_APPWINDOW_ICONS
|
||||
bool "Custom Start/Stop Application Window Icons"
|
||||
default n
|
||||
---help---
|
||||
Select to override the default Application Window Stop and Minimize Icons.
|
||||
|
||||
if NXWM_CUSTOM_APPWINDOW_ICONS
|
||||
|
||||
config NXWM_STOP_BITMAP
|
||||
string "Stop Icon"
|
||||
default "NxWM::g_stopBitmap"
|
||||
---help---
|
||||
The glyph to use as the Stop icon. Default: NxWM::g_stopBitmap
|
||||
|
||||
config NXWM_MINIMIZE_BITMAP
|
||||
string "Minimize Icon"
|
||||
default "NxWM::g_minimizeBitmap"
|
||||
---help---
|
||||
The glyph to use as the Minimize icon. Default: NxWM::g_minimizeBitmap
|
||||
|
||||
endif
|
||||
|
||||
comment "Start Window Configuration"
|
||||
|
||||
comment "Horizontal and vertical spacing of icons in the task bar"
|
||||
|
@ -1,7 +1,7 @@
|
||||
/****************************************************************************
|
||||
* NxWidgets/libnxwidgets/include/cimage.hxx
|
||||
*
|
||||
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
|
||||
* Copyright (C) 2012-2013 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -207,6 +207,12 @@ namespace NXWidgets
|
||||
|
||||
inline FAR IBitmap *getBitmap() const { return m_bitmap; }
|
||||
|
||||
/**
|
||||
* Set the bitmap that this image contains.
|
||||
*/
|
||||
|
||||
inline void setBitmap(FAR IBitmap *bitmap) { m_bitmap = bitmap; }
|
||||
|
||||
/**
|
||||
* Insert the dimensions that this widget wants to have into the rect
|
||||
* passed in as a parameter. All coordinates are relative to the
|
||||
|
@ -1,7 +1,7 @@
|
||||
/****************************************************************************
|
||||
* NxWidgets/libnxwidgets/include/cwidgetcontrol.hxx
|
||||
*
|
||||
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
|
||||
* Copyright (C) 2012-2013 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -39,11 +39,12 @@
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <semaphore.h>
|
||||
#include <time.h>
|
||||
|
||||
@ -203,6 +204,7 @@ namespace NXWidgets
|
||||
struct nxgl_rect_s m_bounds; /**< Size of the display */
|
||||
#ifdef CONFIG_NX_MULTIUSER
|
||||
sem_t m_geoSem; /**< Posted when geometry is valid */
|
||||
sem_t m_boundsSem; /**< Posted when bounds are valid */
|
||||
#endif
|
||||
CWindowEventHandlerList m_eventHandlers; /**< List of event handlers. */
|
||||
|
||||
@ -228,7 +230,7 @@ namespace NXWidgets
|
||||
* @param startTime A time in the past from which to compute the elapsed time.
|
||||
* @return The elapsed time since startTime
|
||||
*/
|
||||
|
||||
|
||||
uint32_t elapsedTime(FAR const struct timespec *startTime);
|
||||
|
||||
/**
|
||||
@ -256,7 +258,7 @@ namespace NXWidgets
|
||||
/**
|
||||
* Delete any widgets in the deletion queue.
|
||||
*/
|
||||
|
||||
|
||||
void processDeleteQueue(void);
|
||||
|
||||
/**
|
||||
@ -324,6 +326,37 @@ namespace NXWidgets
|
||||
giveGeoSem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the bounds semaphore (handling signal interruptions)
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_NX_MULTIUSER
|
||||
void takeBoundsSem(void);
|
||||
#else
|
||||
inline void takeBoundsSem(void) {}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Give the bounds semaphore
|
||||
*/
|
||||
|
||||
inline void giveBoundsSem(void)
|
||||
{
|
||||
#ifdef CONFIG_NX_MULTIUSER
|
||||
sem_post(&m_boundsSem);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for bounds data
|
||||
*/
|
||||
|
||||
inline void waitBoundsData(void)
|
||||
{
|
||||
takeBoundsSem();
|
||||
giveBoundsSem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all mouse events
|
||||
*/
|
||||
@ -345,7 +378,7 @@ namespace NXWidgets
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
|
||||
|
||||
virtual ~CWidgetControl(void);
|
||||
|
||||
/**
|
||||
@ -407,7 +440,7 @@ namespace NXWidgets
|
||||
* all widgets in the window.
|
||||
* @return True means some interesting event occurred
|
||||
*/
|
||||
|
||||
|
||||
bool pollEvents(CNxWidget *widget = (CNxWidget *)NULL);
|
||||
|
||||
/**
|
||||
@ -425,7 +458,7 @@ namespace NXWidgets
|
||||
*
|
||||
* @param widget The widget to be controlled.
|
||||
*/
|
||||
|
||||
|
||||
inline void addControlledWidget(CNxWidget* widget)
|
||||
{
|
||||
m_widgets.push_back(widget);
|
||||
@ -438,7 +471,7 @@ namespace NXWidgets
|
||||
*/
|
||||
|
||||
void removeControlledWidget(CNxWidget* widget);
|
||||
|
||||
|
||||
/**
|
||||
* Get the number of controlled widgets.
|
||||
*
|
||||
@ -456,7 +489,7 @@ namespace NXWidgets
|
||||
*
|
||||
* @param widget The widget to add to the delete queue.
|
||||
*/
|
||||
|
||||
|
||||
void addToDeleteQueue(CNxWidget *widget);
|
||||
|
||||
/**
|
||||
@ -469,7 +502,7 @@ namespace NXWidgets
|
||||
void setClickedWidget(CNxWidget *widget);
|
||||
|
||||
/**
|
||||
* Get the clicked widget pointer.
|
||||
* Get the clicked widget pointer.
|
||||
*
|
||||
* @return Pointer to the clicked widget.
|
||||
*/
|
||||
@ -502,7 +535,7 @@ namespace NXWidgets
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the focused widget pointer.
|
||||
* Get the focused widget pointer.
|
||||
*
|
||||
* @return Pointer to the focused widget.
|
||||
*/
|
||||
@ -513,7 +546,7 @@ namespace NXWidgets
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for the occurrence of a double click.
|
||||
* Check for the occurrence of a double click.
|
||||
*
|
||||
* @return Pointer to the clicked widget.
|
||||
*/
|
||||
@ -582,7 +615,7 @@ namespace NXWidgets
|
||||
* @param pos The (x,y) position of the mouse.
|
||||
* @param buttons See NX_MOUSE_* definitions.
|
||||
*/
|
||||
|
||||
|
||||
void newMouseEvent(FAR const struct nxgl_point_s *pos, uint8_t buttons);
|
||||
|
||||
/**
|
||||
@ -624,7 +657,7 @@ namespace NXWidgets
|
||||
*
|
||||
* @param cursorControl The cursor control code received.
|
||||
*/
|
||||
|
||||
|
||||
void newCursorControlEvent(ECursorControl cursorControl);
|
||||
|
||||
/**
|
||||
@ -639,21 +672,21 @@ namespace NXWidgets
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the window bounding box in physical display coordinated. This
|
||||
* method may need to wait until geometry data is available.
|
||||
* Get the window bounding box in physical display coordinates. This
|
||||
* method may need to wait until bounds data is available.
|
||||
*
|
||||
* @return This function returns the window handle.
|
||||
*/
|
||||
|
||||
inline CRect getWindowBoundingBox(void)
|
||||
{
|
||||
waitGeoData();
|
||||
waitBoundsData();
|
||||
return CRect(&m_bounds);
|
||||
}
|
||||
|
||||
inline void getWindowBoundingBox(FAR struct nxgl_rect_s *bounds)
|
||||
{
|
||||
waitGeoData();
|
||||
waitBoundsData();
|
||||
nxgl_rectcopy(bounds, &m_bounds);
|
||||
}
|
||||
|
||||
@ -759,7 +792,7 @@ namespace NXWidgets
|
||||
|
||||
inline void removeWindowEventHandler(CWindowEventHandler *eventHandler)
|
||||
{
|
||||
m_eventHandlers.removeWindowEventHandler(eventHandler);
|
||||
m_eventHandlers.removeWindowEventHandler(eventHandler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/****************************************************************************
|
||||
* NxWidgets/libnxwidgets/include/cimage.cxx
|
||||
*
|
||||
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
|
||||
* Copyright (C) 2012-2013 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -88,7 +88,7 @@
|
||||
/****************************************************************************
|
||||
* Pre-Processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* Method Implementations
|
||||
****************************************************************************/
|
||||
@ -163,6 +163,13 @@ void CImage::getPreferredDimensions(CRect &rect) const
|
||||
|
||||
void CImage::drawContents(CGraphicsPort *port)
|
||||
{
|
||||
if (!m_bitmap)
|
||||
{
|
||||
// No image to draw
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the the drawable region
|
||||
|
||||
CRect rect;
|
||||
@ -187,11 +194,11 @@ void CImage::drawContents(CGraphicsPort *port)
|
||||
m_bitmap->setSelected(isClicked() || m_highlighted);
|
||||
|
||||
// This is the number of rows that we can draw at the top of the display
|
||||
|
||||
|
||||
nxgl_coord_t nTopRows = m_bitmap->getHeight() - m_origin.y;
|
||||
if (nTopRows > rect.getHeight())
|
||||
{
|
||||
nTopRows = rect.getHeight();
|
||||
nTopRows = rect.getHeight();
|
||||
}
|
||||
else if (nTopRows < 0)
|
||||
{
|
||||
@ -214,7 +221,7 @@ void CImage::drawContents(CGraphicsPort *port)
|
||||
// the display
|
||||
|
||||
nxgl_coord_t nLeftPixels = m_bitmap->getWidth() - m_origin.x;
|
||||
|
||||
|
||||
// This is the number of rows that we have to pad on the right if the display
|
||||
// width is wider than the image width
|
||||
|
||||
@ -257,7 +264,7 @@ void CImage::drawContents(CGraphicsPort *port)
|
||||
|
||||
// Replace any transparent pixels with the background color.
|
||||
// Then we can use the faster opaque drawBitmap() function.
|
||||
|
||||
|
||||
ptr = buffer;
|
||||
for (int i = 0; i < nLeftPixels; i++, ptr++)
|
||||
{
|
||||
@ -337,12 +344,12 @@ void CImage::drawBorder(CGraphicsPort *port)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Work out which colors to use
|
||||
|
||||
nxgl_coord_t color1;
|
||||
nxgl_coord_t color2;
|
||||
|
||||
|
||||
if (isClicked())
|
||||
{
|
||||
// Bevelled into the screen
|
||||
@ -357,7 +364,7 @@ void CImage::drawBorder(CGraphicsPort *port)
|
||||
color1 = getShineEdgeColor();
|
||||
color2 = getShadowEdgeColor();
|
||||
}
|
||||
|
||||
|
||||
port->drawBevelledRect(getX(), getY(), getWidth(), getHeight(), color1, color2);
|
||||
}
|
||||
|
||||
@ -389,7 +396,7 @@ void CImage::onClick(nxgl_coord_t x, nxgl_coord_t y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises an action.
|
||||
* Raises an action.
|
||||
*
|
||||
* @param x The x coordinate of the mouse.
|
||||
* @param y The y coordinate of the mouse.
|
||||
|
@ -1261,8 +1261,8 @@ void CMultiLineTextBox::drawRow(CGraphicsPort *port, int row)
|
||||
uint8_t rowLength = m_text->getLineTrimmedLength(row);
|
||||
|
||||
struct nxgl_point_s pos;
|
||||
pos.x = getRowX(row) + m_canvasX;
|
||||
pos.y = getRowY(row) + m_canvasY;
|
||||
pos.x = getRowX(row) + m_canvasX + rect.getX();
|
||||
pos.y = getRowY(row) + m_canvasY + rect.getY();
|
||||
|
||||
// Determine the background and text color
|
||||
|
||||
|
@ -193,16 +193,16 @@ void CNxTimer::workQueueCallback(FAR void *arg)
|
||||
|
||||
This->m_isRunning = false;
|
||||
|
||||
// Raise the action event.
|
||||
|
||||
This->m_widgetEventHandlers->raiseActionEvent();
|
||||
|
||||
// Restart the timer if this is a repeating timer
|
||||
|
||||
if (This->m_isRepeater)
|
||||
{
|
||||
This->start();
|
||||
}
|
||||
|
||||
// Raise the action event.
|
||||
|
||||
This->m_widgetEventHandlers->raiseActionEvent();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/****************************************************************************
|
||||
* NxWidgets/libnxwidgets/src/cwidgetcontrol.cxx
|
||||
*
|
||||
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
|
||||
* Copyright (C) 2012-2013 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -117,6 +117,7 @@ CWidgetControl::CWidgetControl(FAR const CWidgetStyle *style)
|
||||
sem_init(&m_waitSem, 0, 0);
|
||||
#endif
|
||||
#ifdef CONFIG_NX_MULTIUSER
|
||||
sem_init(&m_boundsSem, 0, 0);
|
||||
sem_init(&m_geoSem, 0, 0);
|
||||
#endif
|
||||
|
||||
@ -135,12 +136,11 @@ CWidgetControl::CWidgetControl(FAR const CWidgetStyle *style)
|
||||
copyWidgetStyle(&m_style, style);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
|
||||
|
||||
CWidgetControl::~CWidgetControl(void)
|
||||
{
|
||||
// Notify any external waiters... this should not happen because it
|
||||
@ -235,7 +235,7 @@ void CWidgetControl::postWindowEvent(void)
|
||||
* all widgets in the window.
|
||||
* @return True means some interesting event occurred
|
||||
*/
|
||||
|
||||
|
||||
bool CWidgetControl::pollEvents(CNxWidget *widget)
|
||||
{
|
||||
// Delete any queued widgets
|
||||
@ -299,7 +299,7 @@ void CWidgetControl::removeControlledWidget(CNxWidget *widget)
|
||||
*
|
||||
* @param widget The widget to add to the delete queue.
|
||||
*/
|
||||
|
||||
|
||||
void CWidgetControl::addToDeleteQueue(CNxWidget *widget)
|
||||
{
|
||||
// Add the widget to the delete queue
|
||||
@ -330,7 +330,7 @@ void CWidgetControl::setClickedWidget(CNxWidget *widget)
|
||||
|
||||
m_clickedWidget->release(m_clickedWidget->getX() - 10, 0);
|
||||
}
|
||||
|
||||
|
||||
// Update the pointer
|
||||
|
||||
m_clickedWidget = widget;
|
||||
@ -394,18 +394,19 @@ void CWidgetControl::geometryEvent(NXHANDLE hWindow,
|
||||
if (!m_hWindow)
|
||||
{
|
||||
// Save one-time server specific information
|
||||
|
||||
|
||||
m_hWindow = hWindow;
|
||||
nxgl_rectcopy(&m_bounds, bounds);
|
||||
giveBoundsSem();
|
||||
}
|
||||
|
||||
// In the normal start up sequence, the window is created with zero size
|
||||
// at position 0,0. The safe thing to do is to set the position (still
|
||||
// with size 0, then then set the size. Assuming that is what is being
|
||||
// done, we will not report that we have valid geometry until the size
|
||||
// becomes nonzero.
|
||||
// becomes nonzero (or actually over 1).
|
||||
|
||||
if (!m_haveGeometry && size->h > 0 && size->w > 0)
|
||||
if (!m_haveGeometry && size->h > 1 && size->w > 1)
|
||||
{
|
||||
// Wake up any threads waiting for initial position information.
|
||||
// REVISIT: If the window is moved or repositioned, then the
|
||||
@ -619,7 +620,7 @@ void CWidgetControl::newKeyboardEvent(uint8_t nCh, FAR const uint8_t *pStr)
|
||||
*
|
||||
* @param cursorControl The cursor control code received.
|
||||
*/
|
||||
|
||||
|
||||
void CWidgetControl::newCursorControlEvent(ECursorControl cursorControl)
|
||||
{
|
||||
// Append the new cursor control
|
||||
@ -688,7 +689,7 @@ void CWidgetControl::copyWidgetStyle(CWidgetStyle *dest, const CWidgetStyle *src
|
||||
* @param tp A time in the past from which to compute the elapsed time.
|
||||
* @return The elapsed time since tp
|
||||
*/
|
||||
|
||||
|
||||
uint32_t CWidgetControl::elapsedTime(FAR const struct timespec *startTime)
|
||||
{
|
||||
struct timespec endTime;
|
||||
@ -701,7 +702,7 @@ uint32_t CWidgetControl::elapsedTime(FAR const struct timespec *startTime)
|
||||
uint32_t seconds = endTime.tv_sec - startTime->tv_sec;
|
||||
|
||||
// Get the elapsed nanoseconds, borrowing from the seconds if necessary
|
||||
|
||||
|
||||
int32_t endNanoSeconds = endTime.tv_nsec;
|
||||
if (startTime->tv_nsec > endNanoSeconds)
|
||||
{
|
||||
@ -767,7 +768,7 @@ void CWidgetControl::handleLeftClick(nxgl_coord_t x, nxgl_coord_t y, CNxWidget *
|
||||
/**
|
||||
* Delete any widgets in the deletion queue.
|
||||
*/
|
||||
|
||||
|
||||
void CWidgetControl::processDeleteQueue(void)
|
||||
{
|
||||
int i = 0;
|
||||
@ -916,6 +917,25 @@ void CWidgetControl::takeGeoSem(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Take the bounds semaphore (handling signal interruptions)
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_NX_MULTIUSER
|
||||
void CWidgetControl::takeBoundsSem(void)
|
||||
{
|
||||
// Take the bounds semaphore. Retry is an error occurs (only if
|
||||
// the error is due to a signal interruption).
|
||||
|
||||
int ret;
|
||||
do
|
||||
{
|
||||
ret = sem_wait(&m_boundsSem);
|
||||
}
|
||||
while (ret < 0 && errno == EINTR);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Clear all mouse events
|
||||
*/
|
||||
|
@ -119,7 +119,6 @@ namespace NxWM
|
||||
CApplicationWindow *m_window; /**< Reference to the application window */
|
||||
TNxArray<struct SStartWindowSlot> m_slots; /**< List of apps in the start window */
|
||||
struct nxgl_size_s m_iconSize; /**< A box big enough to hold the largest icon */
|
||||
pid_t m_taskId; /**< ID of the start window task */
|
||||
|
||||
/**
|
||||
* This is the start window task. This function receives window events from
|
||||
|
@ -1,7 +1,7 @@
|
||||
/****************************************************************************
|
||||
* NxWidgets/nxwm/include/cwindowmessenger.hxx
|
||||
*
|
||||
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
|
||||
* Copyright (C) 2012-2013 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -39,12 +39,12 @@
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <mqueue.h>
|
||||
|
||||
#include <nuttx/wqueue.h>
|
||||
#include <nuttx/nx/nxtk.h>
|
||||
#include <nuttx/nx/nxconsole.h>
|
||||
|
||||
@ -83,9 +83,20 @@ namespace NxWM
|
||||
public NXWidgets::CWidgetControl
|
||||
{
|
||||
private:
|
||||
mqd_t m_mqd; /**< Message queue descriptor used to commincate with the
|
||||
** start window thread. */
|
||||
|
||||
/** Structure that stores data for the work queue callback. */
|
||||
|
||||
struct work_state_t
|
||||
{
|
||||
work_s work;
|
||||
CWindowMessenger *windowMessenger;
|
||||
void *instance;
|
||||
};
|
||||
|
||||
/** Work queue callback functions */
|
||||
|
||||
static void inputWorkCallback(FAR void *arg);
|
||||
static void destroyWorkCallback(FAR void *arg);
|
||||
|
||||
/**
|
||||
* Handle an NX window mouse input event.
|
||||
*
|
||||
@ -109,7 +120,7 @@ namespace NxWM
|
||||
*
|
||||
* @param arg - User provided argument (see nx_block or nxtk_block)
|
||||
*/
|
||||
|
||||
|
||||
void handleBlockedEvent(FAR void *arg);
|
||||
|
||||
public:
|
||||
|
@ -1,7 +1,7 @@
|
||||
/********************************************************************************************
|
||||
* NxWidgets/nxwm/src/capplicationwindow.cxx
|
||||
*
|
||||
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
|
||||
* Copyright (C) 2012-2013 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -54,6 +54,18 @@
|
||||
* Pre-Processor Definitions
|
||||
********************************************************************************************/
|
||||
|
||||
#ifdef CONFIG_NXWM_STOP_BITMAP
|
||||
extern const struct NXWidgets::SRlePaletteBitmap CONFIG_NXWM_STOP_BITMAP;
|
||||
#else
|
||||
# define CONFIG_NXWM_STOP_BITMAP g_stopBitmap
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NXWM_MINIMIZE_BITMAP
|
||||
extern const struct NXWidgets::SRlePaletteBitmap CONFIG_NXWM_MINIMIZE_BITMAP;
|
||||
#else
|
||||
# define CONFIG_NXWM_MINIMIZE_BITMAP g_minimizeBitmap
|
||||
#endif
|
||||
|
||||
/********************************************************************************************
|
||||
* CApplicationWindow Method Implementations
|
||||
********************************************************************************************/
|
||||
@ -192,7 +204,7 @@ bool CApplicationWindow::open(void)
|
||||
{
|
||||
// Create STOP bitmap container
|
||||
|
||||
m_stopBitmap = new NXWidgets::CRlePaletteBitmap(&g_stopBitmap);
|
||||
m_stopBitmap = new NXWidgets::CRlePaletteBitmap(&CONFIG_NXWM_STOP_BITMAP);
|
||||
if (!m_stopBitmap)
|
||||
{
|
||||
return false;
|
||||
@ -244,7 +256,7 @@ bool CApplicationWindow::open(void)
|
||||
#ifndef CONFIG_NXWM_DISABLE_MINIMIZE
|
||||
// Create MINIMIZE application bitmap container
|
||||
|
||||
m_minimizeBitmap = new NXWidgets::CRlePaletteBitmap(&g_minimizeBitmap);
|
||||
m_minimizeBitmap = new NXWidgets::CRlePaletteBitmap(&CONFIG_NXWM_MINIMIZE_BITMAP);
|
||||
if (!m_minimizeBitmap)
|
||||
{
|
||||
return false;
|
||||
@ -375,7 +387,7 @@ void CApplicationWindow::redraw(void)
|
||||
m_minimizeImage->redraw();
|
||||
m_minimizeImage->setRaisesEvents(true);
|
||||
}
|
||||
|
||||
|
||||
// And finally draw the window label
|
||||
|
||||
m_windowLabel->enableDrawing();
|
||||
@ -406,7 +418,7 @@ void CApplicationWindow::hide(void)
|
||||
m_minimizeImage->disableDrawing();
|
||||
m_minimizeImage->setRaisesEvents(false);
|
||||
}
|
||||
|
||||
|
||||
// Disable the window label
|
||||
|
||||
m_windowLabel->disableDrawing();
|
||||
|
@ -1,7 +1,7 @@
|
||||
/********************************************************************************************
|
||||
* NxWidgets/nxwm/src/cstartwindow.cxx
|
||||
*
|
||||
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
|
||||
* Copyright (C) 2012-2013 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -44,8 +44,6 @@
|
||||
#include <csched>
|
||||
#include <cerrno>
|
||||
|
||||
#include <mqueue.h>
|
||||
|
||||
#include "cwidgetcontrol.hxx"
|
||||
|
||||
#include "nxwmconfig.hxx"
|
||||
@ -66,7 +64,7 @@
|
||||
*/
|
||||
|
||||
FAR const char *NxWM::g_startWindowMqName = CONFIG_NXWM_STARTWINDOW_MQNAME;
|
||||
|
||||
|
||||
/********************************************************************************************
|
||||
* CStartWindow Method Implementations
|
||||
********************************************************************************************/
|
||||
@ -89,10 +87,6 @@ CStartWindow::CStartWindow(CTaskbar *taskbar, CApplicationWindow *window)
|
||||
m_taskbar = taskbar;
|
||||
m_window = window;
|
||||
|
||||
// The start window task is not running
|
||||
|
||||
m_taskId = -1;
|
||||
|
||||
// Add our personalized window label
|
||||
|
||||
NXWidgets::CNxString myName = getName();
|
||||
@ -169,24 +163,7 @@ NXWidgets::CNxString CStartWindow::getName(void)
|
||||
|
||||
bool CStartWindow::run(void)
|
||||
{
|
||||
// Some sanity checking
|
||||
|
||||
if (m_taskId >= 0)
|
||||
{
|
||||
// The start window task is already running???
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start the start window task
|
||||
|
||||
m_taskId = TASK_CREATE("StartWindow", CONFIG_NXWM_STARTWINDOW_PRIO,
|
||||
CONFIG_NXWM_STARTWINDOW_STACKSIZE, startWindow,
|
||||
(FAR char * const *)0);
|
||||
|
||||
// Did we successfully start the NxConsole task?
|
||||
|
||||
return m_taskId >= 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,21 +172,8 @@ bool CStartWindow::run(void)
|
||||
|
||||
void CStartWindow::stop(void)
|
||||
{
|
||||
// Delete the start window task --- what are we doing? This should never
|
||||
// happen because the start window task is persistent!
|
||||
|
||||
if (m_taskId >= 0)
|
||||
{
|
||||
// Call task_delete(), possibly stranding resources
|
||||
|
||||
pid_t pid = m_taskId;
|
||||
m_taskId = -1;
|
||||
|
||||
// Then delete the NSH task
|
||||
|
||||
task_delete(pid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the application and free all of its resources. This method
|
||||
* will initiate blocking of messages from the NX server. The server
|
||||
@ -532,7 +496,7 @@ void CStartWindow::getIconBounds(void)
|
||||
|
||||
void CStartWindow::removeAllApplications(void)
|
||||
{
|
||||
// Stop all applications and remove them from the start window. Clearly, there
|
||||
// Stop all applications and remove them from the start window. Clearly, there
|
||||
// are some ordering issues here... On an orderly system shutdown, disconnection
|
||||
// should really occur priority to deleting instances
|
||||
|
||||
@ -543,7 +507,7 @@ void CStartWindow::removeAllApplications(void)
|
||||
IApplicationFactory *app = m_slots.at(0).app;
|
||||
|
||||
// Now, delete the image and the application
|
||||
|
||||
|
||||
delete app;
|
||||
delete m_slots.at(0).image;
|
||||
|
||||
@ -596,108 +560,3 @@ void CStartWindow::handleActionEvent(const NXWidgets::CWidgetEventArgs &e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the start window task. This function receives window events from
|
||||
* the NX listener threads indirectly through this sequence:
|
||||
*
|
||||
* 1. NX listener thread receives a windows event. This may be a
|
||||
* positional change notification, a redraw request, or mouse or
|
||||
* keyboard input.
|
||||
* 2. The NX listener thread performs the callback by calling a
|
||||
* NXWidgets::CCallback method associated with the window.
|
||||
* 3. NXWidgets::CCallback calls into NXWidgets::CWidgetControl to process
|
||||
* the event.
|
||||
* 4. NXWidgets::CWidgetControl records the new state data and raises a
|
||||
* window event.
|
||||
* 5. NXWidgets::CWindowEventHandlerList will give the event to
|
||||
* NxWM::CWindowMessenger.
|
||||
* 6. NxWM::CWindowMessenger will send the a message on a well-known message
|
||||
* queue.
|
||||
* 7. This CStartWindow::startWindow task will receive and process that
|
||||
* message.
|
||||
*/
|
||||
|
||||
int CStartWindow::startWindow(int argc, char *argv[])
|
||||
{
|
||||
/* Open a well-known message queue for reading */
|
||||
|
||||
struct mq_attr attr;
|
||||
attr.mq_maxmsg = CONFIG_NXWM_STARTWINDOW_MXMSGS;
|
||||
attr.mq_msgsize = sizeof(struct SStartWindowMessage);
|
||||
attr.mq_flags = 0;
|
||||
|
||||
mqd_t mqd = mq_open(g_startWindowMqName, O_RDONLY|O_CREAT, 0666, &attr);
|
||||
if (mqd == (mqd_t)-1)
|
||||
{
|
||||
gdbg("ERROR: mq_open(%s) failed: %d\n", g_startWindowMqName, errno);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Now loop forever, receiving and processing messages. Ultimately, all
|
||||
// widget driven events (button presses, etc.) are driven by this logic
|
||||
// on this thread.
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Receive the next message
|
||||
|
||||
struct SStartWindowMessage msg;
|
||||
ssize_t nbytes = mq_receive(mqd, &msg, sizeof(struct SStartWindowMessage), 0);
|
||||
if (nbytes < 0)
|
||||
{
|
||||
int errval = errno;
|
||||
|
||||
// EINTR is not an error. The wait was interrupted by a signal and
|
||||
// we just need to try reading again.
|
||||
|
||||
if (errval != EINTR)
|
||||
{
|
||||
gdbg("ERROR: mq_receive failed: %d\n", errval);
|
||||
}
|
||||
else
|
||||
{
|
||||
gdbg("mq_receive interrupted by signal\n");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
gvdbg("Received msgid=%d nbytes=%d\n", msg.msgId, nbytes);
|
||||
DEBUGASSERT(nbytes = sizeof(struct SStartWindowMessage) && msg.instance);
|
||||
|
||||
// Dispatch the message to the appropriate CWidgetControl and to the
|
||||
// appropriate CWidgetControl method
|
||||
|
||||
switch (msg.msgId)
|
||||
{
|
||||
break;
|
||||
|
||||
case MSGID_MOUSE_INPUT: // New mouse input is available
|
||||
case MSGID_KEYBOARD_INPUT: // New keyboard input is available
|
||||
{
|
||||
// Handle all new window input events by calling the CWidgetControl::pollEvents() method
|
||||
|
||||
NXWidgets::CWidgetControl *control = (NXWidgets::CWidgetControl *)msg.instance;
|
||||
control->pollEvents();
|
||||
}
|
||||
break;
|
||||
|
||||
case MSGID_DESTROY_APP: // Destroy the application
|
||||
{
|
||||
// Handle all destroy application events
|
||||
|
||||
gdbg("Deleting app=%p\n", msg.instance);
|
||||
IApplication *app = (IApplication *)msg.instance;
|
||||
delete app;
|
||||
}
|
||||
break;
|
||||
|
||||
case MSGID_POSITIONAL_CHANGE: // Change in window positional data (not used)
|
||||
case MSGID_REDRAW_REQUEST: // Request to redraw a portion of the window (not used)
|
||||
default:
|
||||
gdbg("ERROR: Unrecognized or unsupported msgId: %d\n", (int)msg.msgId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/********************************************************************************************
|
||||
* NxWidgets/nxwm/src/ctaskbar.cxx
|
||||
*
|
||||
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
|
||||
* Copyright (C) 2012-2013 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -456,11 +456,21 @@ bool CTaskbar::startApplication(IApplication *app, bool minimized)
|
||||
|
||||
NXWidgets::IBitmap *bitmap = app->getIcon();
|
||||
|
||||
// Create a CImage instance to manage the applications icon
|
||||
// Create a CImage instance to manage the applications icon. Assume the
|
||||
// minimum size in case no bitmap is provided (bitmap == NULL)
|
||||
|
||||
int w = 1;
|
||||
int h = 1;
|
||||
|
||||
if (bitmap)
|
||||
{
|
||||
w = bitmap->getWidth();
|
||||
h = bitmap->getHeight();
|
||||
}
|
||||
|
||||
NXWidgets::CImage *image =
|
||||
new NXWidgets::CImage(control, 0, 0, bitmap->getWidth(),
|
||||
bitmap->getHeight(), bitmap, 0);
|
||||
new NXWidgets::CImage(control, 0, 0, w, h, bitmap, 0);
|
||||
|
||||
if (!image)
|
||||
{
|
||||
return false;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/********************************************************************************************
|
||||
* NxWidgets/nxwm/src/cwindowmessenger.cxx
|
||||
*
|
||||
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
|
||||
* Copyright (C) 2012-2013 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -69,20 +69,6 @@ using namespace NxWM;
|
||||
CWindowMessenger::CWindowMessenger(FAR const NXWidgets::CWidgetStyle *style)
|
||||
: NXWidgets::CWidgetControl(style)
|
||||
{
|
||||
// Open a message queue to communicate with the start window task. We need to create
|
||||
// the message queue if it does not exist.
|
||||
|
||||
struct mq_attr attr;
|
||||
attr.mq_maxmsg = CONFIG_NXWM_STARTWINDOW_MXMSGS;
|
||||
attr.mq_msgsize = sizeof(struct SStartWindowMessage);
|
||||
attr.mq_flags = 0;
|
||||
|
||||
m_mqd = mq_open(g_startWindowMqName, O_WRONLY|O_CREAT, 0666, &attr);
|
||||
if (m_mqd == (mqd_t)-1)
|
||||
{
|
||||
gdbg("ERROR: mq_open(%s) failed: %d\n", g_startWindowMqName, errno);
|
||||
}
|
||||
|
||||
// Add ourself to the list of window event handlers
|
||||
|
||||
addWindowEventHandler(this);
|
||||
@ -97,10 +83,6 @@ CWindowMessenger::~CWindowMessenger(void)
|
||||
// Remove ourself from the list of the window event handlers
|
||||
|
||||
removeWindowEventHandler(this);
|
||||
|
||||
// Close the message queue
|
||||
|
||||
(void)mq_close(m_mqd);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,8 +102,8 @@ void CWindowMessenger::handleMouseEvent(void)
|
||||
// 3. The NX server will determine which window gets the mouse input
|
||||
// and send a window event message to the NX listener thread.
|
||||
// 4. The NX listener thread receives a windows event. The NX listener thread
|
||||
// which is part of CTaskBar and was created when NX server connection was
|
||||
// established). This event may be a positional change notification, a
|
||||
// is part of CTaskBar and was created when NX server connection was
|
||||
// established. This event may be a positional change notification, a
|
||||
// redraw request, or mouse or keyboard input. In this case, mouse input.
|
||||
// 5. The NX listener thread handles the message by calling nx_eventhandler().
|
||||
// nx_eventhandler() dispatches the message by calling a method in the
|
||||
@ -133,20 +115,18 @@ void CWindowMessenger::handleMouseEvent(void)
|
||||
// window event.
|
||||
// 8. NXWidgets::CWindowEventHandlerList will give the event to this method
|
||||
// NxWM::CWindowMessenger.
|
||||
// 9. This NxWM::CWindowMessenger method will send the a message on a well-
|
||||
// known message queue.
|
||||
// 10. This CStartWindow::startWindow task will receive and process that
|
||||
// message by calling CWidgetControl::pollEvents()
|
||||
// 9. This NxWM::CWindowMessenger method will schedule an entry on the work
|
||||
// queue.
|
||||
// 10. The work queue callback will finally call pollEvents() to execute whatever
|
||||
// actions the input event should trigger.
|
||||
|
||||
struct SStartWindowMessage outmsg;
|
||||
outmsg.msgId = MSGID_MOUSE_INPUT;
|
||||
outmsg.instance = (FAR void *)static_cast<CWidgetControl*>(this);
|
||||
work_state_t *state = new work_state_t;
|
||||
state->windowMessenger = this;
|
||||
|
||||
int ret = mq_send(m_mqd, &outmsg, sizeof(struct SStartWindowMessage),
|
||||
CONFIG_NXWM_STARTWINDOW_MXMPRIO);
|
||||
int ret = work_queue(USRWORK, &state->work, &inputWorkCallback, state, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
gdbg("ERROR: mq_send failed: %d\n", errno);
|
||||
gdbg("ERROR: work_queue failed: %d\n", ret);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -158,41 +138,13 @@ void CWindowMessenger::handleMouseEvent(void)
|
||||
#ifdef CONFIG_NX_KBD
|
||||
void CWindowMessenger::handleKeyboardEvent(void)
|
||||
{
|
||||
// The logic path here is tortuous but flexible:
|
||||
//
|
||||
// 1. A listener thread receives keyboard input and injects that into NX
|
||||
// via nx_kbdin.
|
||||
// 2. In the multi-user mode, this will send a message to the NX server
|
||||
// 3. The NX server will determine which window gets the keyboard input
|
||||
// and send a window event message to the NX listener thread.
|
||||
// 4. The NX listener thread receives a windows event. The NX listener thread
|
||||
// which is part of CTaskBar and was created when NX server connection was
|
||||
// established). This event may be a positional change notification, a
|
||||
// redraw request, or mouse or keyboard input. In this case, keyboard input.
|
||||
// 5. The NX listener thread handles the message by calling nx_eventhandler().
|
||||
// nx_eventhandler() dispatches the message by calling a method in the
|
||||
// NXWidgets::CCallback instance associated with the window.
|
||||
// NXWidgets::CCallback is a part of the CWidgetControl.
|
||||
// 6. NXWidgets::CCallback calls into NXWidgets::CWidgetControl to process
|
||||
// the event.
|
||||
// 7. NXWidgets::CWidgetControl records the new state data and raises a
|
||||
// window event.
|
||||
// 8. NXWidgets::CWindowEventHandlerList will give the event to this method
|
||||
// NxWM::CWindowMessenger.
|
||||
// 9. This NxWM::CWindowMessenger method will send the a message on a well-
|
||||
// known message queue.
|
||||
// 10. This CStartWindow::startWindow task will receive and process that
|
||||
// message by calling CWidgetControl::pollEvents()
|
||||
work_state_t *state = new work_state_t;
|
||||
state->windowMessenger = this;
|
||||
|
||||
struct SStartWindowMessage outmsg;
|
||||
outmsg.msgId = MSGID_KEYBOARD_INPUT;
|
||||
outmsg.instance = (FAR void *)static_cast<CWidgetControl*>(this);
|
||||
|
||||
int ret = mq_send(m_mqd, &outmsg, sizeof(struct SStartWindowMessage),
|
||||
CONFIG_NXWM_STARTWINDOW_MXMPRIO);
|
||||
int ret = work_queue(USRWORK, &state->work, &inputWorkCallback, state, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
gdbg("ERROR: mq_send failed: %d\n", errno);
|
||||
gdbg("ERROR: work_queue failed: %d\n", ret);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -213,18 +165,40 @@ void CWindowMessenger::handleKeyboardEvent(void)
|
||||
|
||||
void CWindowMessenger::handleBlockedEvent(FAR void *arg)
|
||||
{
|
||||
// Send a message to destroy the window isntance at a later time
|
||||
// Send a message to destroy the window instance.
|
||||
|
||||
struct SStartWindowMessage outmsg;
|
||||
outmsg.msgId = MSGID_DESTROY_APP;
|
||||
outmsg.instance = arg;
|
||||
work_state_t *state = new work_state_t;
|
||||
state->windowMessenger = this;
|
||||
state->instance = arg;
|
||||
|
||||
gdbg("Sending MSGID_DESTROY_APP with instance=%p\n", arg);
|
||||
int ret = mq_send(m_mqd, &outmsg, sizeof(struct SStartWindowMessage),
|
||||
CONFIG_NXWM_STARTWINDOW_MXMPRIO);
|
||||
int ret = work_queue(USRWORK, &state->work, &destroyWorkCallback, state, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
gdbg("ERROR: mq_send failed: %d\n", errno);
|
||||
gdbg("ERROR: work_queue failed: %d\n", ret);
|
||||
}
|
||||
}
|
||||
|
||||
/** Work queue callback functions */
|
||||
|
||||
void CWindowMessenger::inputWorkCallback(FAR void *arg)
|
||||
{
|
||||
work_state_t *state = (work_state_t*)arg;
|
||||
state->windowMessenger->pollEvents();
|
||||
delete state;
|
||||
}
|
||||
|
||||
void CWindowMessenger::destroyWorkCallback(FAR void *arg)
|
||||
{
|
||||
work_state_t *state = (work_state_t*)arg;
|
||||
|
||||
// First make sure any pending input events have been handled.
|
||||
|
||||
state->windowMessenger->pollEvents();
|
||||
|
||||
// Then release the memory.
|
||||
|
||||
gdbg("Deleting app=%p\n", state->instance);
|
||||
IApplication *app = (IApplication *)state->instance;
|
||||
delete app;
|
||||
delete state;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user