Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Tiago Peixoto
graph-tool
Commits
09139678
Commit
09139678
authored
Jun 29, 2014
by
Tiago Peixoto
Browse files
Simplify undirected adaptor so that edge descriptors are unmodified
parent
07f3897c
Changes
2
Hide whitespace changes
Inline
Side-by-side
src/graph/graph_adaptor.hh
View file @
09139678
...
...
@@ -50,12 +50,11 @@ public:
typedef
typename
Graph
::
graph_tag
graph_tag
;
typedef
Graph
graph_type
;
class
EdgeDescriptor
;
typedef
Graph
original_graph_t
;
typedef
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex_descriptor
vertex_descriptor
;
typedef
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex
_descriptor
typedef
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge
_descriptor
edge_descriptor
;
typedef
undirected_tag
directed_category
;
...
...
@@ -92,90 +91,29 @@ struct edge_bundle_type<UndirectedAdaptor<Graph> >:
edge_bundle_type
<
Graph
>
{
};
#endif // BOOST_GRAPH_NO_BUNDLED_PROPERTIES
//==============================================================================
// UndirectedAdaptor::EdgeDescriptor
//==============================================================================
template
<
class
Graph
>
class
UndirectedAdaptor
<
Graph
>::
EdgeDescriptor
:
public
graph_traits
<
Graph
>::
edge_descriptor
struct
get_iterator_category
{
public:
typedef
typename
graph_traits
<
Graph
>::
edge_descriptor
original_edge_t
;
EdgeDescriptor
(){}
EdgeDescriptor
(
const
original_edge_t
&
e
)
:
original_edge_t
(
e
),
_inverted
(
false
)
{}
EdgeDescriptor
(
const
original_edge_t
&
e
,
bool
inverted
)
:
original_edge_t
(
e
),
_inverted
(
inverted
)
{}
bool
IsInverted
()
const
{
return
_inverted
;}
bool
operator
==
(
const
EdgeDescriptor
&
e
)
const
{
return
original_edge_t
(
e
)
==
original_edge_t
(
*
this
);
}
private:
bool
_inverted
;
typedef
typename
graph_traits
<
Graph
>::
out_edge_iterator
iter_t
;
typedef
typename
std
::
iterator_traits
<
iter_t
>::
iterator_category
type
;
};
//==============================================================================
// UndirectedAdaptorEdgeIterator
//==============================================================================
template
<
class
Graph
>
struct
make_undirected_edge
{
make_undirected_edge
(
bool
inverted
)
:
_inverted
(
inverted
)
{}
make_undirected_edge
()
:
_inverted
(
false
)
{}
bool
_inverted
;
typedef
typename
graph_traits
<
Graph
>::
edge_descriptor
original_edge_t
;
typedef
typename
UndirectedAdaptor
<
Graph
>::
EdgeDescriptor
result_type
;
__attribute__
((
always_inline
))
typename
UndirectedAdaptor
<
Graph
>::
EdgeDescriptor
operator
()(
const
original_edge_t
&
e
)
const
{
return
typename
UndirectedAdaptor
<
Graph
>::
EdgeDescriptor
(
e
,
_inverted
);
}
};
template
<
class
Graph
,
class
Iter
>
struct
transformed_iterator
{
typedef
typename
mpl
::
if_
<
is_convertible
<
typename
std
::
iterator_traits
<
Iter
>::
iterator_category
,
std
::
random_access_iterator_tag
>
,
transform_random_access_iterator
<
make_undirected_edge
<
Graph
>
,
Iter
>
,
transform_iterator
<
make_undirected_edge
<
Graph
>
,
Iter
>
>::
type
type
;
typedef
is_convertible
<
typename
std
::
iterator_traits
<
Iter
>::
iterator_category
,
std
::
random_access_iterator_tag
>
is_orig_ra
;
typedef
is_convertible
<
typename
std
::
iterator_traits
<
type
>::
iterator_category
,
std
::
random_access_iterator_tag
>
is_ra
;
BOOST_STATIC_ASSERT
((
!
is_orig_ra
::
value
||
is_ra
::
value
));
};
template
<
class
Graph
>
struct
get_undirected_edge_iterator
{
typedef
typename
graph_traits
<
Graph
>::
edge_iterator
ei_t
;
typedef
typename
transformed_iterator
<
Graph
,
ei_t
>::
type
type
;
};
//==============================================================================
// UndirectedAdaptorOutEdgeIterator
// this will iterate through both in_edges and out_edges of the underlying graph
//==============================================================================
template
<
class
Iter1
,
class
Iter2
>
class
joined_iterator
:
public
boost
::
iterator_facade
<
joined_iterator
<
Iter1
,
Iter2
>
,
typename
std
::
iterator_traits
<
Iter1
>::
value_type
,
typename
std
::
iterator_traits
<
Iter1
>::
iterator_category
,
typename
std
::
iterator_traits
<
Iter1
>::
reference
>
class
joined_edge_iterator
:
public
boost
::
iterator_facade
<
joined_edge_iterator
<
Graph
>
,
typename
graph_traits
<
Graph
>::
edge_descriptor
,
typename
get_iterator_category
<
Graph
>::
type
,
typename
graph_traits
<
Graph
>::
edge_descriptor
>
{
public:
joined_iterator
()
{}
explicit
joined_iterator
(
const
std
::
pair
<
Iter1
,
Iter1
>&
range1
,
const
std
::
pair
<
Iter2
,
Iter2
>&
range2
,
const
Iter1
&
pos1
,
const
Iter2
&
pos2
)
typedef
typename
graph_traits
<
Graph
>::
in_edge_iterator
iter1_t
;
typedef
typename
graph_traits
<
Graph
>::
out_edge_iterator
iter2_t
;
joined_edge_iterator
()
{}
explicit
joined_edge_iterator
(
const
std
::
pair
<
iter1_t
,
iter1_t
>&
range1
,
const
std
::
pair
<
iter2_t
,
iter2_t
>&
range2
,
const
iter1_t
&
pos1
,
const
iter2_t
&
pos2
)
:
_range1
(
range1
),
_range2
(
range2
),
_pos1
(
pos1
),
_pos2
(
pos2
)
{
_flip
=
(
_pos1
==
_range1
.
second
);
}
...
...
@@ -214,7 +152,7 @@ class joined_iterator
}
}
typedef
typename
std
::
iterator_traits
<
I
ter1
>::
difference_type
diff_t
;
typedef
typename
std
::
iterator_traits
<
i
ter1
_t
>::
difference_type
diff_t
;
void
advance
(
diff_t
n
)
{
diff_t
d1
=
_range1
.
second
-
_pos1
;
...
...
@@ -230,40 +168,38 @@ class joined_iterator
}
}
diff_t
distance_to
(
joined_iterator
const
&
other
)
diff_t
distance_to
(
joined_
edge_
iterator
const
&
other
)
{
return
(
other
.
_pos1
-
_pos1
)
+
(
other
.
_pos2
-
_pos2
);
}
bool
equal
(
joined_iterator
const
&
other
)
const
bool
equal
(
joined_
edge_
iterator
const
&
other
)
const
{
return
(
_pos2
==
other
.
_pos2
&&
_pos1
==
other
.
_pos1
);
}
typename
std
::
iterator_traits
<
Iter1
>::
reference
dereference
()
const
typename
graph_traits
<
Graph
>::
edge_descriptor
dereference
()
const
{
typename
graph_traits
<
Graph
>::
edge_descriptor
e
;
if
(
_flip
)
return
*
_pos2
;
return
*
_pos1
;
{
e
=
*
_pos2
;
}
else
{
e
=
*
_pos1
;
get
<
3
>
(
e
)
=
true
;
}
return
e
;
}
std
::
pair
<
I
ter1
,
I
ter1
>
_range1
;
std
::
pair
<
I
ter2
,
I
ter2
>
_range2
;
I
ter1
_pos1
;
I
ter2
_pos2
;
std
::
pair
<
i
ter1
_t
,
i
ter1
_t
>
_range1
;
std
::
pair
<
i
ter2
_t
,
i
ter2
_t
>
_range2
;
i
ter1
_t
_pos1
;
i
ter2
_t
_pos2
;
bool
_flip
;
};
template
<
class
Graph
>
struct
get_undirected_out_edge_iterator
{
typedef
typename
graph_traits
<
Graph
>::
out_edge_iterator
eo_t
;
typedef
typename
graph_traits
<
Graph
>::
in_edge_iterator
ei_t
;
typedef
typename
transformed_iterator
<
Graph
,
eo_t
>::
type
teo_t
;
typedef
typename
transformed_iterator
<
Graph
,
ei_t
>::
type
tei_t
;
typedef
joined_iterator
<
teo_t
,
tei_t
>
type
;
};
//==============================================================================
...
...
@@ -275,7 +211,7 @@ struct get_undirected_out_edge_iterator
template
<
class
Graph
>
struct
get_undirected_adjacency_iterator
{
typedef
typename
get_undirected_out
_edge_iterator
<
Graph
>
::
type
out_edge_iter_t
;
typedef
joined
_edge_iterator
<
Graph
>
out_edge_iter_t
;
typedef
typename
boost
::
adjacency_iterator_generator
<
UndirectedAdaptor
<
Graph
>
,
typename
graph_traits
<
Graph
>::
vertex_descriptor
,
out_edge_iter_t
>::
type
type
;
...
...
@@ -289,13 +225,13 @@ struct get_undirected_adjacency_iterator
template
<
class
Graph
>
struct
graph_traits
<
UndirectedAdaptor
<
Graph
>
>
{
typedef
typename
graph_traits
<
Graph
>::
vertex_descriptor
vertex_descriptor
;
typedef
typename
UndirectedAdaptor
<
Graph
>::
E
dge
D
escriptor
edge_descriptor
;
typedef
typename
graph_traits
<
Graph
>::
e
dge
_d
escriptor
edge_descriptor
;
typedef
typename
get_undirected_adjacency_iterator
<
Graph
>::
type
adjacency_iterator
;
typedef
typename
get_undirected_out
_edge_iterator
<
Graph
>
::
type
out_edge_iterator
;
typedef
joined
_edge_iterator
<
Graph
>
out_edge_iterator
;
typedef
typename
graph_traits
<
Graph
>::
in_edge_iterator
in_edge_iterator
;
typedef
typename
graph_traits
<
Graph
>::
vertex_iterator
vertex_iterator
;
typedef
typename
g
et_undirected_edge_iterator
<
Graph
>::
type
edge_iterator
;
typedef
typename
g
raph_traits
<
Graph
>::
edge_iterator
edge_iterator
;
typedef
undirected_tag
directed_category
;
...
...
@@ -316,9 +252,6 @@ private:
typedef
is_convertible
<
typename
std
::
iterator_traits
<
out_edge_iterator
>::
iterator_category
,
std
::
random_access_iterator_tag
>
is_ra
;
BOOST_STATIC_ASSERT
((
!
is_orig_ra
::
value
||
is_ra
::
value
));
// typedef is_convertible<typename std::iterator_traits<adjacency_iterator>::iterator_category,
// std::random_access_iterator_tag> is_ara;
// BOOST_STATIC_ASSERT((!is_orig_ra::value || is_ara::value));
};
template
<
class
Graph
>
...
...
@@ -335,14 +268,13 @@ struct graph_traits< const UndirectedAdaptor<Graph> >:
//==============================================================================
template
<
class
Graph
>
inline
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex_descriptor
source
(
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge_descriptor
e
,
source
(
const
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge_descriptor
&
e
,
const
UndirectedAdaptor
<
Graph
>&
g
)
{
typedef
typename
graph_traits
<
Graph
>::
edge_descriptor
original_edge_t
;
if
(
e
.
IsInverted
())
return
target
(
original_edge_t
(
e
),
g
.
OriginalGraph
());
if
(
get
<
3
>
(
e
))
return
target
(
e
,
g
.
OriginalGraph
());
else
return
source
(
original_edge_t
(
e
)
,
g
.
OriginalGraph
());
return
source
(
e
,
g
.
OriginalGraph
());
}
//==============================================================================
...
...
@@ -350,14 +282,13 @@ source(typename graph_traits<UndirectedAdaptor<Graph> >::edge_descriptor e,
//==============================================================================
template
<
class
Graph
>
inline
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex_descriptor
target
(
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge_descriptor
e
,
target
(
const
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge_descriptor
&
e
,
const
UndirectedAdaptor
<
Graph
>&
g
)
{
typedef
typename
graph_traits
<
Graph
>::
edge_descriptor
original_edge_t
;
if
(
e
.
IsInverted
())
return
source
(
original_edge_t
(
e
),
g
.
OriginalGraph
());
if
(
get
<
3
>
(
e
))
return
source
(
e
,
g
.
OriginalGraph
());
else
return
target
(
original_edge_t
(
e
)
,
g
.
OriginalGraph
());
return
target
(
e
,
g
.
OriginalGraph
());
}
//==============================================================================
...
...
@@ -393,12 +324,7 @@ std::pair<typename graph_traits<UndirectedAdaptor<Graph> >::edge_iterator,
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge_iterator
>
edges
(
const
UndirectedAdaptor
<
Graph
>&
g
)
{
std
::
pair
<
typename
graph_traits
<
Graph
>::
edge_iterator
,
typename
graph_traits
<
Graph
>::
edge_iterator
>
range
;
range
=
edges
(
g
.
OriginalGraph
());
typedef
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge_iterator
e_iter_t
;
return
std
::
make_pair
(
e_iter_t
(
range
.
first
,
make_undirected_edge
<
Graph
>
()),
e_iter_t
(
range
.
second
,
make_undirected_edge
<
Graph
>
()));
return
edges
(
g
.
OriginalGraph
());
}
//==============================================================================
...
...
@@ -412,17 +338,15 @@ edge(typename graph_traits<UndirectedAdaptor<Graph> >::vertex_descriptor u,
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex_descriptor
v
,
const
UndirectedAdaptor
<
Graph
>&
g
)
{
bool
reversed
=
false
;
std
::
pair
<
typename
graph_traits
<
Graph
>::
edge_descriptor
,
bool
>
res
=
edge
(
u
,
v
,
g
.
OriginalGraph
());
auto
res
=
edge
(
u
,
v
,
g
.
OriginalGraph
());
if
(
!
res
.
second
)
{
res
=
edge
(
v
,
u
,
g
.
OriginalGraph
());
reversed
=
true
;
get
<
3
>
(
res
.
first
)
=
true
;
}
return
std
::
make_pair
(
typename
UndirectedAdaptor
<
Graph
>::
EdgeDescriptor
(
res
.
first
,
reversed
),
res
.
second
)
;
return
res
;
}
//==============================================================================
...
...
@@ -435,26 +359,12 @@ std::pair<typename graph_traits<UndirectedAdaptor<Graph> >::out_edge_iterator,
out_edges
(
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex_descriptor
u
,
const
UndirectedAdaptor
<
Graph
>&
g
)
{
typedef
typename
graph_traits
<
Graph
>::
out_edge_iterator
eo_t
;
typedef
typename
graph_traits
<
Graph
>::
in_edge_iterator
ei_t
;
std
::
pair
<
eo_t
,
eo_t
>
range1
=
out_edges
(
u
,
g
.
OriginalGraph
());
std
::
pair
<
ei_t
,
ei_t
>
range2
=
in_edges
(
u
,
g
.
OriginalGraph
());
typedef
typename
transformed_iterator
<
Graph
,
eo_t
>::
type
teo_t
;
typedef
typename
transformed_iterator
<
Graph
,
ei_t
>::
type
tei_t
;
auto
range1
=
in_edges
(
u
,
g
.
OriginalGraph
());
auto
range2
=
out_edges
(
u
,
g
.
OriginalGraph
());
std
::
pair
<
teo_t
,
teo_t
>
trange1
(
teo_t
(
range1
.
first
,
make_undirected_edge
<
Graph
>
(
false
)),
teo_t
(
range1
.
second
,
make_undirected_edge
<
Graph
>
(
false
)));
std
::
pair
<
tei_t
,
tei_t
>
trange2
(
tei_t
(
range2
.
first
,
make_undirected_edge
<
Graph
>
(
true
)),
tei_t
(
range2
.
second
,
make_undirected_edge
<
Graph
>
(
true
)));
joined_edge_iterator
<
Graph
>
begin
(
range1
,
range2
,
range1
.
first
,
range2
.
first
);
joined_edge_iterator
<
Graph
>
end
(
range1
,
range2
,
range1
.
second
,
range2
.
second
);
joined_iterator
<
teo_t
,
tei_t
>
begin
(
trange1
,
trange2
,
trange1
.
first
,
trange2
.
first
);
joined_iterator
<
teo_t
,
tei_t
>
end
(
trange1
,
trange2
,
trange1
.
second
,
trange2
.
second
);
return
std
::
make_pair
(
begin
,
end
);
}
...
...
@@ -508,7 +418,7 @@ typename graph_traits<UndirectedAdaptor<Graph> >::degree_size_type
out_degree
(
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex_descriptor
u
,
const
UndirectedAdaptor
<
Graph
>&
g
)
{
return
(
out_degree
(
u
,
g
.
OriginalGraph
())
+
in_degree
(
u
,
g
.
OriginalGraph
()));
return
(
out_degree
(
u
,
g
.
OriginalGraph
())
+
in_degree
(
u
,
g
.
OriginalGraph
()));
}
//==============================================================================
...
...
@@ -589,12 +499,7 @@ add_edge(typename graph_traits<UndirectedAdaptor<Graph> >::vertex_descriptor u,
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex_descriptor
v
,
UndirectedAdaptor
<
Graph
>&
g
)
{
std
::
pair
<
typename
graph_traits
<
Graph
>::
edge_descriptor
,
bool
>
retval
=
add_edge
(
u
,
v
,
g
.
OriginalGraph
());
return
std
::
make_pair
(
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge_descriptor
(
retval
.
first
,
false
),
retval
.
second
);
return
add_edge
(
u
,
v
,
g
.
OriginalGraph
());
}
//==============================================================================
...
...
@@ -608,12 +513,7 @@ add_edge(typename graph_traits<UndirectedAdaptor<Graph> >::vertex_descriptor u,
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex_descriptor
v
,
const
EdgeProperties
&
ep
,
UndirectedAdaptor
<
Graph
>&
g
)
{
std
::
pair
<
typename
graph_traits
<
Graph
>::
edge_descriptor
,
bool
>
retval
=
add_edge
(
u
,
v
,
ep
,
g
.
OriginalGraph
());
return
std
::
make_pair
(
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge_descriptor
(
retval
.
first
,
false
),
retval
.
second
);
return
add_edge
(
u
,
v
,
ep
,
g
.
OriginalGraph
());
}
//==============================================================================
...
...
@@ -625,8 +525,8 @@ void remove_edge(typename graph_traits<UndirectedAdaptor<Graph> >::vertex_descri
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
vertex_descriptor
v
,
UndirectedAdaptor
<
Graph
>&
g
)
{
remove_edge
(
u
,
v
,
g
.
OriginalGraph
());
remove_edge
(
v
,
u
,
g
.
OriginalGraph
());
remove_edge
(
u
,
v
,
g
.
OriginalGraph
());
remove_edge
(
v
,
u
,
g
.
OriginalGraph
());
}
//==============================================================================
...
...
@@ -637,8 +537,7 @@ inline __attribute__((always_inline))
void
remove_edge
(
typename
graph_traits
<
UndirectedAdaptor
<
Graph
>
>::
edge_descriptor
e
,
UndirectedAdaptor
<
Graph
>&
g
)
{
remove_edge
(
typename
graph_traits
<
Graph
>::
edge_descriptor
(
e
),
g
.
OriginalGraph
());
remove_edge
(
e
,
g
.
OriginalGraph
());
}
//==============================================================================
...
...
src/graph/graph_adjacency.hh
View file @
09139678
...
...
@@ -134,7 +134,7 @@ class adj_list
public:
struct
graph_tag
{};
typedef
Vertex
vertex_t
;
typedef
std
::
tuple
<
vertex_t
,
vertex_t
,
vertex_t
>
edge_descriptor
;
typedef
std
::
tuple
<
vertex_t
,
vertex_t
,
vertex_t
,
bool
>
edge_descriptor
;
typedef
std
::
vector
<
std
::
pair
<
vertex_t
,
vertex_t
>
>
edge_list_t
;
typedef
typename
integer_range
<
Vertex
>::
iterator
vertex_iterator
;
...
...
@@ -158,7 +158,7 @@ public:
vertex_t
_src
;
typedef
edge_descriptor
result_type
;
edge_descriptor
operator
()(
const
std
::
pair
<
vertex_t
,
vertex_t
>&
v
)
const
{
return
std
::
make_tuple
(
_src
,
v
.
first
,
v
.
second
);
}
{
return
std
::
make_tuple
(
_src
,
v
.
first
,
v
.
second
,
false
);
}
};
struct
make_in_edge
...
...
@@ -168,7 +168,7 @@ public:
vertex_t
_tgt
;
typedef
edge_descriptor
result_type
;
edge_descriptor
operator
()(
const
std
::
pair
<
vertex_t
,
vertex_t
>&
v
)
const
{
return
std
::
make_tuple
(
v
.
first
,
_tgt
,
v
.
second
);
}
{
return
std
::
make_tuple
(
v
.
first
,
_tgt
,
v
.
second
,
false
);
}
};
typedef
transform_random_access_iterator
<
make_out_edge
,
typename
edge_list_t
::
const_iterator
>
...
...
@@ -224,7 +224,7 @@ public:
edge_descriptor
dereference
()
const
{
return
std
::
make_tuple
(
vertex_t
(
_vi
-
_vi_begin
),
_ei
->
first
,
_ei
->
second
);
_ei
->
first
,
_ei
->
second
,
false
);
}
typename
std
::
vector
<
edge_list_t
>::
const_iterator
_vi_begin
;
...
...
@@ -467,9 +467,11 @@ edge(Vertex s, Vertex t, const adj_list<Vertex>& g)
const
auto
&
oes
=
g
.
_out_edges
[
s
];
for
(
size_t
i
=
0
;
i
<
oes
.
size
();
++
i
)
if
(
oes
[
i
].
first
==
t
)
return
std
::
make_pair
(
std
::
make_tuple
(
s
,
t
,
oes
[
i
].
second
),
true
);
return
std
::
make_pair
(
std
::
make_tuple
(
s
,
t
,
oes
[
i
].
second
,
false
),
true
);
Vertex
v
=
graph_traits
<
adj_list
<
Vertex
>
>::
null_vertex
();
return
std
::
make_pair
(
std
::
make_tuple
(
v
,
v
,
v
),
false
);
return
std
::
make_pair
(
std
::
make_tuple
(
v
,
v
,
v
,
false
),
false
);
}
template
<
class
Vertex
>
...
...
@@ -733,7 +735,7 @@ add_edge(Vertex s, Vertex t, adj_list<Vertex>& g)
g
.
_epos
[
idx
].
second
=
g
.
_in_edges
[
t
].
size
()
-
1
;
}
return
std
::
make_pair
(
std
::
make_tuple
(
s
,
t
,
idx
),
true
);
return
std
::
make_pair
(
std
::
make_tuple
(
s
,
t
,
idx
,
false
),
true
);
}
template
<
class
Vertex
>
...
...
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