mirror of
https://github.com/paparazzi/paparazzi.git
synced 2026-06-05 06:54:49 +08:00
active aircraft highlighted. Buttons of others hidden
This commit is contained in:
@@ -508,7 +508,6 @@ let _main =
|
|||||||
|
|
||||||
(** Aircraft notebook *)
|
(** Aircraft notebook *)
|
||||||
let ac_notebook = GPack.notebook ~tab_border:0 () in
|
let ac_notebook = GPack.notebook ~tab_border:0 () in
|
||||||
ac_notebook#connect#switch_page ~callback:(fun i -> Printf.printf "tab=%d -> %d\n%!" ac_notebook#current_page i);
|
|
||||||
|
|
||||||
(** Alerts text frame *)
|
(** Alerts text frame *)
|
||||||
let alert_page = GBin.frame () in
|
let alert_page = GBin.frame () in
|
||||||
@@ -573,25 +572,8 @@ let _main =
|
|||||||
ignore (frame#event#connect#button_press ~callback)
|
ignore (frame#event#connect#button_press ~callback)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
(** Wait for A/Cs and subsequent messages *)
|
||||||
(** Periodically probe new A/Cs *)
|
Live.listen_acs_and_msgs geomap ac_notebook my_alert !auto_center_new_ac;
|
||||||
ignore (Glib.Timeout.add 2000 (fun () -> Live.message_request "map2d" "AIRCRAFTS" [] (fun _sender vs -> Live.aircrafts_msg my_alert geomap ac_notebook vs); false));
|
|
||||||
|
|
||||||
(** New aircraft message *)
|
|
||||||
Live.safe_bind "NEW_AIRCRAFT" (fun _sender vs -> Live.one_new_ac my_alert geomap ac_notebook (Pprz.string_assoc "ac_id" vs));
|
|
||||||
|
|
||||||
(** Listen for all messages on ivy *)
|
|
||||||
Live.listen_flight_params geomap !auto_center_new_ac my_alert;
|
|
||||||
Live.listen_wind_msg geomap;
|
|
||||||
Live.listen_fbw_msg ();
|
|
||||||
Live.listen_engine_status_msg ();
|
|
||||||
Live.listen_if_calib_msg ();
|
|
||||||
Live.listen_waypoint_moved ();
|
|
||||||
Live.listen_infrared ();
|
|
||||||
Live.listen_svsinfo ();
|
|
||||||
Live.listen_telemetry_status ();
|
|
||||||
Live.listen_alert my_alert;
|
|
||||||
Live.listen_error my_alert;
|
|
||||||
|
|
||||||
(** Display the window *)
|
(** Display the window *)
|
||||||
let accel_group = menu_fact#accel_group in
|
let accel_group = menu_fact#accel_group in
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ type aircraft = {
|
|||||||
misc_page : Pages.misc;
|
misc_page : Pages.misc;
|
||||||
dl_settings_page : Pages.settings option;
|
dl_settings_page : Pages.settings option;
|
||||||
rc_settings_page : Pages.rc_settings option;
|
rc_settings_page : Pages.rc_settings option;
|
||||||
|
pages : GObj.widget;
|
||||||
|
notebook_label : GMisc.label;
|
||||||
strip : Strip.t;
|
strip : Strip.t;
|
||||||
mutable first_pos : bool;
|
mutable first_pos : bool;
|
||||||
mutable last_block_name : string;
|
mutable last_block_name : string;
|
||||||
@@ -83,10 +85,26 @@ type aircraft = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let live_aircrafts = Hashtbl.create 3
|
let live_aircrafts = Hashtbl.create 3
|
||||||
|
let active_ac = ref ""
|
||||||
let get_ac = fun vs ->
|
let get_ac = fun vs ->
|
||||||
let ac_id = Pprz.string_assoc "ac_id" vs in
|
let ac_id = Pprz.string_assoc "ac_id" vs in
|
||||||
Hashtbl.find live_aircrafts ac_id
|
Hashtbl.find live_aircrafts ac_id
|
||||||
|
|
||||||
|
let select_ac = fun ?(switch_notebook = true) acs_notebook ac_id ->
|
||||||
|
if !active_ac <> ac_id then
|
||||||
|
let ac = Hashtbl.find live_aircrafts ac_id in
|
||||||
|
ac.notebook_label#set_width_chars 20;
|
||||||
|
ac.strip#show_buttons ();
|
||||||
|
if !active_ac <> "" then begin
|
||||||
|
let ac' = Hashtbl.find live_aircrafts !active_ac in
|
||||||
|
ac'.strip#hide_buttons ();
|
||||||
|
ac'.notebook_label#set_width_chars (String.length ac'.notebook_label#text)
|
||||||
|
end;
|
||||||
|
active_ac := ac_id;
|
||||||
|
if switch_notebook then
|
||||||
|
let n = acs_notebook#page_num ac.pages in
|
||||||
|
acs_notebook#goto_page n
|
||||||
|
|
||||||
|
|
||||||
module M = Map.Make (struct type t = string let compare = compare end)
|
module M = Map.Make (struct type t = string let compare = compare end)
|
||||||
let log =
|
let log =
|
||||||
@@ -298,7 +316,6 @@ let create_ac = fun alert (geomap:G.widget) (acs_notebook:GPack.notebook) (ac_id
|
|||||||
let eb = GBin.event_box () in
|
let eb = GBin.event_box () in
|
||||||
let _label = GMisc.label ~text:name ~packing:eb#add () in
|
let _label = GMisc.label ~text:name ~packing:eb#add () in
|
||||||
eb#coerce#misc#modify_bg [`NORMAL, `NAME color;`ACTIVE, `NAME color];
|
eb#coerce#misc#modify_bg [`NORMAL, `NAME color;`ACTIVE, `NAME color];
|
||||||
_label#set_width_chars 20;
|
|
||||||
|
|
||||||
(** Put a notebook for this A/C *)
|
(** Put a notebook for this A/C *)
|
||||||
let ac_frame = GBin.frame ~packing:(acs_notebook#append_page ~tab_label:eb#coerce) () in
|
let ac_frame = GBin.frame ~packing:(acs_notebook#append_page ~tab_label:eb#coerce) () in
|
||||||
@@ -306,16 +323,9 @@ let create_ac = fun alert (geomap:G.widget) (acs_notebook:GPack.notebook) (ac_id
|
|||||||
let visible = fun w ->
|
let visible = fun w ->
|
||||||
ac_notebook#page_num w#coerce = ac_notebook#current_page in
|
ac_notebook#page_num w#coerce = ac_notebook#current_page in
|
||||||
|
|
||||||
(** Add a strip and connect it to the A/C notebook *)
|
(** Add a strip *)
|
||||||
let select_this_tab =
|
|
||||||
let n = acs_notebook#page_num ac_frame#coerce in
|
|
||||||
fun () -> acs_notebook#goto_page n in
|
|
||||||
let strip = Strip.add config color center_ac (mark geomap ac_id track !Plugin.frame) in
|
let strip = Strip.add config color center_ac (mark geomap ac_id track !Plugin.frame) in
|
||||||
let deselect_others = fun () ->
|
strip#connect (fun () -> select_ac acs_notebook ac_id);
|
||||||
Hashtbl.iter (fun ac_id' ac -> if ac_id' <> ac_id then ac.strip#hide_buttons ()) live_aircrafts in
|
|
||||||
strip#connect (fun () -> select_this_tab (); deselect_others ());
|
|
||||||
deselect_others ();
|
|
||||||
|
|
||||||
|
|
||||||
(** Build the XML flight plan, connect then "jump_to_block" *)
|
(** Build the XML flight plan, connect then "jump_to_block" *)
|
||||||
let fp_xml = ExtXml.child fp_xml_dump "flight_plan" in
|
let fp_xml = ExtXml.child fp_xml_dump "flight_plan" in
|
||||||
@@ -426,8 +436,12 @@ let create_ac = fun alert (geomap:G.widget) (acs_notebook:GPack.notebook) (ac_id
|
|||||||
last_block_name = ""; alt = 0.; target_alt = 0.;
|
last_block_name = ""; alt = 0.; target_alt = 0.;
|
||||||
in_kill_mode = false; speed = 0.;
|
in_kill_mode = false; speed = 0.;
|
||||||
wind_dir = 42.; ground_prox = true;
|
wind_dir = 42.; ground_prox = true;
|
||||||
wind_speed = 0.; } in
|
wind_speed = 0.;
|
||||||
|
pages = ac_frame#coerce;
|
||||||
|
notebook_label = _label
|
||||||
|
} in
|
||||||
Hashtbl.add live_aircrafts ac_id ac;
|
Hashtbl.add live_aircrafts ac_id ac;
|
||||||
|
select_ac acs_notebook ac_id;
|
||||||
|
|
||||||
(** Periodically send the wind estimation through
|
(** Periodically send the wind estimation through
|
||||||
a WIND_INFO message packed into a RAW_DATALINK *)
|
a WIND_INFO message packed into a RAW_DATALINK *)
|
||||||
@@ -816,3 +830,34 @@ let listen_error = fun a ->
|
|||||||
let msg = Pprz.string_assoc "message" vs in
|
let msg = Pprz.string_assoc "message" vs in
|
||||||
log_and_say a "gcs" msg in
|
log_and_say a "gcs" msg in
|
||||||
safe_bind "TELEMETRY_ERROR" get_error
|
safe_bind "TELEMETRY_ERROR" get_error
|
||||||
|
|
||||||
|
|
||||||
|
let listen_acs_and_msgs = fun geomap ac_notebook my_alert auto_center_new_ac ->
|
||||||
|
(** Periodically probe new A/Cs *)
|
||||||
|
ignore (Glib.Timeout.add 2000 (fun () -> message_request "map2d" "AIRCRAFTS" [] (fun _sender vs -> aircrafts_msg my_alert geomap ac_notebook vs); false));
|
||||||
|
|
||||||
|
(** New aircraft message *)
|
||||||
|
safe_bind "NEW_AIRCRAFT" (fun _sender vs -> one_new_ac my_alert geomap ac_notebook (Pprz.string_assoc "ac_id" vs));
|
||||||
|
|
||||||
|
(** Listen for all messages on ivy *)
|
||||||
|
listen_flight_params geomap auto_center_new_ac my_alert;
|
||||||
|
listen_wind_msg geomap;
|
||||||
|
listen_fbw_msg ();
|
||||||
|
listen_engine_status_msg ();
|
||||||
|
listen_if_calib_msg ();
|
||||||
|
listen_waypoint_moved ();
|
||||||
|
listen_infrared ();
|
||||||
|
listen_svsinfo ();
|
||||||
|
listen_telemetry_status ();
|
||||||
|
listen_alert my_alert;
|
||||||
|
listen_error my_alert;
|
||||||
|
|
||||||
|
(** Select the active aircraft on notebook page selection *)
|
||||||
|
let callback = fun i ->
|
||||||
|
let ac_page = ac_notebook#get_nth_page i in
|
||||||
|
Hashtbl.iter
|
||||||
|
(fun ac_id ac ->
|
||||||
|
if ac.pages#get_oid = ac_page#get_oid
|
||||||
|
then select_ac ~switch_notebook:false ac_notebook ac_id)
|
||||||
|
live_aircrafts in
|
||||||
|
ignore (ac_notebook#connect#switch_page ~callback)
|
||||||
|
|||||||
@@ -1,17 +1 @@
|
|||||||
val message_request : string -> string -> Pprz.values -> (string -> Pprz.values -> unit) -> unit
|
val listen_acs_and_msgs : MapCanvas.widget -> GPack.notebook -> Pages.alert -> bool -> unit
|
||||||
|
|
||||||
val aircrafts_msg : Pages.alert -> MapCanvas.widget -> GPack.notebook -> Pprz.values -> unit
|
|
||||||
val safe_bind : string -> (string -> Pprz.values -> unit) -> unit
|
|
||||||
val one_new_ac : Pages.alert -> MapCanvas.widget -> GPack.notebook -> string -> unit
|
|
||||||
val listen_flight_params :
|
|
||||||
< center : MapCanvas.LL.geographic -> unit; .. > -> bool -> Pages.alert -> unit
|
|
||||||
val listen_wind_msg : MapCanvas.widget -> unit
|
|
||||||
val listen_fbw_msg : unit -> unit
|
|
||||||
val listen_engine_status_msg : unit -> unit
|
|
||||||
val listen_if_calib_msg : unit -> unit
|
|
||||||
val listen_waypoint_moved : unit -> unit
|
|
||||||
val listen_infrared : unit -> unit
|
|
||||||
val listen_svsinfo : unit -> unit
|
|
||||||
val listen_alert : Pages.alert -> unit
|
|
||||||
val listen_error : Pages.alert -> unit
|
|
||||||
val listen_telemetry_status : unit -> unit
|
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ type t =
|
|||||||
set_bat : float -> unit;
|
set_bat : float -> unit;
|
||||||
set_color : string -> string -> unit;
|
set_color : string -> string -> unit;
|
||||||
set_label : string -> string -> unit;
|
set_label : string -> string -> unit;
|
||||||
connect : (unit -> unit) -> unit; hide_buttons : unit -> unit >
|
connect : (unit -> unit) -> unit;
|
||||||
|
hide_buttons : unit -> unit;
|
||||||
|
show_buttons : unit -> unit >
|
||||||
|
|
||||||
let bat_max = 12.5
|
let bat_max = 12.5
|
||||||
let bat_min = 9.
|
let bat_min = 9.
|
||||||
@@ -66,44 +68,45 @@ class gauge = fun ?(color="green") ?(history_len=50) gauge v_min v_max ->
|
|||||||
val mutable history_index = -1
|
val mutable history_index = -1
|
||||||
method set = fun value string ->
|
method set = fun value string ->
|
||||||
let {Gtk.width=width; height=height} = gauge#misc#allocation in
|
let {Gtk.width=width; height=height} = gauge#misc#allocation in
|
||||||
let dr = GDraw.pixmap ~width ~height ~window:gauge () in
|
if height > 1 then (* Else the drawing area is not allocated already *)
|
||||||
dr#set_foreground (`NAME "orange");
|
let dr = GDraw.pixmap ~width ~height ~window:gauge () in
|
||||||
dr#rectangle ~x:0 ~y:0 ~width ~height ~filled:true ();
|
dr#set_foreground (`NAME "orange");
|
||||||
|
dr#rectangle ~x:0 ~y:0 ~width ~height ~filled:true ();
|
||||||
let f = (value -. v_min) /. (v_max -. v_min) in
|
|
||||||
let f = max 0. (min 1. f) in
|
let f = (value -. v_min) /. (v_max -. v_min) in
|
||||||
let h = truncate (float height *. f) in
|
let f = max 0. (min 1. f) in
|
||||||
|
let h = truncate (float height *. f) in
|
||||||
(* First call: fill the array with the given value *)
|
|
||||||
if history_index < 0 then begin
|
(* First call: fill the array with the given value *)
|
||||||
|
if history_index < 0 then begin
|
||||||
|
for i = 0 to history_len - 1 do
|
||||||
|
history.(i) <- h
|
||||||
|
done;
|
||||||
|
history_index <- 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
(* Store the value in the history array and update index *)
|
||||||
|
history.(history_index) <- h;
|
||||||
|
history_index <- (history_index+1) mod history_len;
|
||||||
|
|
||||||
|
dr#set_foreground (`NAME color);
|
||||||
|
|
||||||
|
(* From left to right, older to new values *)
|
||||||
|
let polygon = ref [0,height; width,height] in
|
||||||
for i = 0 to history_len - 1 do
|
for i = 0 to history_len - 1 do
|
||||||
history.(i) <- h
|
let idx = (history_index+i) mod history_len in
|
||||||
|
polygon := ((i*width)/history_len, (height-history.(idx))):: !polygon;
|
||||||
done;
|
done;
|
||||||
history_index <- 0;
|
polygon := (width,height-h):: !polygon;
|
||||||
end;
|
dr#polygon ~filled:true !polygon;
|
||||||
|
|
||||||
(* Store the value in the history array and update index *)
|
let context = gauge#misc#create_pango_context in
|
||||||
history.(history_index) <- h;
|
let layout = context#create_layout in
|
||||||
history_index <- (history_index+1) mod history_len;
|
Pango.Layout.set_text layout string;
|
||||||
|
let (w,h) = Pango.Layout.get_pixel_size layout in
|
||||||
dr#set_foreground (`NAME color);
|
dr#put_layout ~x:((width-w)/2) ~y:((height-h)/2) ~fore:`BLACK layout;
|
||||||
|
|
||||||
(* From left to right, older to new values *)
|
(new GDraw.drawable gauge#misc#window)#put_pixmap ~x:0 ~y:0 dr#pixmap
|
||||||
let polygon = ref [0,height; width,height] in
|
|
||||||
for i = 0 to history_len - 1 do
|
|
||||||
let idx = (history_index+i) mod history_len in
|
|
||||||
polygon := ((i*width)/history_len, (height-history.(idx))):: !polygon;
|
|
||||||
done;
|
|
||||||
polygon := (width,height-h):: !polygon;
|
|
||||||
dr#polygon ~filled:true !polygon;
|
|
||||||
|
|
||||||
let context = gauge#misc#create_pango_context in
|
|
||||||
let layout = context#create_layout in
|
|
||||||
Pango.Layout.set_text layout string;
|
|
||||||
let (w,h) = Pango.Layout.get_pixel_size layout in
|
|
||||||
dr#put_layout ~x:((width-w)/2) ~y:((height-h)/2) ~fore:`BLACK layout;
|
|
||||||
|
|
||||||
(new GDraw.drawable gauge#misc#window)#put_pixmap ~x:0 ~y:0 dr#pixmap
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -160,8 +163,10 @@ let add config color center_ac mark =
|
|||||||
|
|
||||||
(** Table (everything except the user buttons) *)
|
(** Table (everything except the user buttons) *)
|
||||||
let strip = GPack.table ~rows ~columns ~col_spacings:3 ~packing:framevb#add () in
|
let strip = GPack.table ~rows ~columns ~col_spacings:3 ~packing:framevb#add () in
|
||||||
strip#set_row_spacing 0 3;
|
strip#set_row_spacing 0 2;
|
||||||
strip#set_row_spacing (rows-2) 3;
|
strip#set_row_spacing 1 2;
|
||||||
|
strip#set_row_spacing (rows-1) 2;
|
||||||
|
strip#set_row_spacing (rows-2) 2;
|
||||||
|
|
||||||
(* Name in top left *)
|
(* Name in top left *)
|
||||||
let name = (GMisc.label ~text: (ac_name) ~packing: (strip#attach ~top: 0 ~left: 0) ()) in
|
let name = (GMisc.label ~text: (ac_name) ~packing: (strip#attach ~top: 0 ~left: 0) ()) in
|
||||||
@@ -187,13 +192,13 @@ let add config color center_ac mark =
|
|||||||
tooltips#set_tip plane_color#coerce ~text:"Flight time - Block time - Stage time - Block name";
|
tooltips#set_tip plane_color#coerce ~text:"Flight time - Block time - Stage time - Block name";
|
||||||
|
|
||||||
(* battery gauge *)
|
(* battery gauge *)
|
||||||
let bat_da = GMisc.drawing_area ~height:60 ~show:true ~packing:(strip#attach ~top:1 ~bottom:(rows-1) ~left:0) () in
|
let bat_da = GMisc.drawing_area ~show:true ~packing:(strip#attach ~top:1 ~bottom:(rows-1) ~left:0) () in
|
||||||
bat_da#misc#realize ();
|
bat_da#misc#realize ();
|
||||||
let bat = new gauge bat_da bat_min bat_max in
|
let bat = new gauge bat_da bat_min bat_max in
|
||||||
|
|
||||||
(* AGL gauge *)
|
(* AGL gauge *)
|
||||||
let agl_box = GBin.event_box ~packing:(strip#attach ~top:1 ~bottom:3 ~left:(columns-1)) () in
|
let agl_box = GBin.event_box ~packing:(strip#attach ~top:1 ~bottom:3 ~left:(columns-1)) () in
|
||||||
let agl_da = GMisc.drawing_area ~width:40 ~height:60 ~show:true ~packing:agl_box#add () in
|
let agl_da = GMisc.drawing_area ~width:30 ~show:true ~packing:agl_box#add () in
|
||||||
agl_da#misc#realize ();
|
agl_da#misc#realize ();
|
||||||
tooltips#set_tip agl_box#coerce ~text:"AGL (m)";
|
tooltips#set_tip agl_box#coerce ~text:"AGL (m)";
|
||||||
let agl = new gauge agl_da 0. agl_max in
|
let agl = new gauge agl_da 0. agl_max in
|
||||||
@@ -225,7 +230,7 @@ let add config color center_ac mark =
|
|||||||
) labels_name;
|
) labels_name;
|
||||||
|
|
||||||
(* Buttons *)
|
(* Buttons *)
|
||||||
let hbox = GPack.hbox ~packing:framevb#add () in
|
let hbox = GPack.hbox ~spacing:2 ~packing:framevb#add () in
|
||||||
let b = GButton.button ~label:"Center A/C" ~packing:hbox#add () in
|
let b = GButton.button ~label:"Center A/C" ~packing:hbox#add () in
|
||||||
ignore(b#connect#clicked ~callback:center_ac);
|
ignore(b#connect#clicked ~callback:center_ac);
|
||||||
let b = GButton.button ~label:"Mark" ~packing:hbox#add () in
|
let b = GButton.button ~label:"Mark" ~packing:hbox#add () in
|
||||||
@@ -237,9 +242,9 @@ let add config color center_ac mark =
|
|||||||
ignore (b#connect#clicked ~callback:mark);
|
ignore (b#connect#clicked ~callback:mark);
|
||||||
|
|
||||||
(* User buttons *)
|
(* User buttons *)
|
||||||
let user_hbox = GPack.hbox ~packing:framevb#add () in
|
let user_hbox = GPack.hbox ~spacing:2 ~packing:framevb#add () in
|
||||||
|
|
||||||
(object
|
object
|
||||||
method set_agl value = set_agl agl value
|
method set_agl value = set_agl agl value
|
||||||
method set_bat value = set_bat bat value
|
method set_bat value = set_bat bat value
|
||||||
method set_label name value = set_label !strip_labels name value
|
method set_label name value = set_label !strip_labels name value
|
||||||
@@ -250,10 +255,8 @@ let add config color center_ac mark =
|
|||||||
ignore (plus30#connect#clicked (fun () -> callback 30.));
|
ignore (plus30#connect#clicked (fun () -> callback 30.));
|
||||||
ignore (minus5#connect#clicked (fun () -> callback (-5.)))
|
ignore (minus5#connect#clicked (fun () -> callback (-5.)))
|
||||||
method hide_buttons () = hbox#misc#hide (); user_hbox#misc#hide ()
|
method hide_buttons () = hbox#misc#hide (); user_hbox#misc#hide ()
|
||||||
|
method show_buttons () = hbox#misc#show (); user_hbox#misc#show ()
|
||||||
method connect = fun (select: unit -> unit) ->
|
method connect = fun (select: unit -> unit) ->
|
||||||
let callback = fun _ ->
|
let callback = fun _ -> select (); true in
|
||||||
select ();
|
|
||||||
hbox#misc#show (); user_hbox#misc#show ();
|
|
||||||
true in
|
|
||||||
ignore (strip_ebox#event#connect#button_press ~callback)
|
ignore (strip_ebox#event#connect#button_press ~callback)
|
||||||
end:t)
|
end
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
type t =
|
type t = <
|
||||||
< add_widget : GObj.widget -> unit;
|
add_widget : GObj.widget -> unit;
|
||||||
connect_shift_alt : (float -> unit) -> unit;
|
connect_shift_alt : (float -> unit) -> unit;
|
||||||
set_agl : float -> unit;
|
set_agl : float -> unit;
|
||||||
set_bat : float -> unit;
|
set_bat : float -> unit;
|
||||||
set_color : string -> string -> unit;
|
set_color : string -> string -> unit;
|
||||||
set_label : string -> string -> unit;
|
set_label : string -> string -> unit;
|
||||||
hide_buttons : unit -> unit;
|
hide_buttons : unit -> unit;
|
||||||
connect : (unit -> unit) -> unit>
|
show_buttons : unit -> unit;
|
||||||
|
connect : (unit -> unit) -> unit
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
val scrolled : GBin.scrolled_window
|
val scrolled : GBin.scrolled_window
|
||||||
|
|||||||
Reference in New Issue
Block a user