Browse Source

Merge pull request #2 from eidheim/master

merge-requests/365/head
Jørgen Lien Sellæg 11 years ago
parent
commit
5c3877cd3c
  1. 21
      juci/CMakeLists.txt
  2. 4
      juci/api.cc
  3. 148
      juci/notebook.cc
  4. 5
      juci/notebook.h
  5. 41
      juci/source.cc
  6. 8
      juci/source.h

21
juci/CMakeLists.txt

@ -1,12 +1,17 @@
cmake_minimum_required (VERSION 2.8.4) cmake_minimum_required (VERSION 2.8.4)
set(project_name juci) set(project_name juci)
set(module juci_to_python_api) set(module juci_to_python_api)
project (${project_name}) project (${project_name})
add_definitions(-DBOOST_LOG_DYN_LINK) add_definitions(-DBOOST_LOG_DYN_LINK)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pthread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -std=c++11 -pthread")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules/") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules/")
#You are of course using Homebrew:
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L/usr/local/opt/gettext/lib -I/usr/local/opt/gettext/include -undefined dynamic_lookup") #TODO: fix this
set(CMAKE_MACOSX_RPATH 1)
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig")
endif()
INCLUDE(FindPkgConfig) INCLUDE(FindPkgConfig)
message("Searcing for libclang") message("Searcing for libclang")
@ -83,6 +88,14 @@ else()
message("Gtkmm link dirs ${GTKMM_LIBRARIES}") message("Gtkmm link dirs ${GTKMM_LIBRARIES}")
message(FATAL_ERROR "The gtkmm libraries are required. Quitting.") message(FATAL_ERROR "The gtkmm libraries are required. Quitting.")
endif() endif()
pkg_check_modules(GTKSVMM gtksourceviewmm-3.0)
if(${GTKSVMM_FOUND})
message("Gtksourceviewmm libraries found. Continuing")
else()
message(FATAL_ERROR "The gtksourceviewmm libraries are required. Quitting.")
endif()
# name of the executable on Windows will be example.exe # name of the executable on Windows will be example.exe
add_executable(${project_name} add_executable(${project_name}
#list of every needed file to create the executable #list of every needed file to create the executable
@ -124,11 +137,13 @@ include_directories(
${Boost_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}
${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}
${GTKMM_INCLUDE_DIRS} ${GTKMM_INCLUDE_DIRS}
${GTKSVMM_INCLUDE_DIRS}
${LCL_INCLUDE_DIRS} ${LCL_INCLUDE_DIRS}
${LIBCLANG_INCLUDE_DIRS} ${LIBCLANG_INCLUDE_DIRS}
) )
link_directories( link_directories(
${GTKMM_LIBRARY_DIRS} ${GTKMM_LIBRARY_DIRS}
${GTKSVMM_LIBRARY_DIRS}
${Boost_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}
${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}
${LCL_LIBRARY_DIRS} ${LCL_LIBRARY_DIRS}
@ -139,6 +154,6 @@ set_target_properties(${module} PROPERTIES PREFIX ""
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/") LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/")
target_link_libraries(${module} ${PYTHON_LIBRARIES} ${Boost_LIBRARIES}) target_link_libraries(${module} ${PYTHON_LIBRARIES} ${Boost_LIBRARIES})
#executable: #executable:
target_link_libraries(${project_name} ${LIVCLANG_LIBRARIES} ${LCL_LIBRARIES} ${GTKMM_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) target_link_libraries(${project_name} ${LIVCLANG_LIBRARIES} ${LCL_LIBRARIES} ${GTKMM_LIBRARIES} ${GTKSVMM_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})

4
juci/api.cc

@ -12,7 +12,9 @@ PluginApi::PluginApi(Menu::Controller& menu_ctl_,
menu_ = &menu_ctl_; menu_ = &menu_ctl_;
notebook_ = &notebook_ctl_; notebook_ = &notebook_ctl_;
DEBUG("Initiating plugins(from plugins.py).."); DEBUG("Initiating plugins(from plugins.py)..");
InitPlugins(); #ifndef __APPLE__
InitPlugins(); //TODO: fix this
#endif
DEBUG("Plugins initiated.."); DEBUG("Plugins initiated..");
} }

148
juci/notebook.cc

@ -1,6 +1,7 @@
#include <fstream> #include <fstream>
#include "notebook.h" #include "notebook.h"
#include "logging.h" #include "logging.h"
#include <regex>
Notebook::Model::Model() { Notebook::Model::Model() {
cc_extension_ = ".cpp"; cc_extension_ = ".cpp";
@ -163,6 +164,77 @@ bool Notebook::Controller:: OnMouseRelease(GdkEventButton* button) {
return false; return false;
} }
bool Notebook::Controller::OnKeyPress(GdkEventKey* key) {
//Indent as in previous line, and indent left after if/else/etc
if(key->keyval==GDK_KEY_Return && key->state==0) {
Gtk::TextIter insert_it = Buffer(text_vec_.at(CurrentPage()))->get_insert()->get_iter();
Gtk::TextIter line_it = Buffer(text_vec_.at(CurrentPage()))->get_iter_at_line(insert_it.get_line());
std::string line(Buffer(text_vec_.at(CurrentPage()))->get_text(line_it, insert_it));
std::smatch sm;
const std::regex bracket_regex("^( *).*\\{ *$");
const std::regex no_bracket_statement_regex("^( *)(if|else|else if|try|catch|while)[^\\}]*$");
const std::regex no_bracket_regex("^( *).*$");
if(std::regex_match(line, sm, bracket_regex)) {
Buffer(text_vec_.at(CurrentPage()))->insert_at_cursor("\n"+sm[1].str()+" ");
}
else if(std::regex_match(line, sm, no_bracket_statement_regex)) {
Buffer(text_vec_.at(CurrentPage()))->insert_at_cursor("\n"+sm[1].str()+" ");
}
else if(std::regex_match(line, sm, no_bracket_regex)) {
Buffer(text_vec_.at(CurrentPage()))->insert_at_cursor("\n"+sm[1].str());
}
CurrentTextView().scroll_to(Buffer(text_vec_.at(CurrentPage()))->get_insert());
return true;
}
//Indent right when clicking tab, no matter where in the line the cursor is. Also works on selected text.
if(key->keyval==GDK_KEY_Tab && key->state==0) {
Gtk::TextIter selection_start, selection_end;
Buffer(text_vec_.at(CurrentPage()))->get_selection_bounds(selection_start, selection_end);
int line_start=selection_start.get_line();
int line_end=selection_end.get_line();
for(int line=line_start;line<=line_end;line++) {
Gtk::TextIter line_it = Buffer(text_vec_.at(CurrentPage()))->get_iter_at_line(line);
Buffer(text_vec_.at(CurrentPage()))->insert(line_it, " ");
}
return true;
}
//Indent left when clicking shift-tab, no matter where in the line the cursor is. Also works on selected text.
else if((key->keyval==GDK_KEY_ISO_Left_Tab || key->keyval==GDK_KEY_Tab) && key->state==GDK_SHIFT_MASK) {
Gtk::TextIter selection_start, selection_end;
Buffer(text_vec_.at(CurrentPage()))->get_selection_bounds(selection_start, selection_end);
int line_start=selection_start.get_line();
int line_end=selection_end.get_line();
for(int line=line_start;line<=line_end;line++) {
Gtk::TextIter line_it = Buffer(text_vec_.at(CurrentPage()))->get_iter_at_line(line);
Gtk::TextIter line_plus_it=line_it;
line_plus_it++;
line_plus_it++;
std::string start(Buffer(text_vec_.at(CurrentPage()))->get_text(line_it, line_plus_it));
if(start==" ")
Buffer(text_vec_.at(CurrentPage()))->erase(line_it, line_plus_it);
}
return true;
}
//Indent left when writing } on a new line
else if(key->keyval==GDK_KEY_braceright) {
Gtk::TextIter insert_it = Buffer(text_vec_.at(CurrentPage()))->get_insert()->get_iter();
Gtk::TextIter line_it = Buffer(text_vec_.at(CurrentPage()))->get_iter_at_line(insert_it.get_line());
Gtk::TextIter line_plus_it=line_it;
line_plus_it++;
line_plus_it++;
std::string start(Buffer(text_vec_.at(CurrentPage()))->get_text(line_it, line_plus_it));
std::string line(Buffer(text_vec_.at(CurrentPage()))->get_text(line_it, insert_it));
for(auto &c: line) {
if(c!=' ')
return false;
}
if(start==" ")
Buffer(text_vec_.at(CurrentPage()))->erase(line_it, line_plus_it);
return false;
}
return false;
}
bool Notebook::Controller::OnKeyRelease(GdkEventKey* key) { bool Notebook::Controller::OnKeyRelease(GdkEventKey* key) {
return GeneratePopup(key->keyval); return GeneratePopup(key->keyval);
} }
@ -265,29 +337,11 @@ bool Notebook::Controller::GeneratePopup(int key_id) {
return true; return true;
} }
bool Notebook::Controller::ScrollEventCallback(GdkEventScroll* scroll_event) {
int page = CurrentPage();
int direction_y = scroll_event->delta_y;
int direction_x = scroll_event->delta_x;
Glib::RefPtr<Gtk::Adjustment> adj =
scrolledtext_vec_.at(page)->
get_vscrollbar()->get_adjustment();
if ( direction_y != 0 ) {
int dir_val = direction_y == -1 ? -model_.scrollvalue_:+model_.scrollvalue_;
adj->set_value(adj->get_value()+dir_val);
text_vec_.at(page)->view().set_vadjustment(adj);
linenumbers_vec_.at(page)->view().set_vadjustment(adj);
}
return true;
}
Notebook::Controller::~Controller() { Notebook::Controller::~Controller() {
INFO("Notebook destructor"); INFO("Notebook destructor");
for (auto &i : text_vec_) delete i; for (auto &i : text_vec_) delete i;
for (auto &i : linenumbers_vec_) delete i;
for (auto &i : editor_vec_) delete i; for (auto &i : editor_vec_) delete i;
for (auto &i : scrolledtext_vec_) delete i; for (auto &i : scrolledtext_vec_) delete i;
for (auto &i : scrolledline_vec_) delete i;
} }
Gtk::Paned& Notebook::Controller::view() { Gtk::Paned& Notebook::Controller::view() {
@ -326,27 +380,15 @@ void Notebook::Controller::OnOpenFile(std::string path) {
Notebook().show_all_children(); Notebook().show_all_children();
Notebook().set_current_page(Pages()-1); Notebook().set_current_page(Pages()-1);
Notebook().set_focus_child(text_vec_.back()->view()); Notebook().set_focus_child(text_vec_.back()->view());
OnBufferChange();
text_vec_.back()->set_is_changed(false); text_vec_.back()->set_is_changed(false);
} }
void Notebook::Controller::OnCreatePage() { void Notebook::Controller::OnCreatePage() {
INFO("Notebook create page"); INFO("Notebook create page");
text_vec_.push_back(new Source::Controller(source_config(), this)); text_vec_.push_back(new Source::Controller(source_config(), this));
linenumbers_vec_.push_back(new Source::Controller(source_config(), this));
scrolledline_vec_.push_back(new Gtk::ScrolledWindow());
scrolledtext_vec_.push_back(new Gtk::ScrolledWindow()); scrolledtext_vec_.push_back(new Gtk::ScrolledWindow());
editor_vec_.push_back(new Gtk::HBox()); editor_vec_.push_back(new Gtk::HBox());
scrolledtext_vec_.back()->add(text_vec_.back()->view()); scrolledtext_vec_.back()->add(text_vec_.back()->view());
scrolledline_vec_.back()->add(linenumbers_vec_.back()->view());
linenumbers_vec_.back()->view().get_buffer()->set_text("1 ");
linenumbers_vec_.back()->view().override_color(Gdk::RGBA("Black"));
linenumbers_vec_.back()->
view().set_justification(Gtk::Justification::JUSTIFY_RIGHT);
scrolledline_vec_.back()->get_vscrollbar()->hide();
linenumbers_vec_.back()->view().set_editable(false);
linenumbers_vec_.back()->view().set_sensitive(false);
editor_vec_.back()->pack_start(*scrolledline_vec_.back(), false, false);
editor_vec_.back()->pack_start(*scrolledtext_vec_.back(), true, true); editor_vec_.back()->pack_start(*scrolledtext_vec_.back(), true, true);
TextViewHandlers(text_vec_.back()->view()); TextViewHandlers(text_vec_.back()->view());
} }
@ -360,14 +402,10 @@ void Notebook::Controller::OnCloseCurrentPage() {
int page = CurrentPage(); int page = CurrentPage();
Notebook().remove_page(page); Notebook().remove_page(page);
delete text_vec_.at(page); delete text_vec_.at(page);
delete linenumbers_vec_.at(page);
delete scrolledtext_vec_.at(page); delete scrolledtext_vec_.at(page);
delete scrolledline_vec_.at(page);
delete editor_vec_.at(page); delete editor_vec_.at(page);
text_vec_.erase(text_vec_.begin()+ page); text_vec_.erase(text_vec_.begin()+ page);
linenumbers_vec_.erase(linenumbers_vec_.begin()+page);
scrolledtext_vec_.erase(scrolledtext_vec_.begin()+page); scrolledtext_vec_.erase(scrolledtext_vec_.begin()+page);
scrolledline_vec_.erase(scrolledline_vec_.begin()+page);
editor_vec_.erase(editor_vec_.begin()+page); editor_vec_.erase(editor_vec_.begin()+page);
} }
} }
@ -455,36 +493,6 @@ void Notebook::Controller::Search(bool forward) {
} }
} }
void Notebook::Controller::OnBufferChange() {
int page = CurrentPage();
int text_nr = Buffer(text_vec_.at(page))->get_line_count();
int line_nr = Buffer(linenumbers_vec_.at(page))->get_line_count();
while (line_nr < text_nr) {
line_nr++;
Buffer(linenumbers_vec_.at(page))->
insert(Buffer(linenumbers_vec_.at(page))->end(),
"\n"+std::to_string(line_nr)+" ");
}
while (line_nr > text_nr) {
Gtk::TextIter iter =
Buffer(linenumbers_vec_.at(page))->get_iter_at_line(line_nr);
iter.backward_char();
line_nr--;
Buffer(linenumbers_vec_.at(page))->
erase(iter,
Buffer(linenumbers_vec_.at(page))->end());
}
if (Buffer(text_vec_.at(page))->get_insert()->get_iter().starts_line() &&
Buffer(text_vec_.at(page))->get_insert()->get_iter().get_line() ==
Buffer(text_vec_.at(page))->end().get_line()) {
GdkEventScroll* scroll = new GdkEventScroll;
scroll->delta_y = 1.0;
scroll->delta_x = 0.0;
ScrollEventCallback(scroll);
delete scroll;
}
text_vec_.at(page)->set_is_changed(true);
}
void Notebook::Controller void Notebook::Controller
::OnDirectoryNavigation(const Gtk::TreeModel::Path& path, ::OnDirectoryNavigation(const Gtk::TreeModel::Path& path,
Gtk::TreeViewColumn* column) { Gtk::TreeViewColumn* column) {
@ -529,10 +537,6 @@ Gtk::Notebook& Notebook::Controller::Notebook() {
void Notebook::Controller::BufferChangeHandler(Glib::RefPtr<Gtk::TextBuffer> void Notebook::Controller::BufferChangeHandler(Glib::RefPtr<Gtk::TextBuffer>
buffer) { buffer) {
buffer->signal_changed().connect(
[this]() {
OnBufferChange();
});
buffer->signal_end_user_action().connect( buffer->signal_end_user_action().connect(
[this]() { [this]() {
//UpdateHistory(); //UpdateHistory();
@ -540,14 +544,12 @@ void Notebook::Controller::BufferChangeHandler(Glib::RefPtr<Gtk::TextBuffer>
} }
void Notebook::Controller::TextViewHandlers(Gtk::TextView& textview) { void Notebook::Controller::TextViewHandlers(Gtk::TextView& textview) {
textview.get_buffer()->signal_changed().connect(
[this]() {
OnBufferChange();
});
textview.signal_button_release_event(). textview.signal_button_release_event().
connect(sigc::mem_fun(*this, &Notebook::Controller::OnMouseRelease), false); connect(sigc::mem_fun(*this, &Notebook::Controller::OnMouseRelease), false);
textview.signal_key_press_event().
connect(sigc::mem_fun(*this, &Notebook::Controller::OnKeyPress), false);
textview.signal_key_release_event(). textview.signal_key_release_event().
connect(sigc::mem_fun(*this, &Notebook::Controller::OnKeyRelease), false); connect(sigc::mem_fun(*this, &Notebook::Controller::OnKeyRelease), false);
} }

5
juci/notebook.h

@ -71,6 +71,7 @@ namespace Notebook {
void Search(bool forward); void Search(bool forward);
Source::Config& source_config() { return source_config_; } Source::Config& source_config() { return source_config_; }
bool OnMouseRelease(GdkEventButton* button); bool OnMouseRelease(GdkEventButton* button);
bool OnKeyPress(GdkEventKey* key);
bool OnKeyRelease(GdkEventKey* key); bool OnKeyRelease(GdkEventKey* key);
std::string OnSaveFileAs(); std::string OnSaveFileAs();
bool LegalExtension(std::string extension); bool LegalExtension(std::string extension);
@ -101,8 +102,8 @@ namespace Notebook {
bool is_new_file_; bool is_new_file_;
Entry::Controller entry_; Entry::Controller entry_;
std::vector<Source::Controller*> text_vec_, linenumbers_vec_; std::vector<Source::Controller*> text_vec_;
std::vector<Gtk::ScrolledWindow*> scrolledtext_vec_, scrolledline_vec_; std::vector<Gtk::ScrolledWindow*> scrolledtext_vec_;
std::vector<Gtk::HBox*> editor_vec_; std::vector<Gtk::HBox*> editor_vec_;
std::list<Gtk::TargetEntry> listTargets_; std::list<Gtk::TargetEntry> listTargets_;
Gtk::TextIter search_match_end_; Gtk::TextIter search_match_end_;

41
juci/source.cc

@ -5,6 +5,7 @@
#include <boost/timer/timer.hpp> #include <boost/timer/timer.hpp>
#include "notebook.h" #include "notebook.h"
#include "logging.h" #include "logging.h"
#include <algorithm>
Source::Location:: Source::Location::
Location(int line_number, int column_offset) : Location(int line_number, int column_offset) :
@ -28,6 +29,9 @@ Range(const Source::Range &org) :
////////////// //////////////
Source::View::View() { Source::View::View() {
override_font(Pango::FontDescription("Monospace")); override_font(Pango::FontDescription("Monospace"));
set_show_line_numbers(true);
set_highlight_current_line(true);
set_smart_home_end(Gsv::SMART_HOME_END_ALWAYS);
} }
string Source::View::GetLine(const Gtk::TextIter &begin) { string Source::View::GetLine(const Gtk::TextIter &begin) {
@ -114,7 +118,7 @@ InitSyntaxHighlighting(const std::string &filepath,
int end_offset, int end_offset,
clang::Index *index) { clang::Index *index) {
set_project_path(project_path); set_project_path(project_path);
std::vector<const char*> arguments = get_compilation_commands(); std::vector<string> arguments = get_compilation_commands();
tu_ = clang::TranslationUnit(index, tu_ = clang::TranslationUnit(index,
filepath, filepath,
arguments, arguments,
@ -201,16 +205,16 @@ const Source::Config& Source::Model::config() const {
return config_; return config_;
} }
std::vector<const char*> Source::Model:: std::vector<std::string> Source::Model::
get_compilation_commands() { get_compilation_commands() {
clang::CompilationDatabase db(project_path()+"/"); clang::CompilationDatabase db(project_path()+"/");
clang::CompileCommands commands(file_path(), &db); clang::CompileCommands commands(file_path(), &db);
std::vector<clang::CompileCommand> cmds = commands.get_commands(); std::vector<clang::CompileCommand> cmds = commands.get_commands();
std::vector<const char*> arguments; std::vector<std::string> arguments;
for (auto &i : cmds) { for (auto &i : cmds) {
std::vector<std::string> lol = i.get_command_as_args(); std::vector<std::string> lol = i.get_command_as_args();
for (int a = 1; a < lol.size()-4; a++) { for (int a = 1; a < lol.size()-4; a++) {
arguments.emplace_back(lol[a].c_str()); arguments.emplace_back(lol[a]);
} }
} }
return arguments; return arguments;
@ -353,8 +357,17 @@ void Source::Controller::OnOpenFile(const string &filepath) {
view().OnUpdateSyntax(model().ExtractTokens(start_offset, end_offset), view().OnUpdateSyntax(model().ExtractTokens(start_offset, end_offset),
model().config()); model().config());
//OnUpdateSyntax must happen in main thread, so the parse-thread
//sends a signal to the main thread that it is to call the following function:
parsing_done.connect([this](){
INFO("Updating syntax");
view().
OnUpdateSyntax(model().ExtractTokens(0, buffer()->get_text().size()),
model().config());
INFO("Syntax updated");
});
buffer()->signal_end_user_action().connect([this]() { buffer()->signal_end_user_action().connect([this]() {
if (!go) {
std::thread parse([this]() { std::thread parse([this]() {
if (parsing.try_lock()) { if (parsing.try_lock()) {
INFO("Starting parsing"); INFO("Starting parsing");
@ -365,31 +378,15 @@ void Source::Controller::OnOpenFile(const string &filepath) {
buffers[model().file_path()] = raw; buffers[model().file_path()] = raw;
if (model().ReParse(buffers) == 0 && if (model().ReParse(buffers) == 0 &&
raw == buffer()->get_text().raw()) { raw == buffer()->get_text().raw()) {
syntax.lock();
go = true;
syntax.unlock();
break; break;
} }
} }
parsing.unlock(); parsing.unlock();
parsing_done();
INFO("Parsing completed"); INFO("Parsing completed");
} }
}); });
parse.detach(); parse.detach();
}
});
buffer()->signal_begin_user_action().connect([this]() {
if (go) {
syntax.lock();
INFO("Updating syntax");
view().
OnUpdateSyntax(model().ExtractTokens(0, buffer()->get_text().size()),
model().config());
go = false;
INFO("Syntax updated");
syntax.unlock();
}
}); });
} }
} }

8
juci/source.h

@ -8,6 +8,7 @@
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include "gtksourceviewmm.h"
namespace Notebook { namespace Notebook {
class Controller; class Controller;
@ -60,7 +61,7 @@ namespace Source {
int kind_; int kind_;
}; };
class View : public Gtk::TextView { class View : public Gsv::View {
public: public:
View(); View();
virtual ~View() { } virtual ~View() { }
@ -135,7 +136,7 @@ namespace Source {
int token_kind); int token_kind);
void HighlightCursor(clang::Token *token, void HighlightCursor(clang::Token *token,
std::vector<Range> *source_ranges); std::vector<Range> *source_ranges);
std::vector<const char*> get_compilation_commands(); std::vector<std::string> get_compilation_commands();
}; };
class Controller { class Controller {
@ -162,9 +163,8 @@ namespace Source {
private: private:
void OnLineEdit(); void OnLineEdit();
void OnSaveFile(); void OnSaveFile();
std::mutex syntax;
std::mutex parsing; std::mutex parsing;
bool go = false; Glib::Dispatcher parsing_done;
bool is_saved_ = false; bool is_saved_ = false;
bool is_changed_ = false; bool is_changed_ = false;

Loading…
Cancel
Save