Commit 5e3030ee authored by Maciej Suminski's avatar Maciej Suminski

Merge kicad-pns-mac

parents 936e0be0 7837dfa3
......@@ -156,6 +156,7 @@ set(COMMON_SRCS
view/view_item.cpp
view/view_group.cpp
math/math_util.cpp
system/fcontext.s
tool/tool_base.cpp
......@@ -169,6 +170,7 @@ set(COMMON_SRCS
geometry/seg.cpp
geometry/shape_line_chain.cpp
geometry/shape_collisions.cpp
geometry/shape_index.cpp
)
add_library(common STATIC ${COMMON_SRCS})
......
......@@ -29,7 +29,7 @@
#include <geometry/shape_circle.h>
#include <geometry/shape_rect.h>
typedef typename VECTOR2I::extended_type ecoord;
typedef VECTOR2I::extended_type ecoord;
static inline bool Collide( const SHAPE_CIRCLE& a, const SHAPE_CIRCLE& b, int clearance, bool needMTV, VECTOR2I& aMTV )
{
......
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013 CERN
* @author Jacobo Aragunde Pérez
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <geometry/shape_index.h>
template<>
const SHAPE* shapeFunctor( SHAPE* aItem )
{
return aItem;
}
/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (c) 2005 Michael Niedermayer <michaelni@gmx.at>
* Copyright (C) CERN
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <math/math_util.h>
// explicit specializations for integer types, taking care of overflow.
template<> int rescale( int numerator, int value, int denominator )
{
return (int) ( (int64_t) numerator * (int64_t) value / (int64_t) denominator );
}
template<> int64_t rescale( int64_t numerator, int64_t value, int64_t denominator )
{
uint64_t r = 0;
int64_t sign = ( ( numerator < 0) ? -1 : 1 ) * ( denominator < 0 ? - 1: 1 ) * (value < 0 ? - 1 : 1);
uint64_t a = abs( numerator );
uint64_t b = abs( value );
uint64_t c = abs( denominator );
r = c / 2;
if( b <= INT_MAX && c <= INT_MAX )
{
if( a <= INT_MAX )
return sign * ( (a * b + r ) / c );
else
return sign * (a / c * b + (a % c * b + r) / c);
} else {
uint64_t a0 = a & 0xFFFFFFFF;
uint64_t a1 = a >> 32;
uint64_t b0 = b & 0xFFFFFFFF;
uint64_t b1 = b >> 32;
uint64_t t1 = a0 * b1 + a1 * b0;
uint64_t t1a = t1 << 32;
int i;
a0 = a0 * b0 + t1a;
a1 = a1 * b1 + (t1 >> 32) + (a0 < t1a);
a0 += r;
a1 += a0 < r;
for( i = 63; i >= 0; i-- )
{
a1 += a1 + ( (a0 >> i) & 1 );
t1 += t1;
if( c <= a1 )
{
a1 -= c;
t1++;
}
}
return t1 * sign;
}
};
......@@ -3,8 +3,23 @@
which may be unpleasant, in particular under Windows (we don't support VC++, while boost::context
does not support mingw */
#ifdef __APPLE__
#if __i386__
#if __i386__
#include "jump_i386_sysv_macho_gas.S"
#include "make_i386_sysv_macho_gas.S"
#elif __x86_64__
#include "jump_x86_64_sysv_macho_gas.S"
#include "make_x86_64_sysv_macho_gas.S"
#else
#error "Missing make_fcontext & jump_fcontext routines for this architecture"
#endif
#else
#if __i386__
#ifdef __WIN32__
#include "jump_i386_pe_gas.S"
......@@ -14,8 +29,12 @@
#include "make_i386_sysv_elf_gas.S"
#endif
#elif __x86_64__
#elif __x86_64__
#include "jump_x86_64_sysv_elf_gas.S"
#include "make_x86_64_sysv_elf_gas.S"
#endif
#else
#error "Missing make_fcontext & jump_fcontext routines for this architecture"
#endif
#endif
/*
Copyright Oliver Kowalke 2009.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
/********************************************************************
* *
* -------------------------------------------------------------- *
* | 0 | 1 | 2 | 3 | 4 | 5 | *
* -------------------------------------------------------------- *
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | *
* -------------------------------------------------------------- *
* | EDI | ESI | EBX | EBP | ESP | EIP | *
* -------------------------------------------------------------- *
* -------------------------------------------------------------- *
* | 6 | 7 | | *
* -------------------------------------------------------------- *
* | 0x18 | 0x1c | | *
* -------------------------------------------------------------- *
* | sp | size | | *
* -------------------------------------------------------------- *
* -------------------------------------------------------------- *
* | 8 | 9 | | *
* -------------------------------------------------------------- *
* | 0x20 | 0x24 | | *
* -------------------------------------------------------------- *
* | fc_mxcsr|fc_x87_cw| | *
* -------------------------------------------------------------- *
* *
* *****************************************************************/
.text
.globl _jump_fcontext
.align 2
_jump_fcontext:
movl 0x4(%esp), %ecx /* load address of the first fcontext_t arg */
movl %edi, (%ecx) /* save EDI */
movl %esi, 0x4(%ecx) /* save ESI */
movl %ebx, 0x8(%ecx) /* save EBX */
movl %ebp, 0xc(%ecx) /* save EBP */
leal 0x4(%esp), %eax /* exclude the return address */
movl %eax, 0x10(%ecx) /* save as stack pointer */
movl (%esp), %eax /* load return address */
movl %eax, 0x14(%ecx) /* save return address */
movl 0x8(%esp), %edx /* load address of the second fcontext_t arg */
movl (%edx), %edi /* restore EDI */
movl 0x4(%edx), %esi /* restore ESI */
movl 0x8(%edx), %ebx /* restore EBX */
movl 0xc(%edx), %ebp /* restore EBP */
movl 0x10(%esp), %eax /* check if fpu enve preserving was requested */
test %eax, %eax
je 1f
stmxcsr 0x20(%ecx) /* save MMX control and status word */
fnstcw 0x24(%ecx) /* save x87 control word */
ldmxcsr 0x20(%edx) /* restore MMX control and status word */
fldcw 0x24(%edx) /* restore x87 control word */
1:
movl 0xc(%esp), %eax /* use third arg as return value after jump */
movl 0x10(%edx), %esp /* restore ESP */
movl %eax, 0x4(%esp) /* use third arg as first arg in context function */
movl 0x14(%edx), %edx /* fetch the address to return to */
jmp *%edx /* indirect jump to context */
/*
Copyright Oliver Kowalke 2009.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
/****************************************************************************************
* *
* ---------------------------------------------------------------------------------- *
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
* ---------------------------------------------------------------------------------- *
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | *
* ---------------------------------------------------------------------------------- *
* | RBX | R12 | R13 | R14 | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
* ---------------------------------------------------------------------------------- *
* | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | *
* ---------------------------------------------------------------------------------- *
* | R15 | RBP | RSP | RIP | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 16 | 17 | 18 | 19 | | *
* ---------------------------------------------------------------------------------- *
* | 0x40 | 0x44 | 0x48 | 0x4c | | *
* ---------------------------------------------------------------------------------- *
* | sp | size | | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 20 | 21 | | *
* ---------------------------------------------------------------------------------- *
* | 0x50 | 0x54 | | *
* ---------------------------------------------------------------------------------- *
* | fc_mxcsr|fc_x87_cw| | *
* ---------------------------------------------------------------------------------- *
* *
* **************************************************************************************/
.text
.globl _jump_fcontext
.align 8
_jump_fcontext:
movq %rbx, (%rdi) /* save RBX */
movq %r12, 0x8(%rdi) /* save R12 */
movq %r13, 0x10(%rdi) /* save R13 */
movq %r14, 0x18(%rdi) /* save R14 */
movq %r15, 0x20(%rdi) /* save R15 */
movq %rbp, 0x28(%rdi) /* save RBP */
cmp $0, %rcx
je 1f
stmxcsr 0x50(%rdi) /* save MMX control and status word */
fnstcw 0x54(%rdi) /* save x87 control word */
ldmxcsr 0x50(%rsi) /* restore MMX control and status word */
fldcw 0x54(%rsi) /* restore x87 control word */
1:
leaq 0x8(%rsp), %rax /* exclude the return address and save as stack pointer */
movq %rax, 0x30(%rdi) /* save as stack pointer */
movq (%rsp), %rax /* save return address */
movq %rax, 0x38(%rdi) /* save return address as RIP */
movq (%rsi), %rbx /* restore RBX */
movq 0x8(%rsi), %r12 /* restore R12 */
movq 0x10(%rsi), %r13 /* restore R13 */
movq 0x18(%rsi), %r14 /* restore R14 */
movq 0x20(%rsi), %r15 /* restore R15 */
movq 0x28(%rsi), %rbp /* restore RBP */
movq 0x30(%rsi), %rsp /* restore RSP */
movq 0x38(%rsi), %rcx /* fetch the address to return to */
movq %rdx, %rax /* use third arg as return value after jump */
movq %rdx, %rdi /* use third arg as first arg in context function */
jmp *%rcx /* indirect jump to context */
/*
Copyright Oliver Kowalke 2009.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
/********************************************************************
* *
* -------------------------------------------------------------- *
* | 0 | 1 | 2 | 3 | 4 | 5 | *
* -------------------------------------------------------------- *
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | *
* -------------------------------------------------------------- *
* | EDI | ESI | EBX | EBP | ESP | EIP | *
* -------------------------------------------------------------- *
* -------------------------------------------------------------- *
* | 6 | 7 | | *
* -------------------------------------------------------------- *
* | 0x18 | 0x1c | | *
* -------------------------------------------------------------- *
* | sp | size | | *
* -------------------------------------------------------------- *
* -------------------------------------------------------------- *
* | 8 | 9 | | *
* -------------------------------------------------------------- *
* | 0x20 | 0x24 | | *
* -------------------------------------------------------------- *
* | fc_mxcsr|fc_x87_cw| | *
* -------------------------------------------------------------- *
* *
* *****************************************************************/
.text
.globl _make_fcontext
.align 2
_make_fcontext:
movl 0x4(%esp), %eax /* load 1. arg of make_fcontext, pointer to context stack (base) */
leal -0x28(%eax), %eax /* reserve space for fcontext_t at top of context stack */
/* shift address in EAX to lower 16 byte boundary */
/* == pointer to fcontext_t and address of context stack */
andl $-16, %eax
movl 0x4(%esp), %edx /* load 1. arg of make_fcontext, pointer to context stack (base) */
movl %edx, 0x18(%eax) /* save address of stack pointer (base) in fcontext_t */
movl 0x8(%esp), %edx /* load 2. arg of make_fcontext, context stack size */
movl %edx, 0x1c(%eax) /* save stack size in fcontext_t */
movl 0xc(%esp), %edx /* load 3. arg of make_fcontext, pointer to context function */
movl %edx, 0x14(%eax) /* save address of context fcuntion in fcontext_t */
stmxcsr 0x20(%eax) /* save MMX control and status word */
fnstcw 0x24(%eax) /* save x87 control word */
leal -0x14(%eax), %edx /* reserve space for the last frame on context stack */
movl %edx, 0x10(%eax) /* save address in EDX as stack pointer for context function */
call 1f
1: popl %ecx /* address of label 1 */
addl $finish-1b, %ecx /* compute abs address of label finish */
movl %ecx, (%edx) /* save address of finish as return address for context function */
/* entered after context function returns */
ret
finish:
/* ESP points to same address as ESP on entry of context function + 0x4 */
xorl %eax, %eax
movl %eax, (%esp) /* exit code is zero */
call __exit /* exit application */
hlt
/*
Copyright Oliver Kowalke 2009.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
/****************************************************************************************
* *
* ---------------------------------------------------------------------------------- *
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
* ---------------------------------------------------------------------------------- *
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | *
* ---------------------------------------------------------------------------------- *
* | RBX | R12 | R13 | R14 | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
* ---------------------------------------------------------------------------------- *
* | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | *
* ---------------------------------------------------------------------------------- *
* | R15 | RBP | RSP | RIP | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 16 | 17 | 18 | 19 | | *
* ---------------------------------------------------------------------------------- *
* | 0x40 | 0x44 | 0x48 | 0x4c | | *
* ---------------------------------------------------------------------------------- *
* | sp | size | | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 20 | 21 | | *
* ---------------------------------------------------------------------------------- *
* | 0x50 | 0x54 | | *
* ---------------------------------------------------------------------------------- *
* | fc_mxcsr|fc_x87_cw| | *
* ---------------------------------------------------------------------------------- *
* *
* **************************************************************************************/
.text
.globl _make_fcontext
.align 8
_make_fcontext:
leaq -0x58(%rdi), %rax /* reserve space for fcontext_t at top of context stack */
/* shift address in RAX to lower 16 byte boundary */
/* == pointer to fcontext_t and address of context stack */
movabs $-16, %r8
andq %r8, %rax
movq %rdi, 0x40(%rax) /* save address of stack pointer (base) in fcontext_t */
movq %rsi, 0x48(%rax) /* save stack size in fcontext_t */
movq %rdx, 0x38(%rax) /* save address of context function in fcontext_t */
stmxcsr 0x50(%rax) /* save MMX control and status word */
fnstcw 0x54(%rax) /* save x87 control word */
leaq -0x8(%rax), %rdx /* reserve space for the return address on context stack, (RSP - 0x8) % 16 == 0 */
movq %rdx, 0x30(%rax) /* save address in RDX as stack pointer for context function */
leaq finish(%rip), %rcx /* compute abs address of label finish */
movq %rcx, (%rdx) /* save address of finish as return address for context function */
/* entered after context function returns */
ret /* return pointer to fcontext_t placed on context stack */
finish:
/* RSP points to same address as RSP on entry of context function + 0x8 */
xorq %rdi, %rdi /* exit code is zero */
call __exit /* exit application */
......@@ -49,7 +49,7 @@ enum ShapeType {
*/
class SHAPE {
protected:
typedef typename VECTOR2I::extended_type ecoord;
typedef VECTOR2I::extended_type ecoord;
public:
/**
......
......@@ -50,10 +50,7 @@ static const SHAPE* shapeFunctor( T aItem )
* shapeFunctor template function: specialization for T = SHAPE*
*/
template<>
const SHAPE* shapeFunctor( SHAPE* aItem )
{
return aItem;
}
const SHAPE* shapeFunctor( SHAPE* aItem );
/**
* boundingBox template method
......@@ -114,79 +111,6 @@ class SHAPE_INDEX {
public:
SHAPE_INDEX();
~SHAPE_INDEX();
/**
* Function Add()
*
* Adds a SHAPE to the index.
* @param shape the new SHAPE
*/
void Add( T shape );
/**
* Function Remove()
*
* Removes a SHAPE to the index.
* @param shape the new SHAPE
*/
void Remove( T shape );
/**
* Function RemoveAll()
*
* Removes all the contents of the index.
*/
void RemoveAll();
/**
* Function Accept()
*
* Accepts a visitor for every SHAPE object contained in this INDEX.
* @param visitor Visitor object to be run
*/
template<class V>
void Accept( V visitor )
{
SHAPE_INDEX::Iterator iter = this->Begin();
while(!iter.IsNull()) {
T shape = *iter;
acceptVisitor(shape, visitor);
iter++;
}
}
/**
* Function Reindex()
*
* Rebuilds the index. This should be used if the geometry of the objects
* contained by the index has changed.
*/
void Reindex();
/**
* Function Query()
*
* Runs a callback on every SHAPE object contained in the bounding box of (shape).
* @param shape shape to search against
* @param minDistance distance threshold
* @param visitor object to be invoked on every object contained in the search area.
*/
template<class V>
int Query( const SHAPE *shape, int minDistance, V& visitor, bool aExact )
{
BOX2I box = shape->BBox();
box.Inflate(minDistance);
int min[2] = {box.GetX(), box.GetY()};
int max[2] = {box.GetRight(), box.GetBottom()};
return this->m_tree->Search(min, max, visitor);
}
class Iterator
{
private:
......@@ -278,6 +202,79 @@ class SHAPE_INDEX {
}
};
SHAPE_INDEX();
~SHAPE_INDEX();
/**
* Function Add()
*
* Adds a SHAPE to the index.
* @param shape the new SHAPE
*/
void Add( T shape );
/**
* Function Remove()
*
* Removes a SHAPE to the index.
* @param shape the new SHAPE
*/
void Remove( T shape );
/**
* Function RemoveAll()
*
* Removes all the contents of the index.
*/
void RemoveAll();
/**
* Function Accept()
*
* Accepts a visitor for every SHAPE object contained in this INDEX.
* @param visitor Visitor object to be run
*/
template<class V>
void Accept( V visitor )
{
Iterator iter = this->Begin();
while(!iter.IsNull()) {
T shape = *iter;
acceptVisitor(shape, visitor);
iter++;
}
}
/**
* Function Reindex()
*
* Rebuilds the index. This should be used if the geometry of the objects
* contained by the index has changed.
*/
void Reindex();
/**
* Function Query()
*
* Runs a callback on every SHAPE object contained in the bounding box of (shape).
* @param shape shape to search against
* @param minDistance distance threshold
* @param visitor object to be invoked on every object contained in the search area.
*/
template<class V>
int Query( const SHAPE *shape, int minDistance, V& visitor, bool aExact )
{
BOX2I box = shape->BBox();
box.Inflate(minDistance);
int min[2] = {box.GetX(), box.GetY()};
int max[2] = {box.GetRight(), box.GetBottom()};
return this->m_tree->Search(min, max, visitor);
}
/**
* Function Begin()
*
......@@ -331,7 +328,7 @@ void SHAPE_INDEX<T>::Reindex() {
RTree<T, int, 2, float>* newTree;
newTree = new RTree<T, int, 2, float>();
SHAPE_INDEX::Iterator iter = this->Begin();
Iterator iter = this->Begin();
while(!iter.IsNull()) {
T shape = *iter;
BOX2I box = boundingBox(shape);
......
......@@ -29,6 +29,7 @@
#include <cmath>
#include <cstdlib>
#include <stdint.h>
#include <climits>
/**
* Function rescale()
......@@ -36,63 +37,9 @@
* Scales a number (value) by rational (numerator/denominator). Numerator must be <= denominator.
*/
template<typename T> static T rescale( T numerator, T value, T denominator )
template<typename T> T rescale( T numerator, T value, T denominator )
{
return numerator * value / denominator;
}
// explicit specializations for integer types, taking care of overflow.
template<> int rescale( int numerator, int value, int denominator )
{
return (int) ( (int64_t) numerator * (int64_t) value / (int64_t) denominator );
}
template<> int64_t rescale( int64_t numerator, int64_t value, int64_t denominator )
{
int64_t r = 0;
int64_t sign = ( ( numerator < 0) ? -1 : 1 ) * ( denominator < 0 ? - 1: 1 ) * (value < 0 ? - 1 : 1);
int64_t a = std::abs( numerator );
int64_t b = std::abs( value );
int64_t c = std::abs( denominator );
r = c / 2;
if( b <= INT_MAX && c <= INT_MAX )
{
if( a <= INT_MAX )
return sign * ( (a * b + r ) / c );
else
return sign * (a / c * b + (a % c * b + r) / c);
} else {
uint64_t a0 = a & 0xFFFFFFFF;
uint64_t a1 = a >> 32;
uint64_t b0 = b & 0xFFFFFFFF;
uint64_t b1 = b >> 32;
uint64_t t1 = a0 * b1 + a1 * b0;
uint64_t t1a = t1 << 32;
int i;
a0 = a0 * b0 + t1a;
a1 = a1 * b1 + (t1 >> 32) + (a0 < t1a);
a0 += r;
a1 += ((uint64_t)a0) < r;
for( i = 63; i >= 0; i-- )
{
a1 += a1 + ( (a0 >> i) & 1 );
t1 += t1;
if( (uint64_t)c <= a1 )
{
a1 -= c;
t1++;
}
}
return t1 * sign;
}
};
#endif // __MATH_UTIL_H
......@@ -98,7 +98,7 @@ public:
*/
void Yield()
{
jump_fcontext( m_self, m_saved, 0 );
boost::context::jump_fcontext( m_self, m_saved, 0 );
}
/**
......@@ -110,11 +110,11 @@ public:
void Yield( ReturnType& retVal )
{
m_retVal = retVal;
jump_fcontext( m_self, m_saved, 0 );
boost::context::jump_fcontext( m_self, m_saved, 0 );
}
/**
* Function SetEntry()
<F11>* Function SetEntry()
*
* Defines the entry point for the coroutine, if not set in the constructor.
*/
......@@ -156,7 +156,7 @@ public:
*/
bool Resume()
{
jump_fcontext( m_saved, m_self, 0 );
boost::context::jump_fcontext( m_saved, m_self, 0 );
return m_running;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment