Commit bcd752f4 authored by Claes Sjofors's avatar Claes Sjofors

Ge graph view added

parent b7e824c3
......@@ -77,6 +77,7 @@
#include "ge_subgraphs_gtk.h"
#include "ge_util.h"
#include "ge_msg.h"
#include "ge_item_view_gtk.h"
#include "wb_wnav_selformat.h"
#include "cow_wow_gtk.h"
#include "cow_logw_gtk.h"
......@@ -1270,6 +1271,25 @@ void GeGtk::activate_view_plant(GtkWidget *w, gpointer data)
#endif
}
void GeGtk::activate_view_graphlist(GtkWidget *w, gpointer data)
{
Ge *ge = (Ge *)data;
int set = (int) gtk_check_menu_item_get_active( GTK_CHECK_MENU_ITEM( ((GeGtk *)ge)->view_graphlist_w));
if ( w != ((GeGtk *)ge)->view_graphlist_w) {
set = !set;
gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( ((GeGtk *)ge)->view_graphlist_w), set ? TRUE : FALSE);
}
if ( set) {
g_object_set( ((GeGtk *)ge)->graph_list, "visible", TRUE, NULL);
}
else {
g_object_set( ((GeGtk *)ge)->graph_list, "visible", FALSE, NULL);
}
ge->set_focus(0);
}
void GeGtk::activate_concorner_right(GtkWidget *w, gpointer gectx)
{
......@@ -2209,6 +2229,10 @@ GeGtk::GeGtk( void *x_parent_ctx,
gtk_widget_add_accelerator( view_plant_w, "activate", accel_g,
'p', GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
view_graphlist_w = gtk_check_menu_item_new_with_mnemonic( "Vie_w graph list");
g_signal_connect( view_graphlist_w, "activate",
G_CALLBACK(activate_view_graphlist), this);
GtkMenu *view_menu = (GtkMenu *) g_object_new( GTK_TYPE_MENU, NULL);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), view_preview_start);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), view_preview_stop);
......@@ -2216,6 +2240,7 @@ GeGtk::GeGtk( void *x_parent_ctx,
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), view_zoom_out);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), view_zoom_reset);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), view_plant_w);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), view_graphlist_w);
GtkWidget *view = gtk_menu_item_new_with_mnemonic("_View");
gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), view);
......@@ -3003,12 +3028,19 @@ GeGtk::GeGtk( void *x_parent_ctx,
gtk_paned_pack2( GTK_PANED(hpaned), vpaned2, FALSE, TRUE);
gtk_widget_show( vpaned1);
GeItemViewGtk *item_view = new GeItemViewGtk( this);
GtkWidget *hpaned2 = gtk_hpaned_new();
graph_list = item_view->widget();
gtk_paned_pack1( GTK_PANED(hpaned2), graph_list, FALSE, FALSE);
gtk_paned_pack2( GTK_PANED(hpaned2), hpaned, TRUE, TRUE);
gtk_widget_show( hpaned2);
GtkWidget *vbox = gtk_vbox_new( FALSE, 0);
gtk_box_pack_start( GTK_BOX(vbox), GTK_WIDGET(menu_bar), FALSE, FALSE, 0);
gtk_box_pack_start( GTK_BOX(vbox), GTK_WIDGET(tools3), FALSE, FALSE, 0);
gtk_box_pack_start( GTK_BOX(vbox), GTK_WIDGET(tools2), FALSE, FALSE, 0);
gtk_box_pack_start( GTK_BOX(vbox), GTK_WIDGET(tools), FALSE, FALSE, 0);
gtk_box_pack_start( GTK_BOX(vbox), GTK_WIDGET(hpaned), TRUE, TRUE, 0);
gtk_box_pack_start( GTK_BOX(vbox), GTK_WIDGET(hpaned2), TRUE, TRUE, 0);
gtk_box_pack_start( GTK_BOX(vbox), GTK_WIDGET(statusbar), FALSE, FALSE, 0);
gtk_container_add( GTK_CONTAINER(toplevel), vbox);
......@@ -3025,6 +3057,7 @@ GeGtk::GeGtk( void *x_parent_ctx,
#endif
g_object_set( cmd_prompt, "visible", FALSE, NULL);
g_object_set( cmd_input, "visible", FALSE, NULL);
g_object_set( graph_list, "visible", FALSE, NULL);
subpalette->get_path( &path_cnt, &path);
graph->set_subgraph_path( path_cnt, path);
......
......@@ -79,6 +79,8 @@ class GeGtk : public Ge {
GtkWidget *grid_size_01_w;
GtkWidget *show_grid_w;
GtkWidget *view_plant_w;
GtkWidget *view_graphlist_w;
GtkWidget *graph_list;
GdkAtom graph_atom;
CoWowRecall *text_recall;
CoWowRecall *name_recall;
......@@ -274,6 +276,7 @@ class GeGtk : public Ge {
static void activate_zoom_out( GtkWidget *w, gpointer gectx);
static void activate_zoom_reset( GtkWidget *w, gpointer gectx);
static void activate_view_plant( GtkWidget *w, gpointer gectx);
static void activate_view_graphlist( GtkWidget *w, gpointer gectx);
static void activate_concorner_right( GtkWidget *w, gpointer gectx);
static void activate_concorner_rounded( GtkWidget *w, gpointer gectx);
static void activate_round_amount_1( GtkWidget *w, gpointer gectx);
......
#include "glow_std.h"
#include <cstdio>
#include <cstring>
#include <vector>
#include "co_dcli.h"
#include "flow.h"
#include "flow_browctx.h"
#include "flow_browapi.h"
#include "flow_browwidget_gtk.h"
#include "glow.h"
#include "glow_colpalctx.h"
#include "glow_colpalapi.h"
#include "glow_colpalwidget_gtk.h"
#include "glow_msg.h"
#include "glow_growctx.h"
#include "glow_growapi.h"
#include "glow_growwidget_gtk.h"
#include "ge_graph_gtk.h"
#include "ge_gtk.h"
#include "ge_subpalette_gtk.h"
#include "ge_subgraphs_gtk.h"
#include "ge_util.h"
#include "ge_msg.h"
#include "ge_item_view_gtk.h"
#include "wb_wnav_selformat.h"
#include "cow_wow_gtk.h"
#include "cow_logw_gtk.h"
#include "wb_nav_gtk.h"
#include "wb_log.h"
#include "ge_item_view_gtk.h"
static void *graph_list_files();
static gpointer graph_list_store( char *);
static void directory_changed( GFileMonitor *, GFile *, GFile *, GFileMonitorEvent, gpointer);
static gboolean button_press_tree_widget( GtkWidget *, GdkEvent *, gpointer);
static void select_tree_item_pos( GtkWidget *, gint, gint);
static void autosave_toggled( GtkToggleButton *, gpointer);
GeItemViewGtk::GeItemViewGtk( gpointer gectx):
ge_ctx(gectx),
toplevel_widget(0),
tree_widget(0)
{
GtkCellRenderer *text_renderer;
GtkTreeViewColumn *name_column;
GtkWidget *scrolled_widget;
GFile *dir;
GFileMonitor *monitor;
char full_path[256];
tree_widget = (GtkWidget *)
g_object_new( GTK_TYPE_TREE_VIEW,
"rules-hint", TRUE,
"headers-visible", FALSE,
"reorderable", TRUE,
"enable-search", TRUE,
"search-column", 0,
NULL);
g_signal_connect( tree_widget, "row-activated",
G_CALLBACK(activate_tree_widget), this);
g_signal_connect( tree_widget, "button-press-event",
G_CALLBACK(button_press_tree_widget), this);
g_signal_connect( tree_widget, "focus-out-event",
G_CALLBACK(focus_out_tree_widget), this);
text_renderer = gtk_cell_renderer_text_new();
name_column =
gtk_tree_view_column_new_with_attributes( "", text_renderer, "text", 0, NULL);
g_object_set( name_column, "resizable", FALSE, "clickable", TRUE, NULL);
gtk_tree_view_append_column( GTK_TREE_VIEW(tree_widget), name_column);
autosave_button = gtk_check_button_new_with_label( "Autosave");
autosave_toggled( GTK_TOGGLE_BUTTON(autosave_button), (gpointer) TRUE);
g_signal_connect( autosave_button, "toggled",
G_CALLBACK(autosave_toggled), (gpointer) FALSE);
scrolled_widget = gtk_scrolled_window_new( NULL, NULL);
gtk_container_add( GTK_CONTAINER(scrolled_widget), tree_widget);
toplevel_widget = gtk_vbox_new( FALSE, 0);
gtk_box_pack_start( GTK_BOX(toplevel_widget), autosave_button, FALSE, FALSE, 8);
gtk_box_pack_start( GTK_BOX(toplevel_widget), scrolled_widget, TRUE, TRUE, 0);
update();
dcli_translate_filename( full_path, "$pwrp_pop");
dir = g_file_new_for_path( full_path);
monitor = g_file_monitor_directory( dir, G_FILE_MONITOR_NONE, NULL, NULL);
g_signal_connect( monitor, "changed",
G_CALLBACK( directory_changed), this);
}
GeItemViewGtk::~GeItemViewGtk()
{
gtk_widget_destroy( toplevel_widget);
}
GtkWidget *GeItemViewGtk::widget() const
{
return toplevel_widget;
}
void GeItemViewGtk::update()
{
GtkListStore *store;
char *texts;
texts = (char *) graph_list_files();
if ( !texts)
return;
store = (GtkListStore *) graph_list_store( texts);
gtk_tree_view_set_model( GTK_TREE_VIEW(tree_widget), GTK_TREE_MODEL(store));
free( texts);
}
void GeItemViewGtk::update( char *full_name, int event)
{
GtkTreeModel *store;
GtkTreeIter iter;
char *nameutf8;
char *name_p;
char *value;
gboolean rv;
name_p = strrchr( full_name, '/');
full_name = strstr( name_p, ".pwg");
if ( !full_name)
return;
name_p[full_name - name_p] = 0;
name_p++; // skip slash
nameutf8 = g_convert( name_p, -1, "UTF-8", "ISO8859-1", NULL, NULL, NULL);
store = gtk_tree_view_get_model( GTK_TREE_VIEW(tree_widget));
rv = gtk_tree_model_get_iter_first( store, &iter);
if ( event == G_FILE_MONITOR_EVENT_CREATED) {
GtkTreeIter sibl;
while ( rv) {
gtk_tree_model_get( store, &iter, 0, &value, -1);
if ( strcmp( name_p, value) < 0)
break;
rv = gtk_tree_model_iter_next( store, &iter);
}
sibl = iter;
if ( rv)
gtk_list_store_insert_before( GTK_LIST_STORE(store), &iter, &sibl);
else
gtk_list_store_append( GTK_LIST_STORE(store), &iter);
gtk_list_store_set( GTK_LIST_STORE(store), &iter, 0, nameutf8, -1);
}
else if ( event == G_FILE_MONITOR_EVENT_DELETED) {
while ( rv) {
gtk_tree_model_get( store, &iter, 0, &value, -1);
if ( strcmp( name_p, value) == 0) {
gtk_list_store_remove( GTK_LIST_STORE(store), &iter);
break;
}
rv = gtk_tree_model_iter_next( store, &iter);
}
}
g_free( nameutf8);
}
char *GeItemViewGtk::selected_text( GtkWidget *tree_widget)
{
char *sel_text;
GtkTreeIter iter;
GtkTreeModel *store;
GtkTreeSelection *selection;
gboolean selected;
sel_text = NULL;
store = gtk_tree_view_get_model( GTK_TREE_VIEW(tree_widget));
selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(tree_widget));
selected = gtk_tree_selection_get_selected( selection, NULL, &iter);
if ( selected)
gtk_tree_model_get( GTK_TREE_MODEL(store), &iter, 0, &sel_text, -1);
return sel_text;
}
char *GeItemViewGtk::selected_text() const
{
return selected_text( tree_widget);
}
void GeItemViewGtk::activate_menu_open( GtkWidget *w, gpointer data)
{
GeItemViewGtk *item_view;
GeGtk *ge;
item_view = (GeItemViewGtk *) data;
ge = (GeGtk *) item_view->ge_ctx;
if ( ge->graph->is_modified()) {
int rv;
char title[] = "Save changes";
char message[64];
sprintf( message, "Your changes will be lost. Do you want to save?");
rv = ge->create_modal_dialog( title, message, "Yes", "No", NULL, NULL);
if ( rv == wow_eModalDialogReturn_Button1) {
GeGtk::activate_save( NULL, item_view->ge_ctx);
}
}
activate_tree_widget( GTK_TREE_VIEW(item_view->tree_widget), NULL, NULL, ge);
}
void GeItemViewGtk::activate_menu_delete( GtkWidget *w, gpointer data)
{
char *sel_text;
GeItemViewGtk *item_view;
item_view = (GeItemViewGtk *) data;
sel_text = item_view->selected_text();
if ( sel_text) {
int rv;
char title[] = "Delete graph";
char message[64];
GeGtk *ge;
pwr_tCmd cmd;
sprintf( message, "Do you want to delete %s?", sel_text);
ge = (GeGtk *) item_view->ge_ctx;
rv = ge->create_modal_dialog( title, message, "Yes", "Cancel", NULL, NULL);
if ( rv != wow_eModalDialogReturn_Button1)
return;
sprintf( cmd, "rm -f $pwrp_pop/%s.pwg", sel_text);
system( cmd);
}
}
void *graph_list_files()
{
int file_cnt;
int allocated, old_allocated;
pwr_tString80 *file_p;
pwr_tString80 *old_file_p;
char found_file[80];
char fname[80];
int sts;
char dev[80];
char dir[80];
char file[80];
char type[80];
int version;
// Get the pwg files and order them
dcli_translate_filename( fname, "$pwrp_pop/*.pwg");
file_cnt = 0;
allocated = 0;
sts = dcli_search_file( fname, found_file, DCLI_DIR_SEARCH_INIT);
while ( ODD(sts)) {
if ( strstr( found_file, "__p")) {
// Skip subgraph pages
sts = dcli_search_file( fname, found_file, DCLI_DIR_SEARCH_NEXT);
continue;
}
file_cnt++;
if ( file_cnt > allocated - 1) {
if ( allocated == 0) {
allocated = 100;
file_p = (pwr_tString80 *) malloc( allocated * sizeof(*file_p));
}
else {
old_file_p = file_p;
old_allocated = allocated;
allocated += 100;
file_p = (pwr_tString80 *) malloc( allocated * sizeof(*file_p));
memcpy( file_p, old_file_p, old_allocated * sizeof(*file_p));
free( old_file_p);
}
}
dcli_parse_filename( found_file, dev, dir, file, type, &version);
strcpy( file_p[file_cnt - 1], file);
if ( strcmp( file, "") == 0)
file_cnt--;
sts = dcli_search_file( fname, found_file, DCLI_DIR_SEARCH_NEXT);
}
dcli_search_file( fname, found_file, DCLI_DIR_SEARCH_END);
if ( !file_cnt) {
return 0;
}
strcpy( file_p[file_cnt], "");
qsort( file_p, file_cnt, sizeof(*file_p), Ge::sort_files);
return file_p;
}
gpointer graph_list_store( char *texts)
{
GtkListStore *store;
GtkTreeIter iter;
char *nameutf8;
char *name_p;
int textsize;
textsize = 80;
name_p = texts;
store = gtk_list_store_new( 1, G_TYPE_STRING);
while ( strcmp( name_p, "") != 0) {
nameutf8 = g_convert( name_p, -1, "UTF-8", "ISO8859-1", NULL, NULL, NULL);
gtk_list_store_append( store, &iter);
gtk_list_store_set( store, &iter, 0, nameutf8, -1);
name_p += textsize;
g_free( nameutf8);
}
return store;
}
void directory_changed( GFileMonitor *, GFile *file, GFile *,
GFileMonitorEvent event, gpointer item_view)
{
char *name;
switch (event) {
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
break;
case G_FILE_MONITOR_EVENT_DELETED:
case G_FILE_MONITOR_EVENT_CREATED:
name = g_file_get_parse_name( file);
((GeItemViewGtk *)item_view)->update( name, event);
g_free( name);
break;
default:
break;
}
}
void GeItemViewGtk::activate_tree_widget( GtkTreeView *tree_widget, GtkTreePath *path,
GtkTreeViewColumn *column, gpointer data)
{
char *sel_text;
gboolean autosave;
GeItemViewGtk *item_view;
GeGtk *ge;
sel_text = GeItemViewGtk::selected_text( GTK_WIDGET(tree_widget));
if ( path && column) {
item_view = (GeItemViewGtk *) data;
ge = (GeGtk *) item_view->ge_ctx;
autosave = gtk_toggle_button_get_active(
GTK_TOGGLE_BUTTON(item_view->autosave_button));
if ( ge->graph->is_modified()) {
char name[80];
ge->graph->get_name( name);
if ( strcmp( name, "") == 0) {
ge->wow->DisplayError("Not saved", "Save current graph first");
return;
}
if ( autosave)
GeGtk::activate_save( NULL, item_view->ge_ctx);
else {
int rv;
char title[] = "Save changes";
char message[64];
sprintf( message, "Your changes will be lost.\nDo you want to save?");
rv = ge->create_modal_dialog( title, message, "Yes", "No", NULL, NULL);
if ( rv == wow_eModalDialogReturn_Button1) {
GeGtk::activate_save( NULL, item_view->ge_ctx);
}
}
}
}
else {
ge = (GeGtk *) data;
}
if ( sel_text) {
ge->open_graph( sel_text);
}
}
gboolean GeItemViewGtk::focus_out_tree_widget( GtkWidget *tree_widget, GdkEvent *, gpointer)
{
GtkTreeIter iter;
GtkTreeModel *store;
GtkTreeSelection *selection;
gboolean selected;
store = gtk_tree_view_get_model( GTK_TREE_VIEW(tree_widget));
selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(tree_widget));
selected = gtk_tree_selection_get_selected( selection, NULL, &iter);
if ( selected)
gtk_tree_selection_unselect_iter( selection, &iter);
return FALSE;
}
gboolean button_press_tree_widget( GtkWidget *tree_widget, GdkEvent *event, gpointer data)
{
GdkEventButton *ev;
GtkMenu *menu;
GtkWidget *menu_item;
static const char *item_text[] = {
"Open",
" ",
"Delete",
""
};
static GCallback item_cb[] = {
G_CALLBACK(GeItemViewGtk::activate_menu_open),
NULL,
G_CALLBACK(GeItemViewGtk::activate_menu_delete),
NULL,
};
ev = (GdkEventButton *) event;
if ( ev->type != GDK_BUTTON_PRESS || ev->button != 3)
return FALSE;
menu = (GtkMenu *) gtk_menu_new();
for ( int i = 0; item_text[i][0]; i++) {
if ( item_text[i][0] == ' ') {
menu_item = gtk_separator_menu_item_new();
}
else {
menu_item = gtk_menu_item_new_with_label( item_text[i]);
g_signal_connect( menu_item, "activate", G_CALLBACK(item_cb[i]), data);
}
gtk_widget_show( menu_item);
gtk_menu_shell_append( GTK_MENU_SHELL(menu), menu_item);
}
select_tree_item_pos( tree_widget, ev->x, ev->y);
gtk_menu_popup( menu, NULL, NULL, NULL, NULL, ev->button,
gtk_get_current_event_time());
return TRUE;
}
void select_tree_item_pos( GtkWidget *tree_widget, gint x, gint y)
{
GtkTreeSelection *selection;
int sel_count;
selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(tree_widget));
sel_count = gtk_tree_selection_count_selected_rows( selection);
if ( sel_count <= 1) {
GtkTreePath *path;
/* Get tree path for row that was clicked */
gtk_tree_view_get_path_at_pos( GTK_TREE_VIEW(tree_widget),
x, y, &path, NULL, NULL, NULL);
gtk_tree_selection_unselect_all( selection);
gtk_tree_selection_select_path( selection, path);
gtk_tree_path_free( path);
}
}
void autosave_toggled( GtkToggleButton *autosave_button, gpointer read)
{
#if 0
GSettings *s;
gboolean autosave;
s = g_settings_new( "org.gtk.proview");
if ( (bool) read) {
autosave = g_settings_get_boolean( s, "autosave");
gtk_toggle_button_set_active( autosave_button, autosave);
}
else {
autosave = gtk_toggle_button_get_active( autosave_button);
g_settings_set_boolean( s, "autosave", autosave);
}
#endif
}
/*
* Proview Open Source Process Control.
* Copyright (C) 2005-2012 SSAB EMEA AB.
*
* This file is part of Proview.
*
* 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 Proview. If not, see <http://www.gnu.org/licenses/>
*
* Linking Proview statically or dynamically with other modules is
* making a combined work based on Proview. Thus, the terms and
* conditions of the GNU General Public License cover the whole
* combination.
*
* In addition, as a special exception, the copyright holders of
* Proview give you permission to, from the build function in the
* Proview Configurator, combine Proview with modules generated by the
* Proview PLC Editor to a PLC program, regardless of the license
* terms of these modules. You may copy and distribute the resulting
* combined work under the terms of your choice, provided that every
* copy of the combined work is accompanied by a complete copy of
* the source code of Proview (the version used to produce the
* combined work), being distributed under the terms of the GNU
* General Public License plus this exception.
**/
#ifndef ge_item_view_gtk_h
#define ge_item_view_gtk_h
#ifndef ge_h
#include "ge.h"
#endif
#include "cow_wow.h"
class GeItemViewGtk
{
private:
gpointer ge_ctx;
GtkWidget *toplevel_widget;
GtkWidget *tree_widget;
GtkWidget *autosave_button;
public:
GeItemViewGtk( gpointer gectx);
~GeItemViewGtk();
GtkWidget *widget() const;
void update();
void update( char *, int);
char *selected_text() const;
static char *selected_text( GtkWidget *);
static void activate_menu_open( GtkWidget *, gpointer);
static void activate_menu_delete( GtkWidget *, gpointer);
static void activate_tree_widget( GtkTreeView *, GtkTreePath *,
GtkTreeViewColumn *, gpointer);
static gboolean focus_out_tree_widget( GtkWidget *, GdkEvent *, gpointer);
};
#endif
......@@ -302,6 +302,7 @@ void Ge::clear_all()
graph->set_gridsize( 1);
graph->set_grid( 0);
graph->clear_all();
graph->set_modified(0);
subpalette->get_path( &path_cnt, &path);
graph->set_subgraph_path( path_cnt, path);
update();
......@@ -1068,7 +1069,13 @@ void Ge::activate_print()
void Ge::activate_new()
{
clear_all();
if ( graph->is_modified()) {
int rv = create_modal_dialog( "New", "Graph is not saved.\nDo you want to continue?", "Yes", "Cancel", NULL, NULL);
if ( rv == wow_eModalDialogReturn_Button1)
clear_all();
}
else
clear_all();
}
void Ge::activate_save()
......
......@@ -2560,6 +2560,7 @@ static int graph_grow_cb( GlowCtx *ctx, glow_tEvent event)
}
else if ( strcmp( type, ".gif") == 0 ||
strcmp( type, ".jpg") == 0 ||
strcmp( type, ".svg") == 0 ||
strcmp( type, ".png") == 0) {
grow_tObject i1;
char name[80];
......
......@@ -993,6 +993,8 @@ class Graph {
/*! \return 1 if modified, 0 if not modified. */
int is_modified();
void set_modified( int mod) { grow_SetModified( grow->ctx, mod);}
//! Set scantime for slow cycle.
/*! \param time Scantime in seconds. */
void set_scantime( double time) { scan_time = time;};
......
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