Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-cw
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-cw
Commits
8c518808
Commit
8c518808
authored
Jul 08, 2005
by
Alexandre Julliard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Put in place the basic infrastructure to allow supporting multiple
desktop windows.
parent
87458a5c
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
115 additions
and
83 deletions
+115
-83
user_main.c
dlls/user/user_main.c
+0
-3
user_private.h
dlls/user/user_private.h
+2
-1
win.c
dlls/user/win.c
+14
-36
winstation.c
dlls/user/winstation.c
+2
-0
win.h
include/win.h
+0
-1
server_protocol.h
include/wine/server_protocol.h
+16
-1
protocol.def
server/protocol.def
+7
-0
request.h
server/request.h
+2
-0
trace.c
server/trace.c
+12
-0
user.h
server/user.h
+2
-0
window.c
server/window.c
+58
-41
No files found.
dlls/user/user_main.c
View file @
8c518808
...
...
@@ -279,9 +279,6 @@ static BOOL process_attach(void)
/* Initialize message spying */
if
(
!
SPY_Init
())
return
FALSE
;
/* Create desktop window */
if
(
!
WIN_CreateDesktopWindow
())
return
FALSE
;
return
TRUE
;
}
...
...
dlls/user/user_private.h
View file @
8c518808
...
...
@@ -177,7 +177,8 @@ struct user_thread_info
HCURSOR
cursor
;
/* 20 Current cursor */
INT
cursor_count
;
/* 24 Cursor show count */
UINT
active_hooks
;
/* 28 Bitmap of active hooks */
/* 2c-7c Available for more data */
HWND
desktop
;
/* 2c Desktop window */
/* 30-7c Available for more data */
};
static
inline
struct
user_thread_info
*
get_user_thread_info
(
void
)
...
...
dlls/user/win.c
View file @
8c518808
...
...
@@ -47,9 +47,6 @@ WINE_DECLARE_DEBUG_CHANNEL(msg);
/**********************************************************************/
/* Desktop window */
static
HWND
hwndDesktop
;
static
WORD
wDragWidth
=
4
;
static
WORD
wDragHeight
=
3
;
...
...
@@ -313,7 +310,7 @@ WND *WIN_GetPtr( HWND hwnd )
return
ptr
;
ptr
=
NULL
;
}
else
if
(
index
==
USER_HANDLE_TO_INDEX
(
hwndDesktop
))
else
if
(
index
==
USER_HANDLE_TO_INDEX
(
GetDesktopWindow
()
))
{
if
(
hwnd
==
GetDesktopWindow
()
||
!
HIWORD
(
hwnd
)
||
HIWORD
(
hwnd
)
==
0xffff
)
ptr
=
WND_DESKTOP
;
else
ptr
=
NULL
;
...
...
@@ -606,34 +603,6 @@ void WIN_DestroyThreadWindows( HWND hwnd )
HeapFree
(
GetProcessHeap
(),
0
,
list
);
}
/***********************************************************************
* WIN_CreateDesktopWindow
*
* Create the desktop window.
*/
BOOL
WIN_CreateDesktopWindow
(
void
)
{
TRACE
(
"Creating desktop window
\n
"
);
SERVER_START_REQ
(
create_window
)
{
req
->
parent
=
0
;
req
->
owner
=
0
;
req
->
atom
=
LOWORD
(
DESKTOP_CLASS_ATOM
);
req
->
instance
=
0
;
if
(
!
wine_server_call_err
(
req
))
hwndDesktop
=
reply
->
handle
;
}
SERVER_END_REQ
;
if
(
!
hwndDesktop
)
{
ERR
(
"error %ld creating desktop window
\n
"
,
GetLastError
()
);
return
FALSE
;
}
return
USER_Driver
.
pCreateDesktopWindow
(
hwndDesktop
);
}
/***********************************************************************
* WIN_FixCoordinates
...
...
@@ -1571,10 +1540,19 @@ HWND WINAPI FindWindowW( LPCWSTR className, LPCWSTR title )
*/
HWND
WINAPI
GetDesktopWindow
(
void
)
{
if
(
hwndDesktop
)
return
hwndDesktop
;
ERR
(
"Wine init error: either you're trying to use an invalid native USER.EXE config, or some graphics/GUI libraries or DLLs didn't initialize properly. Aborting.
\n
"
);
ExitProcess
(
1
);
return
0
;
struct
user_thread_info
*
thread_info
=
get_user_thread_info
();
if
(
!
thread_info
->
desktop
)
{
SERVER_START_REQ
(
get_desktop_window
)
{
if
(
!
wine_server_call
(
req
))
thread_info
->
desktop
=
reply
->
handle
;
}
SERVER_END_REQ
;
if
(
!
thread_info
->
desktop
||
!
USER_Driver
.
pCreateDesktopWindow
(
thread_info
->
desktop
))
ERR
(
"failed to create desktop window
\n
"
);
}
return
thread_info
->
desktop
;
}
...
...
dlls/user/winstation.c
View file @
8c518808
...
...
@@ -28,6 +28,7 @@
#include "wine/server.h"
#include "wine/unicode.h"
#include "wine/debug.h"
#include "user_private.h"
WINE_DEFAULT_DEBUG_CHANNEL
(
winstation
);
...
...
@@ -359,6 +360,7 @@ BOOL WINAPI SetThreadDesktop( HDESK handle )
ret
=
!
wine_server_call_err
(
req
);
}
SERVER_END_REQ
;
if
(
ret
)
get_user_thread_info
()
->
desktop
=
0
;
/* reset the desktop window */
return
ret
;
}
...
...
include/win.h
View file @
8c518808
...
...
@@ -80,7 +80,6 @@ extern ULONG WIN_SetStyle( HWND hwnd, ULONG set_bits, ULONG clear_bits );
extern
BOOL
WIN_GetRectangles
(
HWND
hwnd
,
RECT
*
rectWindow
,
RECT
*
rectClient
);
extern
LRESULT
WIN_DestroyWindow
(
HWND
hwnd
);
extern
void
WIN_DestroyThreadWindows
(
HWND
hwnd
);
extern
BOOL
WIN_CreateDesktopWindow
(
void
);
extern
BOOL
WIN_IsWindowDrawable
(
HWND
hwnd
,
BOOL
);
extern
HWND
*
WIN_ListChildren
(
HWND
hwnd
);
extern
void
MDI_CalcDefaultChildPos
(
HWND
hwndClient
,
INT
total
,
LPPOINT
lpPos
,
INT
delta
,
UINT
*
id
);
...
...
include/wine/server_protocol.h
View file @
8c518808
...
...
@@ -2503,6 +2503,18 @@ struct destroy_window_reply
struct
get_desktop_window_request
{
struct
request_header
__header
;
};
struct
get_desktop_window_reply
{
struct
reply_header
__header
;
user_handle_t
handle
;
};
struct
set_window_owner_request
{
struct
request_header
__header
;
...
...
@@ -3705,6 +3717,7 @@ enum request
REQ_get_named_pipe_info
,
REQ_create_window
,
REQ_destroy_window
,
REQ_get_desktop_window
,
REQ_set_window_owner
,
REQ_get_window_info
,
REQ_set_window_info
,
...
...
@@ -3916,6 +3929,7 @@ union generic_request
struct
get_named_pipe_info_request
get_named_pipe_info_request
;
struct
create_window_request
create_window_request
;
struct
destroy_window_request
destroy_window_request
;
struct
get_desktop_window_request
get_desktop_window_request
;
struct
set_window_owner_request
set_window_owner_request
;
struct
get_window_info_request
get_window_info_request
;
struct
set_window_info_request
set_window_info_request
;
...
...
@@ -4125,6 +4139,7 @@ union generic_reply
struct
get_named_pipe_info_reply
get_named_pipe_info_reply
;
struct
create_window_reply
create_window_reply
;
struct
destroy_window_reply
destroy_window_reply
;
struct
get_desktop_window_reply
get_desktop_window_reply
;
struct
set_window_owner_reply
set_window_owner_reply
;
struct
get_window_info_reply
get_window_info_reply
;
struct
set_window_info_reply
set_window_info_reply
;
...
...
@@ -4191,6 +4206,6 @@ union generic_reply
struct
set_mailslot_info_reply
set_mailslot_info_reply
;
};
#define SERVER_PROTOCOL_VERSION 18
0
#define SERVER_PROTOCOL_VERSION 18
1
#endif
/* __WINE_WINE_SERVER_PROTOCOL_H */
server/protocol.def
View file @
8c518808
...
...
@@ -1769,6 +1769,13 @@ enum message_type
@END
/* Retrieve the desktop window for the current thread */
@REQ(get_desktop_window)
@REPLY
user_handle_t handle; /* handle to the desktop window */
@END
/* Set a window owner */
@REQ(set_window_owner)
user_handle_t handle; /* handle to the window */
...
...
server/request.h
View file @
8c518808
...
...
@@ -243,6 +243,7 @@ DECL_HANDLER(disconnect_named_pipe);
DECL_HANDLER
(
get_named_pipe_info
);
DECL_HANDLER
(
create_window
);
DECL_HANDLER
(
destroy_window
);
DECL_HANDLER
(
get_desktop_window
);
DECL_HANDLER
(
set_window_owner
);
DECL_HANDLER
(
get_window_info
);
DECL_HANDLER
(
set_window_info
);
...
...
@@ -453,6 +454,7 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] =
(
req_handler
)
req_get_named_pipe_info
,
(
req_handler
)
req_create_window
,
(
req_handler
)
req_destroy_window
,
(
req_handler
)
req_get_desktop_window
,
(
req_handler
)
req_set_window_owner
,
(
req_handler
)
req_get_window_info
,
(
req_handler
)
req_set_window_info
,
...
...
server/trace.c
View file @
8c518808
...
...
@@ -2226,6 +2226,15 @@ static void dump_destroy_window_request( const struct destroy_window_request *re
fprintf
(
stderr
,
" handle=%p"
,
req
->
handle
);
}
static
void
dump_get_desktop_window_request
(
const
struct
get_desktop_window_request
*
req
)
{
}
static
void
dump_get_desktop_window_reply
(
const
struct
get_desktop_window_reply
*
req
)
{
fprintf
(
stderr
,
" handle=%p"
,
req
->
handle
);
}
static
void
dump_set_window_owner_request
(
const
struct
set_window_owner_request
*
req
)
{
fprintf
(
stderr
,
" handle=%p,"
,
req
->
handle
);
...
...
@@ -3206,6 +3215,7 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] = {
(
dump_func
)
dump_get_named_pipe_info_request
,
(
dump_func
)
dump_create_window_request
,
(
dump_func
)
dump_destroy_window_request
,
(
dump_func
)
dump_get_desktop_window_request
,
(
dump_func
)
dump_set_window_owner_request
,
(
dump_func
)
dump_get_window_info_request
,
(
dump_func
)
dump_set_window_info_request
,
...
...
@@ -3413,6 +3423,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = {
(
dump_func
)
dump_get_named_pipe_info_reply
,
(
dump_func
)
dump_create_window_reply
,
(
dump_func
)
0
,
(
dump_func
)
dump_get_desktop_window_reply
,
(
dump_func
)
dump_set_window_owner_reply
,
(
dump_func
)
dump_get_window_info_reply
,
(
dump_func
)
dump_set_window_info_reply
,
...
...
@@ -3620,6 +3631,7 @@ static const char * const req_names[REQ_NB_REQUESTS] = {
"get_named_pipe_info"
,
"create_window"
,
"destroy_window"
,
"get_desktop_window"
,
"set_window_owner"
,
"get_window_info"
,
"set_window_info"
,
...
...
server/user.h
View file @
8c518808
...
...
@@ -38,6 +38,8 @@ enum user_object
USER_HOOK
};
#define DESKTOP_ATOM ((atom_t)32769)
/* user handles functions */
extern
user_handle_t
alloc_user_handle
(
void
*
ptr
,
enum
user_object
type
);
...
...
server/window.c
View file @
8c518808
...
...
@@ -96,8 +96,6 @@ struct user_handle_array
int
total
;
};
static
struct
window
*
top_window
;
/* top-level (desktop) window */
/* global window pointers */
static
struct
window
*
shell_window
;
static
struct
window
*
shell_listview
;
...
...
@@ -175,6 +173,12 @@ static inline struct window *get_last_child( struct window *win )
return
ptr
?
LIST_ENTRY
(
ptr
,
struct
window
,
entry
)
:
NULL
;
}
/* check if window is the desktop */
static
inline
int
is_desktop_window
(
const
struct
window
*
win
)
{
return
!
win
->
parent
;
/* only desktop windows have no parent */
}
/* append a user handle to a handle array */
static
int
add_handle_to_array
(
struct
user_handle_array
*
array
,
user_handle_t
handle
)
{
...
...
@@ -293,8 +297,6 @@ inline static void destroy_properties( struct window *win )
/* destroy a window */
static
void
destroy_window
(
struct
window
*
win
)
{
assert
(
win
!=
top_window
);
/* destroy all children */
while
(
!
list_empty
(
&
win
->
children
))
destroy_window
(
LIST_ENTRY
(
list_head
(
&
win
->
children
),
struct
window
,
entry
));
...
...
@@ -403,6 +405,21 @@ void destroy_thread_windows( struct thread *thread )
}
}
/* get the desktop window */
static
struct
window
*
get_desktop_window
(
int
create
)
{
static
struct
window
*
top_window
;
/* FIXME: should be part of the desktop object */
if
(
!
top_window
&&
create
)
{
if
(
!
(
top_window
=
create_window
(
NULL
,
NULL
,
DESKTOP_ATOM
,
0
)))
return
NULL
;
current
->
desktop_users
--
;
top_window
->
thread
=
NULL
;
/* no thread owns the desktop */
top_window
->
style
=
WS_POPUP
|
WS_VISIBLE
|
WS_CLIPSIBLINGS
|
WS_CLIPCHILDREN
;
}
return
top_window
;
}
/* check whether child is a descendant of parent */
int
is_child_window
(
user_handle_t
parent
,
user_handle_t
child
)
{
...
...
@@ -422,7 +439,7 @@ int is_child_window( user_handle_t parent, user_handle_t child )
int
is_top_level_window
(
user_handle_t
window
)
{
struct
window
*
win
=
get_user_object
(
window
,
USER_WINDOW
);
return
(
win
&&
win
->
parent
==
top_window
);
return
(
win
&&
win
->
parent
&&
is_desktop_window
(
win
->
parent
)
);
}
/* make a window active if possible */
...
...
@@ -447,7 +464,7 @@ static inline void inc_window_paint_count( struct window *win, int incr )
/* check if window and all its ancestors are visible */
static
int
is_visible
(
const
struct
window
*
win
)
{
while
(
win
&&
win
!=
top_window
)
while
(
win
&&
win
->
parent
)
{
if
(
!
(
win
->
style
&
WS_VISIBLE
))
return
0
;
win
=
win
->
parent
;
...
...
@@ -533,9 +550,9 @@ static int get_window_children_from_point( struct window *parent, int x, int y,
/* find window containing point (in absolute coords) */
user_handle_t
window_from_point
(
int
x
,
int
y
)
{
struct
window
*
ret
;
struct
window
*
ret
,
*
top_window
;
if
(
!
top_window
)
return
0
;
if
(
!
(
top_window
=
get_desktop_window
(
0
))
)
return
0
;
ret
=
child_window_from_point
(
top_window
,
x
,
y
);
return
ret
->
handle
;
}
...
...
@@ -546,7 +563,7 @@ static int all_windows_from_point( struct window *top, int x, int y, struct user
struct
window
*
ptr
;
/* make point relative to top window */
for
(
ptr
=
top
->
parent
;
ptr
&&
ptr
!=
top_window
;
ptr
=
ptr
->
parent
)
for
(
ptr
=
top
->
parent
;
ptr
;
ptr
=
ptr
->
parent
)
{
x
-=
ptr
->
client_rect
.
left
;
y
-=
ptr
->
client_rect
.
top
;
...
...
@@ -618,8 +635,11 @@ static struct window *find_child_to_repaint( struct window *parent, struct threa
/* find a window that needs to receive a WM_PAINT; also clear its internal paint flag */
user_handle_t
find_window_to_repaint
(
user_handle_t
parent
,
struct
thread
*
thread
)
{
struct
window
*
ptr
,
*
win
=
find_child_to_repaint
(
top_window
,
thread
);
struct
window
*
ptr
,
*
win
,
*
top_window
=
get_desktop_window
(
0
);
if
(
!
top_window
)
return
0
;
win
=
find_child_to_repaint
(
top_window
,
thread
);
if
(
win
&&
parent
)
{
/* check that it is a child of the specified parent */
...
...
@@ -718,7 +738,7 @@ static void set_region_client_rect( struct region *region, struct window *win )
/* get the top-level window to clip against for a given window */
static
inline
struct
window
*
get_top_clipping_window
(
struct
window
*
win
)
{
while
(
win
->
parent
&&
win
->
parent
!=
top_window
)
win
=
win
->
parent
;
while
(
win
->
parent
&&
!
is_desktop_window
(
win
->
parent
)
)
win
=
win
->
parent
;
return
win
;
}
...
...
@@ -916,7 +936,7 @@ static void validate_parents( struct window *child )
if
(
!
child
->
update_region
)
return
;
while
(
win
->
parent
&&
win
->
parent
!=
top_window
)
while
(
win
->
parent
)
{
/* map to parent client coords */
offset_x
+=
win
->
window_rect
.
left
;
...
...
@@ -1095,7 +1115,7 @@ static unsigned int get_window_update_flags( struct window *win, struct window *
/* if some parent is not visible start from the next sibling */
if
(
!
is_visible
(
win
))
return
0
;
for
(
ptr
=
from_child
;
ptr
&&
ptr
!=
top_window
;
ptr
=
ptr
->
parent
)
for
(
ptr
=
from_child
;
ptr
;
ptr
=
ptr
->
parent
)
{
if
(
!
(
ptr
->
style
&
WS_VISIBLE
)
||
(
ptr
->
style
&
WS_MINIMIZE
))
from_sibling
=
ptr
;
if
(
ptr
==
win
)
break
;
...
...
@@ -1106,15 +1126,14 @@ static unsigned int get_window_update_flags( struct window *win, struct window *
if
((
flags
&
UPDATE_NONCLIENT
)
&&
!
(
flags
&
(
UPDATE_PAINT
|
UPDATE_INTERNALPAINT
)))
{
for
(
ptr
=
win
->
parent
;
ptr
&&
ptr
!=
top_window
;
ptr
=
ptr
->
parent
)
for
(
ptr
=
win
->
parent
;
ptr
;
ptr
=
ptr
->
parent
)
{
if
(
!
(
ptr
->
style
&
WS_CLIPCHILDREN
)
&&
win_needs_repaint
(
ptr
))
return
0
;
}
if
(
from_child
&&
!
(
flags
&
UPDATE_ALLCHILDREN
))
{
for
(
ptr
=
from_sibling
?
from_sibling
:
from_child
;
ptr
&&
ptr
!=
top_window
;
ptr
=
ptr
->
parent
)
for
(
ptr
=
from_sibling
?
from_sibling
:
from_child
;
ptr
;
ptr
=
ptr
->
parent
)
{
if
(
!
(
ptr
->
style
&
WS_CLIPCHILDREN
)
&&
win_needs_repaint
(
ptr
))
from_sibling
=
ptr
;
if
(
ptr
==
win
)
break
;
...
...
@@ -1300,35 +1319,24 @@ done:
/* create a window */
DECL_HANDLER
(
create_window
)
{
struct
window
*
win
;
struct
window
*
win
,
*
parent
,
*
owner
=
NULL
;
reply
->
handle
=
0
;
if
(
!
req
->
parent
)
/* return desktop window */
{
if
(
!
top_window
)
{
if
(
!
(
top_window
=
create_window
(
NULL
,
NULL
,
req
->
atom
,
req
->
instance
)))
return
;
current
->
desktop_users
--
;
top_window
->
thread
=
NULL
;
/* no thread owns the desktop */
top_window
->
style
=
WS_POPUP
|
WS_VISIBLE
|
WS_CLIPSIBLINGS
|
WS_CLIPCHILDREN
;
}
win
=
top_window
;
}
else
{
struct
window
*
parent
,
*
owner
=
NULL
;
if
(
!
(
parent
=
get_window
(
req
->
parent
)))
return
;
if
(
req
->
owner
&&
!
(
owner
=
get_window
(
req
->
owner
)))
return
;
if
(
owner
==
top_window
)
owner
=
NULL
;
else
if
(
owner
&&
parent
!=
top_window
)
if
(
!
(
parent
=
get_window
(
req
->
parent
)))
return
;
if
(
req
->
owner
)
{
if
(
!
(
owner
=
get_window
(
req
->
owner
)))
return
;
if
(
is_desktop_window
(
owner
))
owner
=
NULL
;
else
if
(
!
is_desktop_window
(
parent
))
{
/* an owned window must be created as top-level */
set_error
(
STATUS_ACCESS_DENIED
);
return
;
}
if
(
!
(
win
=
create_window
(
parent
,
owner
,
req
->
atom
,
req
->
instance
)))
return
;
}
if
(
!
(
win
=
create_window
(
parent
,
owner
,
req
->
atom
,
req
->
instance
)))
return
;
reply
->
handle
=
win
->
handle
;
reply
->
extra
=
win
->
nb_extra_bytes
;
reply
->
class_ptr
=
get_class_client_ptr
(
win
->
class
);
...
...
@@ -1343,7 +1351,7 @@ DECL_HANDLER(set_parent)
if
(
!
(
win
=
get_window
(
req
->
handle
)))
return
;
if
(
req
->
parent
&&
!
(
parent
=
get_window
(
req
->
parent
)))
return
;
if
(
win
==
top_window
)
if
(
is_desktop_window
(
win
)
)
{
set_error
(
STATUS_INVALID_PARAMETER
);
return
;
...
...
@@ -1360,12 +1368,21 @@ DECL_HANDLER(destroy_window)
struct
window
*
win
=
get_window
(
req
->
handle
);
if
(
win
)
{
if
(
win
!=
top_window
)
destroy_window
(
win
);
if
(
!
is_desktop_window
(
win
)
)
destroy_window
(
win
);
else
set_error
(
STATUS_ACCESS_DENIED
);
}
}
/* retrieve the desktop window for the current thread */
DECL_HANDLER
(
get_desktop_window
)
{
struct
window
*
win
=
get_desktop_window
(
1
);
if
(
win
)
reply
->
handle
=
win
->
handle
;
}
/* set a window owner */
DECL_HANDLER
(
set_window_owner
)
{
...
...
@@ -1374,7 +1391,7 @@ DECL_HANDLER(set_window_owner)
if
(
!
win
)
return
;
if
(
req
->
owner
&&
!
(
owner
=
get_window
(
req
->
owner
)))
return
;
if
(
win
==
top_window
)
if
(
is_desktop_window
(
win
)
)
{
set_error
(
STATUS_ACCESS_DENIED
);
return
;
...
...
@@ -1413,7 +1430,7 @@ DECL_HANDLER(set_window_info)
struct
window
*
win
=
get_window
(
req
->
handle
);
if
(
!
win
)
return
;
if
(
req
->
flags
&&
win
==
top_window
)
if
(
req
->
flags
&&
is_desktop_window
(
win
)
)
{
set_error
(
STATUS_ACCESS_DENIED
);
return
;
...
...
@@ -1805,7 +1822,7 @@ DECL_HANDLER(get_update_region)
{
win
->
paint_flags
&=
~
PAINT_ERASE
;
/* desktop window only gets erased, not repainted */
if
(
win
==
top_window
)
validate_whole_window
(
win
);
if
(
is_desktop_window
(
win
)
)
validate_whole_window
(
win
);
}
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment