From 95733e9c61756fd8033e468f6dc4f31c3603998a Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Wed, 6 Jun 2018 22:54:34 +0200 Subject: [PATCH 01/41] update juce version (5.2.1) --- .gitmodules | 2 +- Client/Source/KiwiApp_Application/KiwiApp_Console.cpp | 2 +- Client/Source/KiwiApp_Application/KiwiApp_Instance.h | 2 +- Client/Source/KiwiApp_Components/KiwiApp_FormComponent.h | 8 ++++++-- Client/Source/KiwiApp_Components/KiwiApp_Window.h | 2 +- Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp | 4 +++- Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h | 4 +++- .../Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp | 7 ++++--- Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp | 6 +++--- Modules/KiwiServer/KiwiServer_Server.cpp | 2 +- ThirdParty/Juce | 2 +- 11 files changed, 25 insertions(+), 16 deletions(-) diff --git a/.gitmodules b/.gitmodules index ba43d85c..298c506a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "ThirdParty/Juce"] path = ThirdParty/Juce - url = https://github.com/julianstorer/JUCE.git + url=https://github.com/WeAreROLI/JUCE.git [submodule "ThirdParty/concurrentqueue"] path = ThirdParty/concurrentqueue url = https://github.com/cameron314/concurrentqueue.git diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp b/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp index 5cae1fe5..fd70a687 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp @@ -95,7 +95,7 @@ namespace kiwi auto msg = history->get(selection[i]).first; if(msg && !msg->text.empty()) { - text += msg->text + "\n"; + text += juce::String(msg->text + "\n"); } } diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Instance.h b/Client/Source/KiwiApp_Application/KiwiApp_Instance.h index e97319b9..6f37b9ba 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_Instance.h +++ b/Client/Source/KiwiApp_Application/KiwiApp_Instance.h @@ -24,7 +24,7 @@ #include #include -#include +#include #include "flip/Document.h" diff --git a/Client/Source/KiwiApp_Components/KiwiApp_FormComponent.h b/Client/Source/KiwiApp_Components/KiwiApp_FormComponent.h index 14cee3c9..337b5cc3 100644 --- a/Client/Source/KiwiApp_Components/KiwiApp_FormComponent.h +++ b/Client/Source/KiwiApp_Components/KiwiApp_FormComponent.h @@ -29,7 +29,9 @@ namespace kiwi // ALERT BOX // // ================================================================================ // - class AlertBox : public juce::Component, private juce::ButtonListener + class AlertBox + : public juce::Component + , private juce::Button::Listener { public: // methods @@ -72,7 +74,9 @@ namespace kiwi // FORM COMPONENT // // ================================================================================ // - class FormComponent : public juce::Component, private juce::ButtonListener + class FormComponent + : public juce::Component + , private juce::Button::Listener { public: // methods diff --git a/Client/Source/KiwiApp_Components/KiwiApp_Window.h b/Client/Source/KiwiApp_Components/KiwiApp_Window.h index ba29f92e..558b811d 100644 --- a/Client/Source/KiwiApp_Components/KiwiApp_Window.h +++ b/Client/Source/KiwiApp_Components/KiwiApp_Window.h @@ -46,7 +46,7 @@ namespace kiwi std::unique_ptr content, bool resizable = false, bool is_main_window = true, - juce::String settings_name = juce::String::empty, + juce::String settings_name = "", bool add_menubar = false); //! @brief Window destructor. Called whenever buttonPressed is called. diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp index 3d4485af..f1ef5c6f 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp @@ -124,7 +124,9 @@ namespace kiwi g.fillPath(p, p.getTransformToScaleToFit(plusRect.reduced(2, plusRect.getHeight() / 4), true)); } - void LookAndFeel::drawTableHeaderColumn(juce::Graphics& g, juce::String const& columnName, + void LookAndFeel::drawTableHeaderColumn(juce::Graphics& g, + juce::TableHeaderComponent&, + juce::String const& columnName, int /*columnId*/, int width, int height, bool isMouseOver, bool isMouseDown, diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h index 610f19e2..3c535e77 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h @@ -48,7 +48,9 @@ namespace kiwi void drawTableHeaderBackground(juce::Graphics& g, juce::TableHeaderComponent& header) override; //! @brief Overriden to draw a custom Table header column. - void drawTableHeaderColumn(juce::Graphics& g, juce::String const& columnName, + void drawTableHeaderColumn(juce::Graphics& g, + juce::TableHeaderComponent&, + juce::String const& columnName, int /*columnId*/, int width, int height, bool isMouseOver, bool isMouseDown, diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp index 8450e715..e9371244 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp @@ -383,9 +383,10 @@ namespace kiwi // PATCHER VIEW WINDOW // // ================================================================================ // - PatcherViewWindow::PatcherViewWindow(PatcherManager& manager, PatcherView& patcherview) : - Window("Untitled", nullptr, true, true, juce::String::empty, !KiwiApp::isMacOSX()), - m_patcher_component(patcherview) + PatcherViewWindow::PatcherViewWindow(PatcherManager& manager, + PatcherView& patcherview) + : Window("Untitled", nullptr, true, true, "", !KiwiApp::isMacOSX()) + , m_patcher_component(patcherview) { // Todo: Add size infos to the Patcher Model setSize(600, 500); diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp index 4c9d4901..fff873d8 100755 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp @@ -135,7 +135,7 @@ namespace kiwi { for(int y = (origin.getY() % grid_size); y < clip_bounds.getBottom(); y += grid_size) { - g.setPixel(x, y); + g.fillRect(x, y, 1, 1); } } } @@ -1863,7 +1863,7 @@ namespace kiwi { juce::String label = TRANS("Undo"); const bool hasUndo = canUndo(); - if(hasUndo) { label += ' ' + getUndoLabel(); } + if(hasUndo) { label += juce::String(" ") + getUndoLabel(); } result.setInfo(label, TRANS("Undo last action"), CommandCategories::general, 0); result.addDefaultKeypress('z', juce::ModifierKeys::commandModifier); @@ -1874,7 +1874,7 @@ namespace kiwi { juce::String label = TRANS("Redo"); const bool hasRedo = canRedo(); - if(hasRedo) { label += ' ' + getRedoLabel(); } + if(hasRedo) { label += juce::String(" ") + getRedoLabel(); } result.setInfo(label, TRANS("Redo action"), CommandCategories::general, 0); result.addDefaultKeypress('z', juce::ModifierKeys::commandModifier | juce::ModifierKeys::shiftModifier); diff --git a/Modules/KiwiServer/KiwiServer_Server.cpp b/Modules/KiwiServer/KiwiServer_Server.cpp index 54d0e0e3..925efa96 100644 --- a/Modules/KiwiServer/KiwiServer_Server.cpp +++ b/Modules/KiwiServer/KiwiServer_Server.cpp @@ -471,7 +471,7 @@ namespace kiwi } m_logger.log("saving session : " + hexadecimal_convert(m_identifier) - + " in file : " + m_backend_file.getFileName()); + + " in file : " + m_backend_file.getFileName().toStdString()); if (!save()) { diff --git a/ThirdParty/Juce b/ThirdParty/Juce index ad915465..06c3674e 160000 --- a/ThirdParty/Juce +++ b/ThirdParty/Juce @@ -1 +1 @@ -Subproject commit ad9154657b2c144feeeb38bef900d2163910459e +Subproject commit 06c3674e510f991d88f881203aedd85a60fbced9 From 8dba54bb03ddb751df6493a3ff2b147cff52d552 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 9 Jun 2018 22:05:58 +0200 Subject: [PATCH 02/41] server app - relative path support for config file --- Server/Source/Main.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Server/Source/Main.cpp b/Server/Source/Main.cpp index ed2e0fb4..f12b4402 100644 --- a/Server/Source/Main.cpp +++ b/Server/Source/Main.cpp @@ -37,6 +37,8 @@ void showHelp() std::cout << "Usage:\n"; std::cout << " -h shows this help message. \n"; std::cout << " -f set the json configuration file to use (needed). \n"; + std::cout << '\n'; + std::cout << "ex: ./Server -f ./config/prod.json" << std::endl; } void on_interupt(int signal) @@ -66,11 +68,25 @@ int main(int argc, char const* argv[]) return 0; } - juce::File configuration_file("./" + cl_parser.getOption("-f")); + juce::File::getSpecialLocation(juce::File::SpecialLocationType::currentApplicationFile) + .setAsCurrentWorkingDirectory(); + const std::string config_filepath = cl_parser.getOption("-f"); + + if(config_filepath.empty()) + { + std::cerr << "Error: Server need a configuration file:\n" << std::endl; + showHelp(); + return 0; + } + + juce::File configuration_file(config_filepath); if(!configuration_file.exists()) { - std::cerr << "Error: Config file does not exist or is unspecified.." << std::endl; + std::cerr << "Error: Config file: \"" + << configuration_file.getFullPathName() + << "\" not found !" << std::endl; + showHelp(); return 0; } From 899b22f33585627f19836e3f0856b090b399322e Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 9 Jun 2018 22:21:30 +0200 Subject: [PATCH 03/41] add kiwi-node-server as submodule --- .gitmodules | 3 +++ ThirdParty/kiwi-node-server | 1 + 2 files changed, 4 insertions(+) create mode 160000 ThirdParty/kiwi-node-server diff --git a/.gitmodules b/.gitmodules index 298c506a..efbaad35 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "ThirdParty/Beast"] path = ThirdParty/Beast url = https://github.com/boostorg/beast.git +[submodule "ThirdParty/kiwi-node-server"] + path = ThirdParty/kiwi-node-server + url = https://github.com/Musicoll/kiwi-node-server.git diff --git a/ThirdParty/kiwi-node-server b/ThirdParty/kiwi-node-server new file mode 160000 index 00000000..5aa126ef --- /dev/null +++ b/ThirdParty/kiwi-node-server @@ -0,0 +1 @@ +Subproject commit 5aa126ef651fbad16ec43492aaa9c6e9628a4e7f From 8e231e3cc3f10c73e293cd2e11bedcb23af22223 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sun, 10 Jun 2018 18:41:27 +0200 Subject: [PATCH 04/41] GUI improvements: - All boxes can now be resized. - Fix several text related gui issues. - Changed the look of the textbox objects. --- .../KiwiApp_Objects/KiwiApp_ClassicView.cpp | 81 +++++---- .../KiwiApp_Objects/KiwiApp_ClassicView.h | 14 +- .../KiwiApp_Objects/KiwiApp_CommentView.cpp | 112 +++++++----- .../KiwiApp_Objects/KiwiApp_CommentView.h | 24 ++- .../KiwiApp_EditableObjectView.cpp | 117 ++++++++++-- .../KiwiApp_EditableObjectView.h | 132 +++++++++----- .../KiwiApp_Objects/KiwiApp_MessageView.cpp | 141 +++++++------- .../KiwiApp_Objects/KiwiApp_MessageView.h | 31 ++-- .../KiwiApp_NumberViewBase.cpp | 4 + .../KiwiApp_Objects/KiwiApp_NumberViewBase.h | 3 + .../KiwiApp_Objects/KiwiApp_ObjectFrame.cpp | 106 ++++++----- .../KiwiApp_Objects/KiwiApp_ObjectFrame.h | 172 +++++++++--------- .../KiwiApp_Objects/KiwiApp_ObjectView.cpp | 29 ++- .../KiwiApp_Objects/KiwiApp_ObjectView.h | 19 +- .../KiwiApp_Patcher/KiwiApp_PatcherView.cpp | 59 +++--- .../KiwiApp_PatcherViewMouseHandler.cpp | 80 ++++---- .../KiwiModel_Objects/KiwiModel_Comment.cpp | 18 +- 17 files changed, 683 insertions(+), 459 deletions(-) diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp index bfa6e72c..ed17e5ab 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp @@ -34,14 +34,17 @@ namespace kiwi ClassicView::ClassicView(model::Object & object_model) : EditableObjectView(object_model) { - juce::Label & label = getLabel(); - - label.setText(object_model.getText(), juce::NotificationType::dontSendNotification); - - juce::Colour bg_colour = object_model.getName() == "errorbox" ? - findColour(ObjectView::ColourIds::Error).withAlpha(0.4f) : - findColour(ObjectView::ColourIds::Background); - + juce::Label & label = getLabel(); + + const auto object_text = object_model.getText(); + label.setText(object_text, juce::NotificationType::dontSendNotification); + + juce::Colour bg_colour = (object_model.getName() == "errorbox" ? + findColour(ObjectView::ColourIds::Error) : + findColour(ObjectView::ColourIds::Background)); + + setColour(ObjectView::ColourIds::Background, bg_colour); + setColour(ObjectView::ColourIds::Outline, juce::Colours::transparentBlack); label.setColour(juce::Label::backgroundColourId, bg_colour); label.setColour(juce::Label::backgroundWhenEditingColourId, bg_colour); @@ -52,43 +55,41 @@ namespace kiwi } ClassicView::~ClassicView() - { + {} + + void ClassicView::validateSize(int& new_width, int& new_height) + { + new_width = std::max(new_width, getMinWidth()); + const auto text_bounds = getTextBoundingBox(getLabel().getText(), new_width); + new_height = std::max(text_bounds.getHeight(), getMinHeight()); } void ClassicView::paintOverChildren (juce::Graphics& g) - { - g.setColour (findColour (ObjectView::ColourIds::Outline)); - - drawOutline(g); - } - - void ClassicView::resized() - { - getLabel().setBounds(getLocalBounds()); + { + g.setColour(findColour(EditableObjectView::ColourIds::Background).contrasting(0.4)); + g.drawLine(0, 0, getWidth(), 0, 6); + g.drawLine(0, getHeight(), getWidth(), getHeight(), 6); } void ClassicView::textChanged() - { - } + {} void ClassicView::textEditorTextChanged(juce::TextEditor& editor) { - const int text_width = editor.getFont().getStringWidth(editor.getText()); - - if(editor.getWidth() < text_width + 16) - { - setSize(text_width + 16, getHeight()); - } + const auto text = editor.getText(); + + auto single_line_text_width = editor.getFont().getStringWidthFloat(text) + getPadding() * 2 + 10; + auto prev_width = getWidth(); + auto text_bounds = getTextBoundingBox(text, single_line_text_width); + + setSize(std::max(prev_width, single_line_text_width), + std::max(std::min(text_bounds.getHeight(), getHeight()), getMinHeight())); } juce::TextEditor* ClassicView::createdTextEditor() { juce::TextEditor * editor = new SuggestEditor(model::Factory::getNames()); - editor->setBounds(getLocalBounds()); - editor->setBorder(juce::BorderSize(0)); - - editor->setColour(juce::TextEditor::ColourIds::textColourId, getLabel().findColour(juce::Label::textWhenEditingColourId)); @@ -98,18 +99,24 @@ namespace kiwi editor->setColour(juce::TextEditor::highlightColourId, findColour(ObjectView::ColourIds::Highlight, true).withAlpha(0.4f)); - editor->setColour(juce::TextEditor::outlineColourId, juce::Colours::transparentWhite); editor->setColour(juce::TextEditor::focusedOutlineColourId, juce::Colours::transparentWhite); - - editor->setScrollbarsShown(false); - editor->setScrollToShowCursor(true); - editor->setReturnKeyStartsNewLine(false); - editor->setMultiLine(true, false); - + + editor->setBounds(getLocalBounds()); + editor->setIndents(getPadding(), getPadding()); + editor->setBorder(juce::BorderSize(0)); + editor->setFont(getLabel().getFont()); + editor->setJustification(getLabel().getJustificationType()); + + editor->setScrollbarsShown(false); + editor->setScrollToShowCursor(true); + editor->setReturnKeyStartsNewLine(false); + editor->setMultiLine(true, true); + editor->setInterceptsMouseClicks(true, false); + editor->addListener(this); return editor; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h index de30f197..0a7fe50a 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h @@ -50,24 +50,24 @@ namespace kiwi ~ClassicView(); private: // methods + + //! @brief Validate the new width and height for the box + void validateSize(int& new_width, int& new_height) override; //! @brief Constructs the label's text editor. //! @brief Overrides EditableObjectView::createTextEditor. - juce::TextEditor* createdTextEditor() override final; + juce::TextEditor* createdTextEditor() override; //! @brief Called when label text changed. //! @details Overrides EditableObjectView::textChanged. - void textChanged() override final; - - //! @brief Called when the object is resized. - void resized() override final; + void textChanged() override; //! @brief Called when the text is being typed. //! @details Used to resize in order to keep text visible. - void textEditorTextChanged(juce::TextEditor& editor) override final; + void textEditorTextChanged(juce::TextEditor& editor) override; //! @brief Paints elements over the text editor. - void paintOverChildren (juce::Graphics& g) override final; + void paintOverChildren (juce::Graphics& g) override; private: // deleted methods diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp index 5fc49a66..4f5d7355 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp @@ -40,76 +40,94 @@ namespace kiwi { return std::make_unique(object_model); } - CommentView::CommentView(model::Object & object_model) : - EditableObjectView(object_model) + CommentView::CommentView(model::Object & object_model) + : EditableObjectView(object_model) { setColour(ObjectView::ColourIds::Background, juce::Colours::transparentWhite); - juce::Label & label = getLabel(); + juce::Label& label = getLabel(); - label.setJustificationType(juce::Justification::topLeft); + const auto comment_text = object_model.getAttribute("text")[0].getString(); + label.setText(comment_text, juce::NotificationType::dontSendNotification); - label.setText(object_model.getAttribute("text")[0].getString(), - juce::NotificationType::dontSendNotification); + label.setColour(juce::Label::backgroundColourId, + findColour(ObjectView::ColourIds::Background)); - label.setColour(juce::Label::backgroundColourId, findColour(ObjectView::ColourIds::Background)); - label.setColour(juce::Label::backgroundWhenEditingColourId, - findColour(ObjectView::ColourIds::Background)); - label.setColour(juce::Label::textColourId, findColour(ObjectView::ColourIds::Text)); - label.setColour(juce::Label::textWhenEditingColourId, findColour(ObjectView::ColourIds::Text)); + label.setColour(juce::Label::backgroundWhenEditingColourId, + findColour(ObjectView::ColourIds::Background)); + + label.setColour(juce::Label::textColourId, + findColour(ObjectView::ColourIds::Text).brighter(0.4)); + + label.setColour(juce::Label::textWhenEditingColourId, + findColour(ObjectView::ColourIds::Text)); addAndMakeVisible(label); } CommentView::~CommentView() - { + {} + + void CommentView::lockStatusChanged(bool is_locked) + { + m_patcher_view_locked = is_locked; + repaint(); } void CommentView::paintOverChildren (juce::Graphics& g) - { - g.setColour (findColour (ObjectView::ColourIds::Outline)); - - drawOutline(g); - } - - void CommentView::resized() - { - getLabel().setBounds(getLocalBounds()); + { + if(!m_patcher_view_locked) + { + // dashed outline + juce::Path path; + path.addRectangle(getLocalBounds()); + const juce::PathStrokeType path_stroke(1.0); + float const dashed_length[2] {2.f, 2.f}; + path_stroke.createDashedStroke(path, path, dashed_length, 2); + + g.setColour(findColour (ObjectView::ColourIds::Outline).brighter(0.8).withAlpha(0.8f)); + g.strokePath(path, path_stroke); + } + } + + void CommentView::validateSize(int& new_width, int& new_height) + { + new_width = std::max(new_width, getMinWidth()); + const auto text_bounds = getTextBoundingBox(getLabel().getText(), new_width); + new_height = std::max(text_bounds.getHeight(), getMinHeight()); } void CommentView::textEditorTextChanged(juce::TextEditor& editor) - { - setSize(std::max(editor.getTextWidth() + 16, 40), editor.getTextHeight()); + { + setSize(std::max(getWidth(), getMinWidth()), + std::max(editor.getTextHeight() + 2, getMinHeight())); } void CommentView::attributeChanged(std::string const& name, tool::Parameter const& param) - { - if (name == "text") - { - getLabel().setText(param[0].getString(), juce::NotificationType::dontSendNotification); + { + static const std::string text_param = "text"; + + if (name == text_param) + { + const auto new_text = param[0].getString(); + getLabel().setText(new_text, juce::NotificationType::dontSendNotification); + + const auto width = getWidth(); + const auto text_bounds = getTextBoundingBox(new_text, width); + setSize(std::max(width, getMinWidth()), + std::max(text_bounds.getHeight(), getMinHeight())); } } void CommentView::textChanged() - { - model::Object & model = getModel(); - - model.setWidth(getWidth()); - - model.setHeight(getHeight()); - - setAttribute("text", tool::Parameter(tool::Parameter::Type::String, - {getLabel().getText().toStdString()})); + { + const auto new_text = getLabel().getText().toStdString(); + setAttribute("text", tool::Parameter(tool::Parameter::Type::String, {new_text})); } juce::TextEditor* CommentView::createdTextEditor() { - juce::TextEditor * editor = new juce::TextEditor(); - - editor->setBounds(getLocalBounds()); - editor->setBorder(juce::BorderSize(0)); - editor->setFont(getLabel().getFont()); - + auto* editor = new juce::TextEditor(); editor->setColour(juce::TextEditor::ColourIds::textColourId, getLabel().findColour(juce::Label::textWhenEditingColourId)); @@ -120,12 +138,17 @@ namespace kiwi { editor->setColour(juce::TextEditor::highlightColourId, findColour(ObjectView::ColourIds::Highlight, true).withAlpha(0.4f)); - editor->setColour(juce::TextEditor::outlineColourId, juce::Colours::transparentWhite); editor->setColour(juce::TextEditor::focusedOutlineColourId, - juce::Colours::transparentWhite); + juce::Colours::transparentWhite); + + editor->setBounds(getLocalBounds()); + editor->setIndents(getPadding(), getPadding()); + editor->setBorder(juce::BorderSize(0)); + editor->setFont(getFont()); + editor->setJustification(getLabel().getJustificationType()); editor->setScrollbarsShown(false); editor->setScrollToShowCursor(true); @@ -134,7 +157,6 @@ namespace kiwi { editor->setInterceptsMouseClicks(true, false); editor->addListener(this); - return editor; } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.h index 47f5d28c..13091b75 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.h @@ -33,9 +33,10 @@ namespace kiwi { // COMMENT VIEW // // ================================================================================ // - //! @brief The view of any textual kiwi object. - class CommentView : public EditableObjectView, - public juce::TextEditor::Listener + //! @brief The *comment* object view. + class CommentView + : public EditableObjectView + , public juce::TextEditor::Listener { public: // methods @@ -51,10 +52,13 @@ namespace kiwi { //! @brief Destructor. ~CommentView(); - private: // methods - - //! @brief Called when the object is resized. - void resized() override final; + private: // methods + + //! @brief Validate the new width and height for the box + void validateSize(int& new_width, int& new_height) override; + + //! @brief Called every time a patcher is locked or unlocked. + void lockStatusChanged(bool is_locked) override; //! @brief Called when the text is being typed. //! @details Used to resize in order to keep text visible. @@ -72,7 +76,11 @@ namespace kiwi { //! @brief Constructs the label's text editor. //! @details Overrides EditableOjectView::createTextEditor. - juce::TextEditor* createdTextEditor() override final; + juce::TextEditor* createdTextEditor() override final; + + private: // variables + + bool m_patcher_view_locked {false}; private: // deleted methods diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp index 8a632287..19ecc2ce 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp @@ -27,17 +27,19 @@ namespace kiwi { // EDITABLE OBJECT VIEW // // ================================================================================ // - EditableObjectView::EditableObjectView(model::Object & object_model) : - ObjectView(object_model), - m_label(*this), - m_editable(true), - m_listeners() - { + EditableObjectView::EditableObjectView(model::Object & object_model) + : ObjectView(object_model) + , m_label(*this) + { + m_label.setFont(14); + m_label.setMinimumHorizontalScale(0); + m_label.setInterceptsMouseClicks(false, false); + m_label.setBorderSize(juce::BorderSize(getPadding())); + m_label.setJustificationType(juce::Justification::topLeft); } EditableObjectView::~EditableObjectView() - { - } + {} void EditableObjectView::setEditable(bool editable) { @@ -50,13 +52,76 @@ namespace kiwi { { m_label.showEditor(); } + } + + void EditableObjectView::setText(juce::String const& new_text) + { + m_label.setText(new_text, juce::NotificationType::dontSendNotification); + } + + juce::String EditableObjectView::getText() const + { + return m_label.getText(); + } + + void EditableObjectView::setFont(juce::Font font) + { + m_label.setFont(font); + } + + juce::Font EditableObjectView::getFont() const + { + return m_label.getFont(); + } + + void EditableObjectView::setPadding(int padding) + { + m_padding = std::min(0, padding); + } + + int EditableObjectView::getPadding() const + { + return m_padding; + } + + int EditableObjectView::getMinHeight() const + { + return getFont().getHeight() + m_padding * 2; + } + + int EditableObjectView::getMinWidth() const + { + return getFont().getHeight() + m_padding * 2; + } + + void EditableObjectView::resized() + { + m_label.setBounds(getLocalBounds()); + } + + juce::Rectangle EditableObjectView::getTextBoundingBox(juce::String const& text, + float max_width) const + { + juce::GlyphArrangement arr; + const int padding = getPadding(); + const int side_padding = padding * 2; + const float max_line_width = max_width - side_padding; + const auto font { getFont() }; + arr.addJustifiedText (font, text, + padding, padding, + max_line_width, + juce::Justification::topLeft); + + const auto bounds = arr.getBoundingBox(0, -1, true); + return bounds.withHeight(bounds.getHeight() + side_padding); } void EditableObjectView::labelTextChanged (juce::Label* label) { textChanged(); - m_listeners.call(&EditableObjectView::Listener::textChanged, m_label.getText().toStdString()); + m_listeners.call(&EditableObjectView::Listener::textChanged, + m_label.getText().toStdString()); } juce::Label & EditableObjectView::getLabel() @@ -86,15 +151,34 @@ namespace kiwi { void EditableObjectView::removeListener(Listener& listener) { m_listeners.remove(listener); + } + + void EditableObjectView::paint(juce::Graphics& g) + { + g.fillAll(findColour(EditableObjectView::ColourIds::Background)); + } + + void EditableObjectView::drawLabel(juce::Graphics& g) + { + if(!m_label.isBeingEdited()) + { + g.setColour (m_label.findColour(juce::Label::ColourIds::textColourId)); + + g.setFont(getFont()); + g.drawMultiLineText(m_label.getText(), + getPadding(), + getFont().getHeight(), + m_label.getWidth() - getPadding() * 2); + } } // ================================================================================ // // LABEL // // ================================================================================ // - EditableObjectView::Label::Label(EditableObjectView & object_view): - juce::Label(), - m_object_view(object_view) + EditableObjectView::Label::Label(EditableObjectView & object_view) + : juce::Label() + , m_object_view(object_view) { addListener(&m_object_view); } @@ -104,9 +188,14 @@ namespace kiwi { removeListener(&m_object_view); } - juce::TextEditor * EditableObjectView::Label::createEditorComponent() + juce::TextEditor* EditableObjectView::Label::createEditorComponent() { return m_object_view.createdTextEditor(); - } + } + + void EditableObjectView::Label::paint(juce::Graphics& g) + { + m_object_view.drawLabel(g); + } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.h index 7353cc31..dda39863 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.h @@ -32,44 +32,12 @@ namespace kiwi // ================================================================================ // //! @brief Abstract class for object's views that can be edited in mode unlock. - class EditableObjectView : public ObjectView, public juce::Label::Listener - { - public: // classes - - struct Listener - { - //! @brief Destructor. - virtual ~Listener() = default; - - //! @brief Called when the text has been edited and return key was pressed. - virtual void textChanged(std::string const& new_text) = 0; - - //! @brief Called when the classic view ends its edition. - virtual void editorHidden() = 0; - - //! @brief Called when the classic view enters its edition mode. - virtual void editorShown() = 0; - }; - - private: // classes - - class Label : public juce::Label - { - public: // methods - - //! @brief Constructor. - Label(EditableObjectView & object_view); - - //! @brief Destructor. - ~Label(); - - //! @brief Called to create the text editor once edit is called. - juce::TextEditor* createEditorComponent() override final; - - private: // members - - EditableObjectView & m_object_view; - }; + class EditableObjectView + : public ObjectView + , public juce::Label::Listener + { + public: // classes + struct Listener; public: // methods @@ -87,18 +55,80 @@ namespace kiwi void removeListener(Listener& listener); //! @brief Edits the label. - void edit(); + void edit(); + + //! @brief Set a new text. + void setText(juce::String const& new_text); + + //! @brief Get the text. + juce::String getText() const; + + //! @brief Set a new font. + void setFont(juce::Font font); + + //! @brief Get the current font used by the box. + juce::Font getFont() const; + + //! @brief Set a padding value. + void setPadding(int padding); + + //! @brief Get the current padding value. + int getPadding() const; + + //! @brief Get the minimum height. + //! @details The minimum height is (font_height + 2 * padding) + int getMinHeight() const; + + //! @brief Get the minimum width. + //! @details The minimum width is (font_height + 2 * padding) + int getMinWidth() const; + + private: // classes + + class Label : public juce::Label + { + public: // methods + + //! @brief Constructor. + Label(EditableObjectView & object_view); + + //! @brief Destructor. + ~Label(); + + //! @brief Called to create the text editor once edit is called. + juce::TextEditor* createEditorComponent() override final; + + void paint(juce::Graphics& g) override; + + private: // members + + EditableObjectView & m_object_view; + }; protected: // methods + + //! @brief Called when the object is resized. + //! @details Default implementation apply local bounds to the label. + virtual void resized() override; //! @brief Returns the label created by the editable object. juce::Label & getLabel(); //! @brief Sets the editable object view as editable or not. //! @details Editable object is editable by default. - void setEditable(bool editable); + void setEditable(bool editable); + + //! @brief Try to find the bounding bow of a text + //! @details This is a default implementation, + //! subclasses can override this method if they need. + virtual juce::Rectangle getTextBoundingBox(juce::String const& text, + float max_width) const; - private: // methods + private: // methods + + void paint(juce::Graphics& g) override; + + void drawLabel(juce::Graphics& g); //! @brief Creates the text editor used by label when it's edited. virtual juce::TextEditor* createdTextEditor() = 0; @@ -121,8 +151,9 @@ namespace kiwi private: // members Label m_label; - bool m_editable; - tool::Listeners m_listeners; + bool m_editable {true}; + tool::Listeners m_listeners; + int m_padding {3}; private: // deleted methods @@ -131,5 +162,20 @@ namespace kiwi EditableObjectView(EditableObjectView && other) = delete; EditableObjectView& operator=(EditableObjectView const& other) = delete; EditableObjectView& operator=(EditableObjectView && other) = delete; + }; + + struct EditableObjectView::Listener + { + //! @brief Destructor. + virtual ~Listener() = default; + + //! @brief Called when the text has been edited and return key was pressed. + virtual void textChanged(std::string const& new_text) = 0; + + //! @brief Called when the classic view ends its edition. + virtual void editorHidden() = 0; + + //! @brief Called when the classic view enters its edition mode. + virtual void editorShown() = 0; }; } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp index 66f757d6..6f168613 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp @@ -40,62 +40,76 @@ namespace kiwi { return std::make_unique(object_model); } - MessageView::MessageView(model::Object & object_model) : - EditableObjectView(object_model), - m_output_message(object_model.getSignal<>(model::Message::Signal::outputMessage)), - m_active(false) - { - juce::Label & label = getLabel(); - - label.setText(object_model.getAttribute("text")[0].getString(), - juce::NotificationType::dontSendNotification); - - label.setColour(juce::Label::backgroundColourId, findColour(ObjectView::ColourIds::Background)); - label.setColour(juce::Label::backgroundWhenEditingColourId, - findColour(ObjectView::ColourIds::Background)); - label.setColour(juce::Label::textColourId, findColour(ObjectView::ColourIds::Text)); - label.setColour(juce::Label::textWhenEditingColourId, findColour(ObjectView::ColourIds::Text)); - - label.setInterceptsMouseClicks(false, false); - + MessageView::MessageView(model::Object & object_model) + : EditableObjectView(object_model) + , m_output_message(object_model.getSignal<>(model::Message::Signal::outputMessage)) + , m_active(false) + { + setColour(ObjectView::ColourIds::Background, juce::Colours::whitesmoke); + setColour(ObjectView::ColourIds::Outline, juce::Colours::whitesmoke.withAlpha(0.f)); + + juce::Label & label = getLabel(); + const auto object_text = object_model.getAttribute("text")[0].getString(); + label.setText(object_text, juce::NotificationType::dontSendNotification); + + label.setColour(juce::Label::backgroundColourId, + findColour(ObjectView::ColourIds::Background)); + + label.setColour(juce::Label::backgroundWhenEditingColourId, + findColour(ObjectView::ColourIds::Background)); + + label.setColour(juce::Label::textColourId, + findColour(ObjectView::ColourIds::Text)); + + label.setColour(juce::Label::textWhenEditingColourId, + findColour(ObjectView::ColourIds::Text)); + addAndMakeVisible(label); } MessageView::~MessageView() - { + {} + + void MessageView::validateSize(int& new_width, int& new_height) + { + new_width = std::max(new_width, getMinWidth()); + const auto text_bounds = getTextBoundingBox(getLabel().getText(), new_width); + new_height = std::max(text_bounds.getHeight(), getMinHeight()); } void MessageView::mouseDown(juce::MouseEvent const& e) - { - m_active = true; - - repaint(); - + { + m_active = true; m_output_message(); + repaint(); } void MessageView::mouseUp(juce::MouseEvent const& e) { m_active = false; - repaint(); + } + + void MessageView::paint(juce::Graphics& g) + { + g.setColour(findColour(EditableObjectView::ColourIds::Background).contrasting(m_active ? 0.1 : 0.)); + g.fillRoundedRectangle(getLocalBounds().toFloat(), 3.f); + + g.setColour(findColour(EditableObjectView::ColourIds::Background).contrasting(0.3)); + g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5), 3.f, 1.5f); } void MessageView::paintOverChildren (juce::Graphics& g) { - g.setColour (findColour (ObjectView::ColourIds::Outline)); - - drawOutline(g); - - juce::Path corner; - - juce::Rectangle bounds = getLocalBounds(); + g.setColour(findColour(EditableObjectView::ColourIds::Background).contrasting(0.3)); + auto bounds = getLocalBounds(); if (m_active) { g.setColour(findColour(ObjectView::ColourIds::Active)); } - + + juce::Path corner; corner.addTriangle(bounds.getTopRight().toFloat() - juce::Point(10, 0), bounds.getTopRight().toFloat(), bounds.getTopRight().toFloat() + juce::Point(0, 10)); @@ -103,42 +117,41 @@ namespace kiwi { g.fillPath(corner); } - void MessageView::resized() - { - getLabel().setBounds(getLocalBounds()); - } - void MessageView::textEditorTextChanged(juce::TextEditor& editor) - { - const int text_width = editor.getFont().getStringWidth(editor.getText()); - - if(editor.getWidth() < text_width + 16) - { - setSize(text_width + 16, getHeight()); - } + { + const auto text = editor.getText(); + auto single_line_text_width = editor.getFont().getStringWidthFloat(text) + 16 + 2; + auto width = std::max(getWidth() + 16, single_line_text_width); + + auto prev_width = getWidth(); + auto text_bounds = getTextBoundingBox(text, width); + + setSize(std::max(prev_width, single_line_text_width), + std::max(std::min(text_bounds.getHeight(), getHeight()), getMinHeight())); } void MessageView::attributeChanged(std::string const& name, tool::Parameter const& param) - { - if (name == "text") - { - getLabel().setText(param[0].getString(), juce::NotificationType::dontSendNotification); + { + static const std::string name_text = "text"; + if (name == name_text) + { + setText(param[0].getString()); } } void MessageView::textChanged() - { - juce::Label& label = getLabel(); - + { // Parse text and convert it back to string to display the formated version. - const auto atoms = tool::AtomHelper::parse(label.getText().toStdString(), + const auto atoms = tool::AtomHelper::parse(getText().toStdString(), tool::AtomHelper::ParsingFlags::Comma | tool::AtomHelper::ParsingFlags::Dollar); auto formatted_text = tool::AtomHelper::toString(atoms); - const int text_width = label.getFont().getStringWidth(formatted_text); + const int text_width = getFont().getStringWidth(formatted_text); model::Object & model = getModel(); - model.setWidth(text_width + 16); + + model.setWidth(std::max(26, text_width) + 10); + model.setHeight(getMinHeight()); // set the attribute and label text with formated text getLabel().setText(formatted_text, juce::NotificationType::dontSendNotification); @@ -149,36 +162,36 @@ namespace kiwi { juce::TextEditor* MessageView::createdTextEditor() { - juce::TextEditor * editor = new juce::TextEditor(); - - editor->setBounds(getLocalBounds()); - editor->setBorder(juce::BorderSize(0)); - + auto* editor = new juce::TextEditor(); editor->setColour(juce::TextEditor::ColourIds::textColourId, getLabel().findColour(juce::Label::textWhenEditingColourId)); editor->setColour(juce::TextEditor::backgroundColourId, - getLabel().findColour(juce::Label::backgroundWhenEditingColourId)); + juce::Colours::transparentWhite); editor->setColour(juce::TextEditor::highlightColourId, findColour(ObjectView::ColourIds::Highlight, true).withAlpha(0.4f)); - editor->setColour(juce::TextEditor::outlineColourId, juce::Colours::transparentWhite); editor->setColour(juce::TextEditor::focusedOutlineColourId, juce::Colours::transparentWhite); + + editor->setBounds(getLocalBounds()); + editor->setIndents(getPadding(), getPadding()); + editor->setBorder(juce::BorderSize(0)); + editor->setFont(getFont()); + editor->setJustification(getLabel().getJustificationType()); editor->setScrollbarsShown(false); editor->setScrollToShowCursor(true); editor->setReturnKeyStartsNewLine(false); - editor->setMultiLine(true, false); + editor->setMultiLine(true, true); editor->setInterceptsMouseClicks(true, false); editor->addListener(this); - return editor; } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.h index 8ed61de2..38e4b90b 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.h @@ -33,9 +33,10 @@ namespace kiwi { // MESSAGE VIEW // // ================================================================================ // - //! @brief The view of any textual kiwi object. - class MessageView : public EditableObjectView, - public juce::TextEditor::Listener + //! @brief The *message* object view. + class MessageView + : public EditableObjectView + , public juce::TextEditor::Listener { public: // methods @@ -51,34 +52,36 @@ namespace kiwi { //! @brief Destructor. ~MessageView(); - private: // methods + private: // methods + + //! @brief Validate the new width and height for the box + void validateSize(int& new_width, int& new_height) override; //! @brief Called when the message is clicked. - void mouseDown(juce::MouseEvent const& e) override final; + void mouseDown(juce::MouseEvent const& e) override; //! @brief Called when the bang is unclicked. - void mouseUp(juce::MouseEvent const& e) override final; - - //! @brief Called when the object is resized. - void resized() override final; + void mouseUp(juce::MouseEvent const& e) override; //! @brief Called when the text is being typed. //! @details Used to resize in order to keep text visible. - void textEditorTextChanged(juce::TextEditor& editor) override final; + void textEditorTextChanged(juce::TextEditor& editor) override; + + void paint(juce::Graphics& g) override; //! @brief Paints elements over the text editor. - void paintOverChildren (juce::Graphics& g) override final; + void paintOverChildren (juce::Graphics& g) override; //! @brief Called whenever one of the object's attribute has changed. - void attributeChanged(std::string const& name, tool::Parameter const& param) override final; + void attributeChanged(std::string const& name, tool::Parameter const& param) override; //! @brief Called when the label text has changed. //! @details Overrides EditableObjectView::textChanged. - void textChanged() override final; + void textChanged() override; //! @brief Constructs the label's text editor. //! @details Overrides EditableOjectView::createTextEditor. - juce::TextEditor* createdTextEditor() override final; + juce::TextEditor* createdTextEditor() override; private: // members diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp index 08c03281..d95cb647 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp @@ -51,7 +51,11 @@ namespace kiwi { } NumberViewBase::~NumberViewBase() + {} + + void NumberViewBase::validateSize(int& new_width, int& new_height) { + new_height = getMinHeight(); } void NumberViewBase::paint(juce::Graphics & g) diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.h index aed12c4c..604c062a 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.h @@ -59,6 +59,9 @@ namespace kiwi { private: // methods + //! @brief Validate the new width and height for the box + void validateSize(int& new_width, int& new_height) override; + //! @brief Called when the object is resized. void resized() override final; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp index 09663d7e..96ea29cf 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp @@ -32,26 +32,27 @@ namespace kiwi // OBJECT FRAME // // ================================================================================ // - ObjectFrame::ObjectFrame(PatcherView& patcher_view, std::unique_ptr object_view) : - m_object_view(std::move(object_view)), - m_patcher_view(patcher_view), - m_io_width(6), - m_io_height(3), - m_inlets(getModel().getNumberOfInlets()), - m_outlets(getModel().getNumberOfOutlets()), - m_outline(10, 4, 1) - { - initColours(); + ObjectFrame::ObjectFrame(PatcherView& patcher_view, + std::unique_ptr object_view) + : m_object_view(std::move(object_view)) + , m_patcher_view(patcher_view) + , m_io_width(6) + , m_io_height(3) + , m_inlets(getModel().getNumberOfInlets()) + , m_outlets(getModel().getNumberOfOutlets()) + , m_outline(10, 4, 1) + { + const bool pv_locked = isLocked(); + setInterceptsMouseClicks(pv_locked, pv_locked); + m_object_view->lockStatusChanged(pv_locked); + initColours(); + updateBoundsFromModel(false); + addChildComponent(m_outline); - + updateOutline(); + addAndMakeVisible(m_object_view.get()); - - updateBounds(false); - - updateOutline(); - - setInterceptsMouseClicks(isLocked(), isLocked()); } ObjectFrame::~ObjectFrame() @@ -73,18 +74,22 @@ namespace kiwi m_outline.setBounds(getLocalBounds()); } - void ObjectFrame::updateBounds(bool animate) + void ObjectFrame::updateBoundsFromModel(bool animate) { model::Object const& model = getModel(); if(!model.removed()) { - const juce::Point origin = m_patcher_view.getOriginPosition(); + const juce::Point origin = m_patcher_view.getOriginPosition(); + + int width = model.getWidth(); + int height = model.getHeight(); + + validateSize(width, height); const juce::Rectangle object_bounds(model.getX() + origin.getX(), model.getY() + origin.getY(), - model.getWidth(), - model.getHeight()); + width, height); const juce::Rectangle frame_bounds = object_bounds.expanded(m_outline.getBorderThickness()); @@ -128,6 +133,11 @@ namespace kiwi juce::Rectangle ObjectFrame::getObjectBounds() const { return m_object_view->getBounds().withPosition(getPosition() + m_object_view->getBounds().getPosition()); + } + + void ObjectFrame::validateSize(int& new_width, int& new_height) + { + m_object_view->validateSize(new_width, new_height); } void ObjectFrame::mouseDown(juce::MouseEvent const& e) @@ -259,7 +269,7 @@ namespace kiwi const bool animate = (ctrl == flip::Controller::UNDO || ctrl == flip::Controller::EXTERNAL); - updateBounds(animate); + updateBoundsFromModel(animate); repaint(); } @@ -274,9 +284,7 @@ namespace kiwi { dynamic_cast(m_object_view.get())->removeListener(*this); - ClassicView * object_view = dynamic_cast(m_object_view.get()); - - if (object_view) + if (auto* object_view = dynamic_cast(m_object_view.get())) { getPatcherView().objectTextChanged(*this, new_text); } @@ -298,12 +306,10 @@ namespace kiwi void ObjectFrame::editObject() { - EditableObjectView * object_view = dynamic_cast(m_object_view.get()); - - if (object_view != nullptr) + if (auto* editable_object_view = dynamic_cast(m_object_view.get())) { - object_view->addListener(*this); - object_view->edit(); + editable_object_view->addListener(*this); + editable_object_view->edit(); } } @@ -364,14 +370,16 @@ namespace kiwi } void ObjectFrame::lockStatusChanged() - { + { + const bool locked = isLocked(); + setInterceptsMouseClicks(locked, locked); + m_object_view->lockStatusChanged(locked); repaint(); - setInterceptsMouseClicks(isLocked(), isLocked()); } void ObjectFrame::patcherViewOriginPositionChanged() { - updateBounds(false); + updateBoundsFromModel(false); } juce::Point ObjectFrame::getInletPatcherPosition(const size_t index) const @@ -473,21 +481,19 @@ namespace kiwi ObjectFrame::Outline::Outline(int resize_length, int resize_thickness, - int inner_thickness): - juce::Component(), - m_resize_length(resize_length), - m_resize_thickness(resize_thickness), - m_inner_thickness(inner_thickness), - m_corners(), - m_borders(), - m_resize_colour(), - m_inner_colour() - { - } + int inner_thickness) + : juce::Component() + , m_resize_length(resize_length) + , m_resize_thickness(resize_thickness) + , m_inner_thickness(inner_thickness) + , m_corners() + , m_borders() + , m_resize_colour() + , m_inner_colour() + {} ObjectFrame::Outline::~Outline() - { - } + {} int ObjectFrame::Outline::getBorderThickness() const { @@ -639,10 +645,10 @@ namespace kiwi drawBorder(g, Border::Top); drawBorder(g, Border::Right); drawBorder(g, Border::Bottom); - - drawCorner(g, Border::Left | Border::Top); - drawCorner(g, Border::Top | Border::Right); - drawCorner(g, Border::Right | Border::Bottom); + + drawCorner(g, Border::Left | Border::Top); + drawCorner(g, Border::Top | Border::Right); + drawCorner(g, Border::Right | Border::Bottom); drawCorner(g, Border::Bottom | Border::Left); } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h index fd34e2b7..3700cfa7 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h @@ -40,89 +40,10 @@ namespace kiwi //! @brief A juce component holding the object's graphical representation. //! @details ObjectFrame is implemented as a wrapper around an object view that displays //! selections and outlet and handle certain interactions. - class ObjectFrame : public juce::Component, - public EditableObjectView::Listener + class ObjectFrame + : public juce::Component + , public EditableObjectView::Listener { - public: // classes - - enum ColourIds - { - Selection = 0x1100000, - SelectionOtherView = 0x1100001, - SelectionDistant = 0x1100002, - Pin = 0x1100003 - }; - - public: // classes - - struct Outline : public juce::Component - { - public: // classes - - enum class Border : int - { - Top = 1 << 0, - Bottom = 1 << 1, - Left = 1 << 2, - Right = 1 << 3 - }; - - public: // methods - - //! @brief Constructor. - //! @details Defines the resizable corner size, its thickness and the inner border thickness? - Outline(int resize_length, - int resize_thickness, - int inner_thickness); - - //! @brief Tests if the point reaches an interactive resiable corner. - bool hitTest(juce::Point const& pt, HitTester& hit_tester) const; - - //! @brief Returns the corner border width. - int getBorderThickness() const; - - //! @brief Returns the corner border length. - int getResizeLength() const; - - //! @brief Sets the corner colour. - void setResizeColour(juce::Colour colour); - - //! @brief Sets the inner border colour. - void setInnerColour(juce::Colour colour); - - ~Outline(); - - private: // methods - - //! @brief Draws a corner. - void drawCorner(juce::Graphics & g, Border border); - - //! @brief Draws a border. - void drawBorder(juce::Graphics & g, Border border); - - //! @brief Graphical rendering method. - void paint(juce::Graphics & g) override final; - - //! @brief Call once size changed. Recompute borders and corners position. - void resized() override final; - - //! @brief Update the corners position - void updateCorners(); - - //! @brief Update the borders position. - void updateBorders(); - - private: // members - - int m_resize_length; - int m_resize_thickness; - int m_inner_thickness; - std::map, 3>> m_corners; - std::map> m_borders; - juce::Colour m_resize_colour; - juce::Colour m_inner_colour; - }; - public: // methods //! @brief Constructor. @@ -182,7 +103,90 @@ namespace kiwi void mouseUp(juce::MouseEvent const& e) override final; //! @brief Called when object's frame is clicked. - void mouseDrag(juce::MouseEvent const& e) override final; + void mouseDrag(juce::MouseEvent const& e) override final; + + //! @brief Validate the new width and height for the box + void validateSize(int& new_width, int& new_height); + + public: // classes + + enum ColourIds + { + Selection = 0x1100000, + SelectionOtherView = 0x1100001, + SelectionDistant = 0x1100002, + Pin = 0x1100003 + }; + + public: // classes + + struct Outline : public juce::Component + { + public: // classes + + enum class Border : int + { + Top = 1 << 0, + Bottom = 1 << 1, + Left = 1 << 2, + Right = 1 << 3 + }; + + public: // methods + + //! @brief Constructor. + //! @details Defines the resizable corner size, its thickness and the inner border thickness? + Outline(int resize_length, + int resize_thickness, + int inner_thickness); + + //! @brief Tests if the point reaches an interactive resiable corner. + bool hitTest(juce::Point const& pt, HitTester& hit_tester) const; + + //! @brief Returns the corner border width. + int getBorderThickness() const; + + //! @brief Returns the corner border length. + int getResizeLength() const; + + //! @brief Sets the corner colour. + void setResizeColour(juce::Colour colour); + + //! @brief Sets the inner border colour. + void setInnerColour(juce::Colour colour); + + ~Outline(); + + private: // methods + + //! @brief Draws a corner. + void drawCorner(juce::Graphics & g, Border border); + + //! @brief Draws a border. + void drawBorder(juce::Graphics & g, Border border); + + //! @brief Graphical rendering method. + void paint(juce::Graphics & g) override final; + + //! @brief Call once size changed. Recompute borders and corners position. + void resized() override final; + + //! @brief Update the corners position + void updateCorners(); + + //! @brief Update the borders position. + void updateBorders(); + + private: // members + + int m_resize_length; + int m_resize_thickness; + int m_inner_thickness; + std::map, 3>> m_corners; + std::map> m_borders; + juce::Colour m_resize_colour; + juce::Colour m_inner_colour; + }; private: // methods @@ -206,7 +210,7 @@ namespace kiwi void initColours(); //! @brief Called to update the bounds of the object. - void updateBounds(bool animate); + void updateBoundsFromModel(bool animate); //! @brief Updates the outline according to the selection status. //! @details Makes it visible or not and updates its colour. diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp index acf2f4c0..38ced89d 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp @@ -33,18 +33,19 @@ namespace kiwi // OBJECT VIEW // // ================================================================================ // - ObjectView::ObjectView(model::Object & object_model): - m_model(object_model), - m_border_size(1.5), - m_master(this, [](ObjectView*){}) + ObjectView::ObjectView(model::Object & object_model) + : m_model(object_model) + , m_border_size(1.5) + , m_master(this, [](ObjectView*){}) { object_model.addListener(*this); - - setColour(ObjectView::ColourIds::Error, juce::Colour::fromFloatRGBA(0.6, 0.1, 0.1, 0.)); - setColour(ObjectView::ColourIds::Background, juce::Colours::white); + + setColour(ObjectView::ColourIds::Error, juce::Colour::fromRGBA(223, 97, 94, 250)); + const juce::Colour bgcolor = juce::Colours::white; + setColour(ObjectView::ColourIds::Background, bgcolor); setColour(ObjectView::ColourIds::Text, juce::Colours::black); - setColour(ObjectView::ColourIds::Outline, juce::Colours::black); - setColour(ObjectView::ColourIds::Highlight, juce::Colour::fromFloatRGBA(0., 0.5, 1., 0.)); + setColour(ObjectView::ColourIds::Outline, bgcolor.contrasting(0.8)); + setColour(ObjectView::ColourIds::Highlight, juce::Colour::fromFloatRGBA(0., 0.5, 1., 0.4)); setColour(ObjectView::ColourIds::Active, juce::Colour(0xff21ba90)); } @@ -93,11 +94,21 @@ namespace kiwi juce::Rectangle ObjectView::getOutline() const { return getLocalBounds(); + } + + void ObjectView::validateSize(int& new_width, int& new_height) + { + ; } void ObjectView::drawOutline(juce::Graphics & g) { g.drawRect(getOutline(), m_border_size); + } + + void ObjectView::lockStatusChanged(bool is_locked) + { + // nothing to do by default } // ================================================================================ // diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h index d5962419..422baac8 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h @@ -32,14 +32,17 @@ namespace kiwi { - class ObjectFrame; + class ObjectFrame; + class PatcherView; // ================================================================================ // // OBJECT VIEW // // ================================================================================ // //! @brief Abstract for objects graphical representation. - class ObjectView : public juce::Component, public model::Object::Listener + class ObjectView + : public juce::Component + , public model::Object::Listener { public: // classes @@ -68,9 +71,15 @@ namespace kiwi void modelAttributeChanged(std::string const& name, tool::Parameter const& param) override final; //! @brief Called when a parameter has changed. - void modelParameterChanged(std::string const& name, tool::Parameter const& param) override final; - - protected: // methods + void modelParameterChanged(std::string const& name, tool::Parameter const& param) override final; + + //! @brief Validate the new width and height for the box + virtual void validateSize(int& new_width, int& new_height); + + //! @brief Called every time a patcher is locked or unlocked. + virtual void lockStatusChanged(bool is_locked); + + protected: // methods //! @biref Returns the main scheduler. tool::Scheduler<> & getScheduler() const; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp index fff873d8..e866d00a 100755 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp @@ -40,20 +40,20 @@ namespace kiwi // PATCHER VIEW // // ================================================================================ // - PatcherView::PatcherView(PatcherManager& manager, - Instance& instance, - model::Patcher& patcher, - model::Patcher::View& view) : - m_manager(manager), - m_instance(instance), - m_patcher_model(patcher), - m_view_model(view), - m_viewport(*this), - m_hittester(*this), - m_mouse_handler(*this), - m_io_highlighter(), - m_lasso(*this), - m_grid_size(20) + PatcherView::PatcherView(PatcherManager& manager, + Instance& instance, + model::Patcher& patcher, + model::Patcher::View& view) + : m_manager(manager) + , m_instance(instance) + , m_patcher_model(patcher) + , m_view_model(view) + , m_viewport(*this) + , m_hittester(*this) + , m_mouse_handler(*this) + , m_io_highlighter() + , m_lasso(*this) + , m_grid_size(20) { KiwiApp::bindToCommandManager(this); KiwiApp::bindToKeyMapping(this); @@ -990,7 +990,11 @@ namespace kiwi if(link.added()) { addLinkView(link); } } - bool patcher_area_uptodate = false; + bool patcher_area_uptodate = false; + + // update parameters before updating bounds + // (ie text objects adapt their sizes depending on the size) + updateParameters(patcher); // send ObjectView change notification for(auto& object : patcher.getObjects()) @@ -1065,8 +1069,6 @@ namespace kiwi } } - updateParameters(patcher); - if(view.removed()) {} } @@ -1366,10 +1368,10 @@ namespace kiwi if(it == m_objects.cend()) { const auto it = (zorder > 0) ? m_objects.begin() + zorder : m_objects.end(); + + auto of = std::make_unique(*this, Factory::createObjectView(object)); - std::unique_ptr object_view = Factory::createObjectView(object); - - ObjectFrame& object_frame = **(m_objects.emplace(it, new ObjectFrame(*this, std::move(object_view)))); + auto& object_frame = **(m_objects.emplace(it, std::move(of))); addAndMakeVisible(object_frame, zorder); } @@ -1528,12 +1530,13 @@ namespace kiwi std::vector atoms = tool::AtomHelper::parse(new_text); - std::unique_ptr object_model = model::Factory::create(atoms); + auto object_model = model::Factory::create(atoms); - juce::Point origin = getOriginPosition(); - juce::Rectangle box_bounds = object_frame.getObjectBounds(); + auto origin = getOriginPosition(); + auto box_bounds = object_frame.getObjectBounds(); - object_model->setPosition(box_bounds.getX() - origin.x, box_bounds.getY() - origin.y); + object_model->setPosition(box_bounds.getX() - origin.x, + box_bounds.getY() - origin.y); // handle error box case if(object_model->getName() == "errorbox") @@ -1545,8 +1548,12 @@ namespace kiwi } if (!object_model->hasFlag(model::ObjectClass::Flag::DefinedSize)) - { - const int text_width = juce::Font().getStringWidth(new_text) + 12; + { + // TODO: we should fetch these values dynamically : + const int object_text_padding = 3; + const juce::Font object_font = juce::Font(15); + + const int text_width = object_font.getStringWidth(new_text) + object_text_padding*2; const int max_io = std::max(object_model->getNumberOfInlets(), object_model->getNumberOfOutlets()) * 14; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp index 94abd1d3..ab22e50c 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp @@ -216,61 +216,43 @@ namespace kiwi } } - void MouseHandler::applyNewBounds(model::Object & object_model, juce::Rectangle new_bounds, double ratio) const + void MouseHandler::applyNewBounds(model::Object & model, juce::Rectangle new_bounds, double ratio) const { - juce::ComponentBoundsConstrainer bounds_constrainer; + juce::ComponentBoundsConstrainer constrainer; - if (!object_model.hasFlag(model::ObjectClass::Flag::ResizeWidth)) - { - bounds_constrainer.setMinimumWidth(object_model.getWidth()); - bounds_constrainer.setMaximumWidth(object_model.getWidth()); - } - else if (object_model.getMinWidth() != 0) - { - bounds_constrainer.setMinimumWidth(object_model.getMinWidth()); - } + const double default_min_width = 20.; + const double default_min_height = 20.; - if (!object_model.hasFlag(model::ObjectClass::Flag::ResizeHeight)) - { - bounds_constrainer.setMinimumHeight(object_model.getHeight()); - bounds_constrainer.setMaximumHeight(object_model.getHeight()); - } - else if (object_model.getMinHeight() != 0) - { - bounds_constrainer.setMinimumHeight(object_model.getMinHeight()); - } + constrainer.setMinimumWidth((model.getMinWidth() > 0) ? model.getMinWidth() : default_min_width); + constrainer.setMinimumHeight((model.getMinHeight() > 0) ? model.getMinHeight() : default_min_height); - if (object_model.getRatio() != 0) + if (model.getRatio() != 0) { - bounds_constrainer.setFixedAspectRatio(1 / object_model.getRatio()); + constrainer.setFixedAspectRatio(1 / model.getRatio()); } else if (ratio != 0) { - bounds_constrainer.setFixedAspectRatio(1 / ratio); + constrainer.setFixedAspectRatio(1 / ratio); } juce::Rectangle limits = m_patcher_view.getBounds(); - juce::Rectangle previous_bounds(object_model.getX(), - object_model.getY(), - object_model.getWidth(), - object_model.getHeight()); + juce::Rectangle previous_bounds(model.getX(), model.getY(), + model.getWidth(), model.getHeight()); juce::Rectangle target_bounds = new_bounds; - bounds_constrainer.checkBounds(target_bounds, - previous_bounds, - limits, - m_direction & Direction::Up, - m_direction & Direction::Left, - m_direction & Direction::Down, - m_direction & Direction::Right); - - object_model.setPosition(target_bounds.getX(), target_bounds.getY()); - - object_model.setWidth(target_bounds.getWidth()); - - object_model.setHeight(target_bounds.getHeight()); + constrainer.checkBounds(target_bounds, + previous_bounds, + limits, + m_direction & Direction::Up, + m_direction & Direction::Left, + m_direction & Direction::Down, + m_direction & Direction::Right); + + model.setPosition(target_bounds.getX(), target_bounds.getY()); + model.setWidth(target_bounds.getWidth()); + model.setHeight(target_bounds.getHeight()); } void MouseHandler::continueAction(juce::MouseEvent const& e) @@ -417,9 +399,19 @@ namespace kiwi new_bounds.setBottom(std::max(new_bounds.getBottom() + delta.getY(), new_bounds.getY())); } - auto& document = m_patcher_view.m_patcher_model.entity().use(); + if(auto* box = hit_tester.getObject()) + { + int new_width = new_bounds.getWidth(); + int new_height = new_bounds.getHeight(); + box->validateSize(new_width, new_height); + new_bounds.setSize(new_width, new_height); + } - applyNewBounds(*document.get(bounds_it.first), new_bounds, ratio); + auto& document = m_patcher_view.m_patcher_model.entity().use(); + if(auto* model = document.get(bounds_it.first)) + { + applyNewBounds(*model, new_bounds, ratio); + } } model::DocumentManager::commitGesture(m_patcher_view.m_patcher_model, "Resize object"); @@ -682,7 +674,7 @@ namespace kiwi direction |= Direction::Up; } - if ((border & HitTester::Border::Right) && object_model.hasFlag(model::ObjectClass::Flag::ResizeWidth)) + if ((border & HitTester::Border::Right)) // && object_model.hasFlag(model::ObjectClass::Flag::ResizeWidth)) { direction |= Direction::Right; } @@ -692,7 +684,7 @@ namespace kiwi direction |= Direction::Down; } - if ((border & HitTester::Border::Left) && object_model.hasFlag(model::ObjectClass::Flag::ResizeWidth)) + if ((border & HitTester::Border::Left) ) // && object_model.hasFlag(model::ObjectClass::Flag::ResizeWidth)) { direction |= Direction::Left; } diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp index 8ca1f580..c09a4050 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp @@ -40,6 +40,7 @@ namespace kiwi { namespace model { // Flags comment_class->setFlag(ObjectClass::Flag::DefinedSize); + comment_class->setFlag(ObjectClass::Flag::ResizeWidth); // DataModel flip::Class & comment_model = DataModel::declare() @@ -55,24 +56,23 @@ namespace kiwi { namespace model { return std::make_unique(args); } - Comment::Comment(std::vector const& args): - Object(), - m_comment_text("") + Comment::Comment(std::vector const& args) + : Object() + , m_comment_text("") { if (args.size() > 0) throw Error("comment too many arguments"); setMinWidth(20.); setMinHeight(20.); - setWidth(40); + setWidth(120.); setHeight(20.); } - Comment::Comment(flip::Default& d): - Object(d), - m_comment_text("") - { - } + Comment::Comment(flip::Default& d) + : Object(d) + , m_comment_text("") + {} void Comment::writeAttribute(std::string const& name, tool::Parameter const& parameter) { From 41fa573f18dc7949a94b0b5910f4e9ef8cb4d9c0 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sun, 10 Jun 2018 19:38:04 +0200 Subject: [PATCH 05/41] fix linux build --- .../KiwiApp_Objects/KiwiApp_EditableObjectView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp index 19ecc2ce..7db7e280 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp @@ -106,8 +106,8 @@ namespace kiwi { const int padding = getPadding(); const int side_padding = padding * 2; const float max_line_width = max_width - side_padding; - const auto font { getFont() }; - arr.addJustifiedText (font, text, + + arr.addJustifiedText (getFont(), text, padding, padding, max_line_width, juce::Justification::topLeft); From 1da4140c14d4ce689437f67bd681ce52eb1906d3 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sun, 10 Jun 2018 19:38:15 +0200 Subject: [PATCH 06/41] Add Juce dependency to the engine --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 798c587e..7e24fa62 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -394,6 +394,7 @@ add_library(KiwiEngine STATIC ${KIWI_ENGINE_SRC}) target_include_directories(KiwiEngine PUBLIC ${ROOT_DIR}/Modules) target_add_dependency(KiwiEngine KiwiModel) target_add_dependency(KiwiEngine KiwiDsp) +target_add_dependency(KiwiEngine Juce) set_target_properties(KiwiEngine PROPERTIES FOLDER Modules) source_group_rec("${KIWI_ENGINE_SRC}" ${ROOT_DIR}/Modules/KiwiEngine) From 29f90a038afdae7e4551f72abc6dfa04656ef227 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sun, 10 Jun 2018 21:33:27 +0200 Subject: [PATCH 07/41] Add new [sfplay~] object --- Client/Source/KiwiApp.cpp | 3 +- .../KiwiEngine_Objects/KiwiEngine_Objects.h | 1 + .../KiwiEngine_SfPlayTilde.cpp | 419 ++++++++++++++++++ .../KiwiEngine_SfPlayTilde.h | 143 ++++++ Modules/KiwiModel/KiwiModel_DataModel.cpp | 3 +- .../KiwiModel_Objects/KiwiModel_Objects.h | 4 +- .../KiwiModel_SfPlayTilde.cpp | 93 ++++ .../KiwiModel_Objects/KiwiModel_SfPlayTilde.h | 49 ++ docs/software/objects.md | 1 + 9 files changed, 713 insertions(+), 3 deletions(-) create mode 100644 Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp create mode 100644 Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.h create mode 100755 Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.cpp create mode 100755 Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.h diff --git a/Client/Source/KiwiApp.cpp b/Client/Source/KiwiApp.cpp index 0cb4b512..f89c1419 100644 --- a/Client/Source/KiwiApp.cpp +++ b/Client/Source/KiwiApp.cpp @@ -209,7 +209,8 @@ namespace kiwi engine::SwitchTilde::declare(); engine::Float::declare(); engine::ClipTilde::declare(); - engine::Clip::declare(); + engine::Clip::declare(); + engine::SfPlayTilde::declare(); } void KiwiApp::declareObjectViews() diff --git a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_Objects.h b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_Objects.h index cb719e42..f16d6fed 100644 --- a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_Objects.h +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_Objects.h @@ -85,3 +85,4 @@ #include #include #include +#include diff --git a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp new file mode 100644 index 00000000..a9e52c8f --- /dev/null +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp @@ -0,0 +1,419 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#include +#include + +namespace kiwi { namespace engine { + + // ================================================================================ // + // SOUNDFILE PLAYER // + // ================================================================================ // + + SoundFilePlayer::SoundFilePlayer() + : m_buffer() + , m_audio_source_channel_info(m_buffer) + { + m_format_manager.registerBasicFormats(); + m_transport_source.addChangeListener(this); + } + + SoundFilePlayer::~SoundFilePlayer() + { + stop(); + } + + bool SoundFilePlayer::start(const juce::File& file, double start_ms, double end_ms) + { + stop(); + + std::unique_ptr reader (m_format_manager.createReaderFor(file)); + + if (reader) + { + const auto sf_frames = reader->lengthInSamples; + const auto sf_samplerate = reader->sampleRate; + + const int64_t start_sample = (start_ms * 0.001 * sf_samplerate); + + if(start_sample >= 0 && start_sample < sf_frames) + { + int64_t end_sample = sf_frames; + + if(start_ms < end_ms) + { + end_sample = std::min(static_cast(end_ms * 0.001 * sf_samplerate), + sf_frames); + } + + int64_t length = end_sample - start_sample; + auto susection_reader = new juce::AudioSubsectionReader(reader.release(), start_sample, length, true); + + auto new_source = std::make_unique(susection_reader, true); + m_transport_source.setSource(new_source.get(), 0, nullptr, susection_reader->sampleRate); + m_reader_source.reset(new_source.release()); + setLoop(is_looping); + changeState(Starting); + } + } + + return false; + } + + void SoundFilePlayer::stop() + { + changeState(Stopping); + } + + bool SoundFilePlayer::isPlaying() const + { + return m_transport_source.isPlaying(); + } + + void SoundFilePlayer::setLoop(bool should_loop) + { + is_looping = should_loop; + if (m_reader_source != nullptr) + { + m_reader_source->setLooping(should_loop); + } + } + + void SoundFilePlayer::setNumberOfChannels(size_t channels) + { + m_channels = channels > 0 ? channels : m_channels; + } + + size_t SoundFilePlayer::getNumberOfChannels() const + { + return m_channels; + } + + juce::String SoundFilePlayer::getSupportedFormats() const + { + return m_format_manager.getWildcardForAllFormats(); + } + + void SoundFilePlayer::printInfos(juce::File file, + std::function post) + { + std::unique_ptr reader (m_format_manager.createReaderFor(file)); + + if (reader) + { + post("- file: " + file.getFullPathName()); + post("- format: " + reader->getFormatName()); + post("- sampling rate: " + juce::String(reader->sampleRate)); + post("- channels: " + juce::String(reader->numChannels)); + post("- duration: " + juce::RelativeTime::seconds(reader->lengthInSamples / reader->sampleRate).getDescription()); + post("- bits per sample: " + juce::String(reader->bitsPerSample)); + + // other infos; + const auto speaker_arr = reader->getChannelLayout().getSpeakerArrangementAsString(); + if(!speaker_arr.isEmpty()) + { + post("- speaker arrangement: " + speaker_arr + "\n"); + } + + const auto metadata = reader->metadataValues.getDescription(); + if(!metadata.isEmpty()) + { + post("- metadata: " + metadata + "\n"); + } + } + } + + void SoundFilePlayer::setPlayingStoppedCallback(playingStoppedCallback && fn) + { + m_playing_stopped_callback = std::move(fn); + } + + void SoundFilePlayer::prepare(double sample_rate, size_t vector_size) + { + m_buffer.setSize(getNumberOfChannels(), vector_size); + m_audio_source_channel_info.startSample = 0.; + m_audio_source_channel_info.numSamples = vector_size; + + m_transport_source.prepareToPlay(vector_size, sample_rate); + } + + bool SoundFilePlayer::read(dsp::Buffer& outputs) + { + if (m_reader_source == nullptr) + { + const auto channels = outputs.getNumberOfChannels(); + for(auto channel = 0; channel < channels; ++channel) + { + outputs[channel].fill(0.); + } + + return false; + } + + m_transport_source.getNextAudioBlock(m_audio_source_channel_info); + + // recopy samples + + const auto channels = outputs.getNumberOfChannels(); + for(auto channel = 0; channel < channels; ++channel) + { + auto& output = outputs[channel]; + if(channel < m_buffer.getNumChannels() + && output.size() <= m_buffer.getNumSamples()) + { + for(auto i = 0; i < output.size(); ++i) + { + output[i] = m_buffer.getReadPointer(channel)[i]; + } + } + } + + return true; + } + + void SoundFilePlayer::release() + { + m_transport_source.releaseResources(); + } + + void SoundFilePlayer::changeState (TransportState new_state) + { + if (m_state != new_state) + { + m_state = new_state; + + switch (m_state) + { + case Stopped: + if(m_playing_stopped_callback) + { + m_playing_stopped_callback(); + } + break; + + case Starting: + m_transport_source.start(); + break; + + case Playing: + break; + + case Stopping: + m_transport_source.stop(); + break; + } + } + } + + void SoundFilePlayer::changeListenerCallback(juce::ChangeBroadcaster* source) + { + if (source == &m_transport_source) + { + if (m_transport_source.isPlaying()) + changeState (Playing); + else + changeState (Stopped); + } + } + + // ================================================================================ // + // SFPLAY~ // + // ================================================================================ // + + void SfPlayTilde::declare() + { + Factory::add("sfplay~", &SfPlayTilde::create); + } + + std::unique_ptr SfPlayTilde::create(model::Object const& model, Patcher & patcher) + { + return std::make_unique(model, patcher); + } + + SfPlayTilde::SfPlayTilde(model::Object const& model, Patcher& patcher) + : AudioObject(model, patcher) + { + const auto& args = model.getArguments(); + const auto channels = !args.empty() && args[0].getInt() > 0 ? args[0].getInt() : 2; + m_player.setNumberOfChannels(channels); + + m_player.setPlayingStoppedCallback([this](){ + defer([this](){ + send(getNumberOfOutputs() - 1, {"bang"}); + }); + }); + } + + SfPlayTilde::~SfPlayTilde() + { + closeFileDialog(); + } + + void SfPlayTilde::receive(size_t index, std::vector const& args) + { + if (!args.empty() && index == 0) + { + if (args[0].isString() && args[0].getString() == "open") + { + if(args.size() > 1) + { + if(args[1].isString()) + { + openFile(juce::File(args[1].getString())); + } + } + else + { + openFileDialog(); + } + } + else if ((args[0].isNumber() && args[0].getInt() == 1) + || args[0].getString() == "start") + { + m_player.start(m_file_to_read); + } + else if ((args[0].isNumber() && args[0].getInt() == 0) + || args[0].getString() == "stop") + { + m_player.stop(); + } + else if (args[0].getString() == "seek") + { + if(args.size() > 1 && args[1].isNumber()) + { + double start_ms = args[1].getFloat(); + double end_ms = ((args.size() > 2 && args[2].isNumber()) + ? args[2].getFloat() + : -1.); + + m_player.start(m_file_to_read, start_ms, end_ms); + } + } + else if (args[0].getString() == "loop") + { + if(args.size() == 2 && args[1].isNumber()) + { + m_player.setLoop(args[1].getInt()); + } + else + { + warning("sfplay~: loop message must be followed by 0 or 1"); + } + } + else if (args[0].getString() == "print") + { + post("*sfplay~ infos*"); + const auto filepath = m_file_to_read.getFullPathName(); + if(filepath.isNotEmpty()) + { + m_player.printInfos(m_file_to_read, [this](juce::String const& line){ + post(line.toStdString()); + }); + } + else + { + post("- no file opened"); + } + } + } + } + + bool SfPlayTilde::openFile(juce::File file) + { + const auto file_path = file.getFullPathName(); + if(!juce::File::isAbsolutePath(file_path)) + { + warning("sfplay~: is not an absolute path"); + return false; + } + + if(file.isDirectory()) + { + warning("sfplay~: invalid file path"); + return false; + } + + if(!file.exists()) + { + warning("sfplay~: file doesn't exist \"" + + file_path.toStdString() + "\""); + return false; + } + + // that really exist. + + m_player.stop(); + m_file_to_read = file; + return true; + } + + void SfPlayTilde::openFileDialog() + { + const auto default_dir = juce::File::getSpecialLocation(juce::File::userMusicDirectory); + + auto dir = (!m_file_to_read.getFullPathName().isEmpty() + ? m_file_to_read.getParentDirectory() + : default_dir); + + m_file_chooser = std::make_unique("Choose a file to read...", + dir, m_player.getSupportedFormats(), true); + + deferMain([this, fc = m_file_chooser.get()]() { + + const int fc_flags = (juce::FileBrowserComponent::openMode + | juce::FileBrowserComponent::canSelectFiles); + + fc->launchAsync(fc_flags, [this](juce::FileChooser const& chooser) { + auto file = chooser.getResult(); + if(file.getFullPathName().isNotEmpty()) + { + openFile(file); + } + }); + }); + } + + void SfPlayTilde::closeFileDialog() + { + deferMain([this]() { + + m_file_chooser.reset(); + + }); + } + + void SfPlayTilde::prepare(dsp::Processor::PrepareInfo const& infos) + { + m_player.prepare(infos.sample_rate, infos.vector_size); + setPerformCallBack(this, &SfPlayTilde::perform); + } + + void SfPlayTilde::perform(dsp::Buffer const& input, dsp::Buffer& output) noexcept + { + m_player.read(output); + } + + void SfPlayTilde::release() + { + m_player.release(); + } + +}} diff --git a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.h b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.h new file mode 100644 index 00000000..109377f8 --- /dev/null +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.h @@ -0,0 +1,143 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace kiwi { namespace engine { + + // ================================================================================ // + // SOUNDFILE PLAYER // + // ================================================================================ // + + class SoundFilePlayer + : public juce::ChangeListener + { + public: // methods + + SoundFilePlayer(); + ~SoundFilePlayer(); + + //! @brief Starts reading file from start pos to end pos. + //! @details if end time is greater than starting time, + //! file will be played from the start position to the end of the file + bool start(const juce::File& file, double start_ms = 0., double end_ms = -1.); + + void stop(); + + bool isPlaying() const; + + void setLoop(bool should_loop); + + void setNumberOfChannels(size_t channels); + + size_t getNumberOfChannels() const; + + juce::String getSupportedFormats() const; + + void prepare(double sample_rate, size_t vector_size); + + bool read(dsp::Buffer& input); + + void release(); + + using playingStoppedCallback = std::function; + void setPlayingStoppedCallback(playingStoppedCallback && fn); + + //! @brief Print file infos. + void printInfos(juce::File file, + std::function printer); + + private: + + enum TransportState + { + Stopped, + Starting, + Playing, + Stopping + }; + + void changeListenerCallback(juce::ChangeBroadcaster* source) override; + + void changeState(TransportState new_state); + + private: + + size_t m_channels; + + playingStoppedCallback m_playing_stopped_callback {nullptr}; + + bool is_looping {false}; + + juce::AudioFormatManager m_format_manager; + std::unique_ptr m_reader_source {nullptr}; + juce::AudioTransportSource m_transport_source; + TransportState m_state {Stopped}; + + juce::AudioBuffer m_buffer; + juce::AudioSourceChannelInfo m_audio_source_channel_info; + }; + + // ================================================================================ // + // SFPLAY~ // + // ================================================================================ // + + class SfPlayTilde + : public engine::AudioObject + { + public: // methods + + SfPlayTilde(model::Object const& model, Patcher& patcher); + ~SfPlayTilde(); + + void receive(size_t index, std::vector const& args) override final; + + private: + + bool openFile(juce::File file); + + void openFileDialog(); + void closeFileDialog(); + + void perform(dsp::Buffer const& input, dsp::Buffer& output) noexcept; + void prepare(dsp::Processor::PrepareInfo const& infos) override; + void release() override; + + public: // internal + + static void declare(); + static std::unique_ptr create(model::Object const& model, Patcher & patcher); + + private: + + juce::File m_file_to_read; + std::unique_ptr m_file_chooser; + + SoundFilePlayer m_player; + }; + +}} diff --git a/Modules/KiwiModel/KiwiModel_DataModel.cpp b/Modules/KiwiModel/KiwiModel_DataModel.cpp index 9e82d92b..80062b26 100644 --- a/Modules/KiwiModel/KiwiModel_DataModel.cpp +++ b/Modules/KiwiModel/KiwiModel_DataModel.cpp @@ -96,7 +96,8 @@ namespace kiwi model::SwitchTilde::declare(); model::Float::declare(); model::ClipTilde::declare(); - model::Clip::declare(); + model::Clip::declare(); + model::SfPlayTilde::declare(); } void DataModel::init(std::function declare_object) diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h index 4de18ba2..59927853 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h @@ -84,4 +84,6 @@ #include #include #include -#include +#include +#include + diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.cpp new file mode 100755 index 00000000..abe1a816 --- /dev/null +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.cpp @@ -0,0 +1,93 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#include + +#include + +namespace kiwi { namespace model { + + // ================================================================================ // + // SFPLAY~ // + // ================================================================================ // + + void SfPlayTilde::declare() + { + auto kiwi_class = std::make_unique("sfplay~", &SfPlayTilde::create); + + auto& flip_class = DataModel::declare() + .name(kiwi_class->getModelName().c_str()) + .inherit(); + + Factory::add(std::move(kiwi_class), flip_class); + } + + std::unique_ptr SfPlayTilde::create(std::vector const& args) + { + return std::make_unique(args); + } + + SfPlayTilde::SfPlayTilde(std::vector const& args) + { + pushInlet({PinType::IType::Signal, PinType::IType::Control}); + + pushOutlet(PinType::IType::Signal); + + if(!args.empty() && args[0].isInt()) + { + const auto channels = args[0].getInt(); + if(channels > 1) + { + for(auto i = 1; i < channels; ++i) + { + pushOutlet({PinType::IType::Signal}); + } + } + } + + pushOutlet(PinType::IType::Control); // bang when finished + } + + std::string SfPlayTilde::getIODescription(bool is_inlet, size_t index) const + { + if(is_inlet) + { + if(index == 0) + { + return "(msg) Open file, start, stop, int"; + } + } + else + { + if(index < getNumberOfOutlets() - 1) + { + return "Audio Channel " + std::to_string(index + 1); + } + else + { + return "bang when playing ended"; + } + } + + return {}; + } + +}} diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.h b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.h new file mode 100755 index 00000000..ddc6dba7 --- /dev/null +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.h @@ -0,0 +1,49 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#pragma once + +#include + +namespace kiwi { namespace model { + + // ================================================================================ // + // SFPLAY~ // + // ================================================================================ // + + class SfPlayTilde : public model::Object + { + public: + + static void declare(); + + static std::unique_ptr create(std::vector const& args); + + SfPlayTilde(flip::Default& d) + : model::Object(d) + {} + + SfPlayTilde(std::vector const& args); + + std::string getIODescription(bool is_inlet, size_t index) const override; + }; + +}} diff --git a/docs/software/objects.md b/docs/software/objects.md index f6f464ec..058c7f50 100644 --- a/docs/software/objects.md +++ b/docs/software/objects.md @@ -72,3 +72,4 @@ A list of help patches describing how each object works is file_stream (file.createOutputStream()); + + if (file_stream == nullptr) + return false; // abort + + // Now create a WAV writer object that writes to our output stream... + juce::WavAudioFormat wav_format; + juce::AudioFormatWriter* writer = wav_format.createWriterFor(file_stream.get(), m_sample_rate, getNumberOfChannels(), 16, {}, 0); + + if (writer == nullptr) + return false; // abort + + // (passes responsibility for deleting the stream to the writer object that is now using it) + file_stream.release(); + + // Now we'll create one of these helper objects which will act as a FIFO buffer, and will + // write the data to disk on our background thread. + m_threaded_writer = std::make_unique(writer, + m_background_thread, + 32768); + + // And now, swap over our active writer pointer so that the audio callback will start using it.. + const std::lock_guard lock (m_writer_lock); + m_active_writer = m_threaded_writer.get(); + + return true; + } + + void SoundFileRecorder::stop() + { + // First, clear this pointer to stop the audio callback from using our writer object.. + { + const std::lock_guard lock (m_writer_lock); + m_active_writer = nullptr; + } + + // Now we can delete the writer object. It's done in this order because the deletion could + // take a little time while remaining data gets flushed to disk, so it's best to avoid blocking + // the audio callback while this happens. + m_threaded_writer.reset(); + } + + bool SoundFileRecorder::isRecording() const + { + return m_active_writer != nullptr; + } + + void SoundFileRecorder::setNumberOfChannels(size_t channels) + { + m_channels = channels > 0 ? channels : m_channels; + m_buffer_ref.resize(m_channels); + } + + size_t SoundFileRecorder::getNumberOfChannels() const + { + return m_channels; + } + + void SoundFileRecorder::prepare(double sample_rate, size_t vector_size) + { + m_sample_rate = sample_rate; + m_vector_size = vector_size; + } + + bool SoundFileRecorder::write(dsp::Buffer const& input) + { + const std::lock_guard lock (m_writer_lock); + + if (m_active_writer != nullptr) + { + const auto channels = input.getNumberOfChannels(); + for(auto channel = 0; channel < channels; ++channel) + { + m_buffer_ref[channel] = input[channel].data(); + } + + return m_active_writer->write(m_buffer_ref.data(), m_vector_size); + } + + return false; + } + + // ================================================================================ // + // SFRECORD~ // + // ================================================================================ // + + void SfRecordTilde::declare() + { + Factory::add("sfrecord~", &SfRecordTilde::create); + } + + std::unique_ptr SfRecordTilde::create(model::Object const& model, Patcher & patcher) + { + return std::make_unique(model, patcher); + } + + SfRecordTilde::SfRecordTilde(model::Object const& model, Patcher& patcher) + : AudioObject(model, patcher) + { + m_recorder.setNumberOfChannels(getNumberOfInputs()); + } + + SfRecordTilde::~SfRecordTilde() + { + closeFileDialog(); + } + + void SfRecordTilde::receive(size_t index, std::vector const& args) + { + if (!args.empty() && index == 0) + { + if (args[0].isString() && args[0].getString() == "open") + { + if(args.size() > 1) + { + if(args[0].isString()) + { + openFile(juce::File(args[1].getString())); + } + } + else + { + openFileDialog(); + } + } + else if ((args[0].isNumber() && args[0].getInt() == 1) + || args[0].getString() == "start") + { + record(); + } + else if ((args[0].isNumber() && args[0].getInt() == 0) + || args[0].getString() == "stop") + { + stop(); + } + else if (args[0].getString() == "record") + { + double duration_ms = -1; + + if(args.size() > 1 && args[1].isNumber()) + { + duration_ms = args[1].getFloat(); + } + + record(duration_ms); + } + } + } + + bool SfRecordTilde::openFile(juce::File file) + { + const auto path = file.getFullPathName(); + if(!juce::File::isAbsolutePath(path)) + { + warning("sfrecord~: is not an absolute path"); + return false; + } + + if(file.isDirectory()) + { + warning("sfrecord~: invalid file path"); + return false; + } + + // is a file + + if(!file.hasWriteAccess()) + { + warning("sfrecord~: no write access to file \"" + + path.toStdString() + "\""); + return false; + } + + // that has write access + + if(!file.hasFileExtension(m_extension)) + { + file = file.withFileExtension(m_extension); + } + + // and valid extension + + if(!file.exists()) + { + if(!file.create()) + { + warning("sfrecord~: can't create file \"" + path.toStdString() + "\""); + return false; + } + } + else + { + warning("sfrecord~: file will be overwritten \"" + + path.toStdString() + "\""); + } + + // that really exist. + + m_file_to_write = path; + return true; + } + + void SfRecordTilde::openFileDialog() + { + const auto default_dir = juce::File::getSpecialLocation(juce::File::userMusicDirectory); + + auto dir = (!m_file_to_write.getFullPathName().isEmpty() + ? m_file_to_write.getParentDirectory() + : default_dir); + + if (dir.createDirectory().wasOk()) + { + dir = dir.getChildFile("Untitled.wav"); + } + + m_file_chooser = std::make_unique("Choose a file to save...", + dir, "*.wav;", true); + deferMain([this, fc = m_file_chooser.get()]() { + + const int fc_flags = (juce::FileBrowserComponent::saveMode + | juce::FileBrowserComponent::canSelectFiles); + + fc->launchAsync(fc_flags, [this](juce::FileChooser const& chooser) { + auto file = chooser.getResult(); + if(file.getFullPathName().isNotEmpty()) + { + openFile(file); + } + }); + }); + } + + void SfRecordTilde::closeFileDialog() + { + deferMain([this]() { + + m_file_chooser.reset(); + + }); + } + + void SfRecordTilde::stop() + { + defer([this]{ + m_writer_count = 0; + m_recorder.stop(); + }); + } + + void SfRecordTilde::record(double duration_ms) + { + m_writer_count = 0; + m_time_to_stop_ms = duration_ms > 0 ? duration_ms : -1; + m_recorder.start(m_file_to_write); + m_file_to_write = juce::File(); // consumed + } + + void SfRecordTilde::perform(dsp::Buffer const& input, dsp::Buffer& output) noexcept + { + if(m_recorder.write(input)) + { + size_t sampleframe = output[0].size(); + dsp::sample_t* output_sig = output[0].data(); + + while(sampleframe--) + { + *output_sig++ = (m_writer_count++ / m_sample_rate * 1000.); + } + + if((m_time_to_stop_ms > 0) && m_time_to_stop_ms < output_sig[sampleframe-1]) + { + // defered stop + stop(); + m_time_to_stop_ms = 0; + } + } + else + { + output[0].fill(m_writer_count / m_sample_rate * 1000.); + } + } + + void SfRecordTilde::prepare(dsp::Processor::PrepareInfo const& infos) + { + m_sample_rate = infos.sample_rate; + + m_recorder.prepare(infos.sample_rate, infos.vector_size); + setPerformCallBack(this, &SfRecordTilde::perform); + } + + void SfRecordTilde::release() + { + m_sample_rate = 0.; + } + +}} diff --git a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.h b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.h new file mode 100644 index 00000000..90fbbcc0 --- /dev/null +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.h @@ -0,0 +1,118 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#pragma once + +#include +#include +#include +#include + +namespace kiwi { namespace engine { + + // ================================================================================ // + // SOUNDFILE RECORDER // + // ================================================================================ // + + class SoundFileRecorder + { + public: // methods + + SoundFileRecorder(); + + ~SoundFileRecorder(); + + bool start(const juce::File& file); + + void stop(); + + bool isRecording() const; + + bool write(dsp::Buffer const& input); + + void setNumberOfChannels(size_t channels); + + size_t getNumberOfChannels() const; + + void prepare(double sample_rate, size_t vector_size); + + private: + + size_t m_channels; + + std::vector m_buffer_ref; + + juce::TimeSliceThread m_background_thread; + std::unique_ptr m_threaded_writer = nullptr; + + double m_sample_rate = 0.; + size_t m_vector_size = 0; + + std::mutex m_writer_lock; + juce::AudioFormatWriter::ThreadedWriter* m_active_writer {nullptr}; + }; + + // ================================================================================ // + // SFRECORD~ // + // ================================================================================ // + + class SfRecordTilde + : public AudioObject + { + public: // methods + + static void declare(); + + static std::unique_ptr create(model::Object const& model, Patcher & patcher); + + SfRecordTilde(model::Object const& model, Patcher& patcher); + + ~SfRecordTilde(); + + void receive(size_t index, std::vector const& args) override final; + + private: + + void perform(dsp::Buffer const& input, dsp::Buffer& output) noexcept; + void prepare(dsp::Processor::PrepareInfo const& infos) override; + void release() override; + + bool openFile(juce::File file); + void openFileDialog(); + void closeFileDialog(); + + void record(double duration_ms = -1); + void stop(); + + private: + + const std::string m_extension = "wav"; + + juce::File m_file_to_write; + std::unique_ptr m_file_chooser; + + SoundFileRecorder m_recorder; + double m_sample_rate = 0.; + long m_writer_count {0}; + double m_time_to_stop_ms {-1}; + }; + +}} diff --git a/Modules/KiwiModel/KiwiModel_DataModel.cpp b/Modules/KiwiModel/KiwiModel_DataModel.cpp index 80062b26..04275466 100644 --- a/Modules/KiwiModel/KiwiModel_DataModel.cpp +++ b/Modules/KiwiModel/KiwiModel_DataModel.cpp @@ -97,7 +97,8 @@ namespace kiwi model::Float::declare(); model::ClipTilde::declare(); model::Clip::declare(); - model::SfPlayTilde::declare(); + model::SfPlayTilde::declare(); + model::SfRecordTilde::declare(); } void DataModel::init(std::function declare_object) diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h index 59927853..293d8350 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h @@ -86,4 +86,5 @@ #include #include #include +#include diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.cpp new file mode 100755 index 00000000..47c8ec33 --- /dev/null +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.cpp @@ -0,0 +1,90 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#include + +#include + +namespace kiwi { namespace model { + + // ================================================================================ // + // SFRECORD~ // + // ================================================================================ // + + void SfRecordTilde::declare() + { + auto kiwi_class = std::make_unique("sfrecord~", &SfRecordTilde::create); + + auto& flip_class = DataModel::declare() + .name(kiwi_class->getModelName().c_str()) + .inherit(); + + Factory::add(std::move(kiwi_class), flip_class); + } + + std::unique_ptr SfRecordTilde::create(std::vector const& args) + { + return std::make_unique(args); + } + + SfRecordTilde::SfRecordTilde(std::vector const& args) + { + pushInlet({PinType::IType::Signal, PinType::IType::Control}); + pushOutlet(PinType::IType::Signal); + + if(!args.empty() && args[0].isInt()) + { + const auto channels = args[0].getInt(); + if(channels > 1) + { + for(auto i = 1; i < channels; ++i) + { + pushInlet({PinType::IType::Signal}); + } + } + } + } + + std::string SfRecordTilde::getIODescription(bool is_inlet, size_t index) const + { + if(is_inlet) + { + if(index == 0) + { + return "open, start, stop recording, Audio Channel 1"; + } + else + { + return "Audio Channel " + std::to_string(index + 1); + } + } + else + { + if(index == 0) + { + return "(signal) Recording time (ms)"; + } + } + + return {}; + } + +}} diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.h b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.h new file mode 100755 index 00000000..6d565fe1 --- /dev/null +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.h @@ -0,0 +1,49 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#pragma once + +#include + +namespace kiwi { namespace model { + + // ================================================================================ // + // SFRECORD~ // + // ================================================================================ // + + class SfRecordTilde : public model::Object + { + public: + + static void declare(); + + static std::unique_ptr create(std::vector const& args); + + SfRecordTilde(flip::Default& d) + : model::Object(d) + {} + + SfRecordTilde(std::vector const& args); + + std::string getIODescription(bool is_inlet, size_t index) const override; + }; + +}} diff --git a/docs/software/objects.md b/docs/software/objects.md index 058c7f50..048be7da 100644 --- a/docs/software/objects.md +++ b/docs/software/objects.md @@ -73,3 +73,4 @@ A list of help patches describing how each object works is link to download a first example and open the example with Kiwi. +Pease click on this link to download a first example and open the example with Kiwi. diff --git a/docs/software/objects.md b/docs/software/objects.md index 048be7da..f73fdd39 100644 --- a/docs/software/objects.md +++ b/docs/software/objects.md @@ -2,7 +2,7 @@ ## Help patches -A list of help patches describing how each object works is here. The list of help patches is currently incomplete but will evolve later on. +A list of help patches describing how each object works is here. The list of help patches is currently incomplete but will evolve later on. ## List of objects diff --git a/docs/software/tutorials.md b/docs/software/tutorials.md index 60edd721..b17f430d 100644 --- a/docs/software/tutorials.md +++ b/docs/software/tutorials.md @@ -1,6 +1,6 @@ # Tutorials -A set of tutorials is available here. Each tutorial aims at taking you through the creation of a specific audio processing or exploring a certain aspect of Kiwi software. +A set of tutorials is available here. Each tutorial aims at taking you through the creation of a specific audio processing or exploring a certain aspect of Kiwi software. Available tutorials: From d961f6f598e17df3aa28a72e86c834c16346aa11 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Mon, 11 Jun 2018 02:02:24 +0200 Subject: [PATCH 10/41] small fix --- .../KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp index a9e52c8f..4890040b 100644 --- a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp @@ -60,8 +60,7 @@ namespace kiwi { namespace engine { if(start_ms < end_ms) { - end_sample = std::min(static_cast(end_ms * 0.001 * sf_samplerate), - sf_frames); + end_sample = std::min(end_ms * 0.001 * sf_samplerate, sf_frames); } int64_t length = end_sample - start_sample; From b9318e0034832c91958e4e2462719a184a7f4a76 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Mon, 11 Jun 2018 02:03:11 +0200 Subject: [PATCH 11/41] Open kiwi file by dragging it to the console --- .../KiwiApp_Application/KiwiApp_Console.cpp | 44 +++++++++++++++++++ .../KiwiApp_Application/KiwiApp_Console.h | 22 +++++++--- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp b/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp index fd70a687..ba25a607 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp @@ -175,6 +175,44 @@ namespace kiwi erase(); } + // ================================================================================ // + // FileDragAndDropTarget // + // ================================================================================ // + + bool ConsoleContent::isInterestedInFileDrag(juce::StringArray const& files) + { + if(!files.isEmpty()) + { + juce::File file = files[0]; + return file.hasFileExtension(".kiwi"); + } + + return false; + } + + void ConsoleContent::fileDragEnter(juce::StringArray const&, int, int) + { + m_is_dragging_over = true; + repaint(); + } + + void ConsoleContent::fileDragExit(juce::StringArray const&) + { + m_is_dragging_over = false; + repaint(); + } + + void ConsoleContent::filesDropped(juce::StringArray const& files, int x, int y) + { + if(!files.isEmpty()) + { + juce::File file = files[0]; + KiwiApp::useInstance().openFile(file); + m_is_dragging_over = false; + repaint(); + } + } + // ================================================================================ // // TABLE LIST BOX MODEL // // ================================================================================ // @@ -260,6 +298,12 @@ namespace kiwi left += width; } + + if(m_is_dragging_over) + { + g.setColour(juce::Colours::blue.withAlpha(0.5f)); + g.drawRect(getLocalBounds().withTop(m_table.getHeaderHeight()), 5); + } } void ConsoleContent::paintCell(juce::Graphics& g, diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Console.h b/Client/Source/KiwiApp_Application/KiwiApp_Console.h index b5a359bd..dc2f9802 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_Console.h +++ b/Client/Source/KiwiApp_Application/KiwiApp_Console.h @@ -37,11 +37,12 @@ namespace kiwi //! @brief The juce ConsoleContent Component //! @details The juce Console Component maintain a ConsoleHistory and display Console messages to the user. //! The user can select a message to copy it to the system clipboard, delete a specific message or a range of messages, sort messages, double-click on a row to hilight the corresponding object... - class ConsoleContent : - public ConsoleHistory::Listener, - public juce::Component, - public juce::TableListBoxModel, - public juce::TableHeaderComponent::Listener + class ConsoleContent + : public ConsoleHistory::Listener + , public juce::Component + , public juce::TableListBoxModel + , public juce::TableHeaderComponent::Listener + , public juce::FileDragAndDropTarget { public: @@ -125,6 +126,15 @@ namespace kiwi //! @brief Clear all the console content. void clearAll(); + // ================================================================================ // + // FileDragAndDropTarget // + // ================================================================================ // + + bool isInterestedInFileDrag(juce::StringArray const& files) override; + void fileDragEnter(juce::StringArray const&, int, int) override; + void fileDragExit(juce::StringArray const&) override; + void filesDropped(juce::StringArray const& files, int x, int y) override; + // ================================================================================ // // TABLE HEADER COMPONENT LISTENER // // ================================================================================ // @@ -161,6 +171,8 @@ namespace kiwi juce::Font m_font; juce::TableListBox m_table; + bool m_is_dragging_over {false}; + friend class Console; }; From c1dd581c3a8f1c65adbec124b6389281027fce62 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Mon, 11 Jun 2018 12:10:44 +0200 Subject: [PATCH 12/41] incompatible version handling quick-fix --- Client/Source/KiwiApp.cpp | 31 ++++++++++++------- Client/Source/KiwiApp.h | 9 ++++-- .../KiwiApp_Application/KiwiApp_Instance.cpp | 9 ++++-- .../KiwiApp_DocumentBrowser.cpp | 6 ++++ 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/Client/Source/KiwiApp.cpp b/Client/Source/KiwiApp.cpp index dedd35d9..611b127d 100644 --- a/Client/Source/KiwiApp.cpp +++ b/Client/Source/KiwiApp.cpp @@ -333,7 +333,6 @@ namespace kiwi void KiwiApp::setAuthUser(Api::AuthUser const& auth_user) { (*KiwiApp::use().m_api_controller).setAuthUser(auth_user); - KiwiApp::useInstance().login(); } @@ -349,21 +348,31 @@ namespace kiwi KiwiApp::commandStatusChanged(); } + bool KiwiApp::canConnectToServer() + { + return KiwiApp::use().m_same_app_and_server_version; + } + void KiwiApp::checkLatestRelease() { std::string current_version = getApplicationVersion().toStdString(); - Api::CallbackFn on_success = [current_version](std::string const& latest_version) - { - KiwiApp::useScheduler().schedule([current_version, latest_version]() + Api::CallbackFn on_success = [this, current_version](std::string const& server_version) { + + KiwiApp::useScheduler().schedule([this, current_version, server_version]() { + + m_same_app_and_server_version = (current_version == server_version); + if (!m_same_app_and_server_version) { - if (current_version.compare(latest_version) != 0) - { - juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::QuestionIcon, - "New release available" , - "Upgrading required to access remote documents.\n\n Please visit:\n https://github.com/Musicoll/Kiwi/releases"); - } - }); + juce::String title = "Incompatible Application and Server versions."; + juce::String text = "- app: " + juce::String(current_version) + "\n"; + text += "- server: " + juce::String(server_version) + "\n"; + text += "\nPlease visit: https://github.com/Musicoll/Kiwi/releases"; + + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, + title, text); + } + }); }; Api::ErrorCallback on_fail =[](Api::Error error) diff --git a/Client/Source/KiwiApp.h b/Client/Source/KiwiApp.h index ac7cbb2f..00319aa2 100644 --- a/Client/Source/KiwiApp.h +++ b/Client/Source/KiwiApp.h @@ -111,7 +111,10 @@ namespace kiwi static Api::AuthUser const& getCurrentUser(); //! @brief Log-out the user - static void logout(); + static void logout(); + + //! @brief Return true if the application can connect to the server. + static bool canConnectToServer(); //! @brief Get the current running engine instance. static engine::Instance& useEngineInstance(); @@ -252,6 +255,8 @@ namespace kiwi LookAndFeel m_looknfeel; TooltipWindow m_tooltip_window; std::unique_ptr m_settings; - std::unique_ptr> m_scheduler; + std::unique_ptr> m_scheduler; + + bool m_same_app_and_server_version = false; }; } diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp b/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp index be5edcad..b4a5aced 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp @@ -415,10 +415,13 @@ namespace kiwi void Instance::showDocumentBrowserWindow() { - showWindowWithId(WindowId::DocumentBrowser, [&browser = m_browser](){ + showWindowWithId(WindowId::DocumentBrowser, [&browser = m_browser](){ + + const bool can_connect = (KiwiApp::canConnectToServer() + && KiwiApp::getCurrentUser().isLoggedIn()); + return std::make_unique("Document Browser", - std::make_unique(browser, - KiwiApp::getCurrentUser().isLoggedIn()), + std::make_unique(browser, can_connect), true, false, "document_browser_window"); }); } diff --git a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp index 8d099aa8..dd021f21 100644 --- a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp +++ b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp @@ -284,6 +284,12 @@ namespace kiwi void DocumentBrowser::Drive::refresh_internal() { + if(!KiwiApp::canConnectToServer()) + { + m_drive->updateDocumentList({}); + return; + } + std::weak_ptr drive(m_drive); KiwiApp::useApi().getDocuments([drive](Api::Response res, Api::Documents docs) From 234332c11c29ba2380564786f6e50b78cc3b3c60 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Mon, 11 Jun 2018 19:25:55 +0200 Subject: [PATCH 13/41] fix DocumentBrowser tooltip --- .../KiwiApp_DocumentBrowserView.cpp | 8 +++----- .../KiwiApp_Components/KiwiApp_TooltipWindow.cpp | 2 +- Client/Source/KiwiApp_Network/KiwiApp_Api.cpp | 14 ++++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp index ea356479..41ff5ff4 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp @@ -632,14 +632,12 @@ namespace kiwi std::string DocumentBrowserView::DriveView::createDocumentToolTip(DocumentBrowser::Drive::DocumentSession const& doc) { std::string tooltip = "name: " + doc.getName() + "\n" - + "created by : " + doc.getAuthor() + "\n" - + "created at : " + doc.getCreationDate() + "\n" - + "last opened at : " + doc.getOpenedDate() + "\n" - + "last opened by : " + doc.getOpenedUser(); + + "created by: " + doc.getAuthor() + " (" + doc.getCreationDate() + ")\n" + + "last opened by: " + doc.getOpenedUser() + " (" + doc.getOpenedDate() + ")"; if (doc.isTrashed()) { - tooltip += "\ntrashed at : " + doc.getTrashedDate(); + tooltip += "\ntrashed at: " + doc.getTrashedDate(); } return tooltip; diff --git a/Client/Source/KiwiApp_Components/KiwiApp_TooltipWindow.cpp b/Client/Source/KiwiApp_Components/KiwiApp_TooltipWindow.cpp index 54aeddfa..6e0ef7d1 100644 --- a/Client/Source/KiwiApp_Components/KiwiApp_TooltipWindow.cpp +++ b/Client/Source/KiwiApp_Components/KiwiApp_TooltipWindow.cpp @@ -52,7 +52,7 @@ namespace kiwi } setAlwaysOnTop(true); - setOpaque(true); + setOpaque(false); if(parent_comp != nullptr) { diff --git a/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp b/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp index 1904c302..436f30ce 100644 --- a/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp +++ b/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp @@ -20,6 +20,7 @@ */ #include "KiwiApp_Api.h" +#include namespace kiwi { @@ -524,13 +525,14 @@ namespace kiwi std::string Api::convertDate(std::string const& date) { - std::string result = date; + const bool with_date = true; + const bool with_time = true; + const bool with_second = false; + const bool use_24_hour_clock = true; - result.replace(result.find_first_of("T"), 1 , " "); - result.replace(result.find_first_of("Z"), 1 , " "); - result.append("GMT"); - - return result; + return (juce::Time::fromISO8601(juce::String(date)) + .toString(with_date, with_time, with_second, use_24_hour_clock) + .toStdString()); } bool Api::hasJsonHeader(Response const& res) From 2e9076b3999e8f39d0a1b48b179ae7a9ef5bf6ed Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Mon, 11 Jun 2018 23:04:56 +0200 Subject: [PATCH 14/41] update trashed document view --- .../KiwiApp_DocumentBrowserView.cpp | 72 +++++++++++-------- .../KiwiApp_DocumentBrowserView.h | 4 +- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp index 41ff5ff4..a0362a26 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp @@ -39,8 +39,8 @@ namespace kiwi // DOCUMENT BROWSER // // ================================================================================ // - DocumentBrowserView::DocumentBrowserView(DocumentBrowser& browser, bool enabled) : - m_browser(browser) + DocumentBrowserView::DocumentBrowserView(DocumentBrowser& browser, bool enabled) + : m_browser(browser) { setSize(1, 1); @@ -93,20 +93,22 @@ namespace kiwi DocumentBrowserView::DriveView::RowElem::RowElem(DriveView& drive_view, std::string const& name, - std::string const& tooltip) : - m_drive_view(drive_view), - m_name(name), - m_open_btn("open", std::unique_ptr(juce::Drawable::createFromImageData(binary_data::images::open_png, binary_data::images::open_png_size)), ImageButton::ButtonStyle::ImageFitted), - m_kiwi_filetype_img(juce::ImageCache::getFromMemory(binary_data::images::kiwi_filetype_png, - binary_data::images::kiwi_filetype_png_size)), - m_row(-1) + std::string const& tooltip) + : m_drive_view(drive_view) + , m_name(name) + , m_open_btn("open", std::unique_ptr(juce::Drawable::createFromImageData(binary_data::images::open_png, binary_data::images::open_png_size)), ImageButton::ButtonStyle::ImageFitted) + , m_kiwi_filetype_img(juce::ImageCache::getFromMemory(binary_data::images::kiwi_filetype_png, + binary_data::images::kiwi_filetype_png_size)) { setTooltip(tooltip); - m_open_btn.setCommand(std::bind(&DriveView::openDocument, &m_drive_view, m_row)); - m_open_btn.setSize(40, 40); - m_open_btn.setTooltip("open this patcher"); - addChildComponent(m_open_btn); + if(!m_drive_view.isShowingTrashedDocuments()) + { + m_open_btn.setCommand(std::bind(&DriveView::openDocument, &m_drive_view, m_row)); + m_open_btn.setSize(40, 40); + m_open_btn.setTooltip("open this patcher"); + addChildComponent(m_open_btn); + } // label setup m_name_label.setText(m_name, juce::NotificationType::dontSendNotification); @@ -137,35 +139,44 @@ namespace kiwi m_name_label.setText(m_name, juce::NotificationType::dontSendNotification); m_selected = now_selected; - m_open_btn.setVisible(m_selected); + m_open_btn.setVisible(m_selected && !m_drive_view.isShowingTrashedDocuments()); repaint(); } void DocumentBrowserView::DriveView::RowElem::paint(juce::Graphics& g) { + const bool is_trash_row = m_drive_view.isShowingTrashedDocuments(); const auto bounds = getLocalBounds(); - const juce::Colour bg_color(0xDDFFFFFF); + const juce::Colour bg_color(is_trash_row ? 0xDDDDDDDD : 0xDDFFFFFF); const juce::Colour selected_color_color(juce::Colours::lightblue); - g.setColour(m_selected ? selected_color_color : m_mouseover ? bg_color.darker(0.1f) : bg_color); - g.fillAll(); + g.fillAll(((!is_trash_row && m_selected) + ? selected_color_color + : (m_mouseover ? bg_color.darker(0.1f) : bg_color))); - // document status notifier (connected / disconnected / not-connected) - g.setColour(juce::Colours::grey); - g.fillRect(0, 0, 5, getHeight()); + if(!is_trash_row) + { + // document status notifier (connected / disconnected / not-connected) + g.setColour(juce::Colours::grey); + g.fillRect(0, 0, 5, getHeight()); + } g.setColour(bg_color.darker(0.5f)); g.drawHorizontalLine(getBottom() - 1, 0., getWidth()); g.drawImage(m_kiwi_filetype_img, - juce::Rectangle(10, 5, 30, 30), + juce::Rectangle(is_trash_row ? 5 : 10, 5, 30, 30), juce::RectanglePlacement::stretchToFit, false); } void DocumentBrowserView::DriveView::RowElem::resized() { const auto bounds = getLocalBounds(); - m_open_btn.setBounds(bounds.reduced(5).withLeft(bounds.getWidth() - 40)); + + if(!m_drive_view.isShowingTrashedDocuments()) + { + m_open_btn.setBounds(bounds.reduced(5).withLeft(bounds.getWidth() - 40)); + } m_name_label.setBounds(bounds.reduced(5).withRight(m_open_btn.getX() - 5).withLeft(40)); } @@ -185,7 +196,7 @@ namespace kiwi { juce::PopupMenu m; - if (!m_drive_view.getTrashMode()) + if (!m_drive_view.isShowingTrashedDocuments()) { m.addItem(1, "Rename"); m.addItem(2, "Delete"); @@ -354,7 +365,7 @@ namespace kiwi m_create_document_btn.setCommand([this]() { - if (!m_drive_view.getTrashMode()) + if (!m_drive_view.isShowingTrashedDocuments()) m_drive_view.createDocument(); }); m_create_document_btn.setSize(40, 40); @@ -370,12 +381,12 @@ namespace kiwi m_trash_btn.setCommand([this]() { - bool new_trash_mode = !m_drive_view.getTrashMode(); + bool new_trash_mode = !m_drive_view.isShowingTrashedDocuments(); m_drive_view.setTrashMode(new_trash_mode); m_trash_btn.setAlpha(new_trash_mode ? 1. : 0.5); m_create_document_btn.setAlpha(new_trash_mode ? 0.5 : 1.); }); - m_trash_btn.setAlpha(m_drive_view.getTrashMode() ? 1. : 0.5); + m_trash_btn.setAlpha(m_drive_view.isShowingTrashedDocuments() ? 1. : 0.5); m_trash_btn.setSize(40, 40); m_trash_btn.setTooltip("Display trashed documents"); addAndMakeVisible(m_trash_btn); @@ -391,7 +402,7 @@ namespace kiwi m_create_document_btn.setCommand([this]() { - if (!m_drive_view.getTrashMode()) + if (!m_drive_view.isShowingTrashedDocuments()) m_drive_view.createDocument(); }); m_create_document_btn.setAlpha(1); @@ -617,7 +628,7 @@ namespace kiwi }); } - bool DocumentBrowserView::DriveView::getTrashMode() const + bool DocumentBrowserView::DriveView::isShowingTrashedDocuments() const { return m_trash_mode; } @@ -799,6 +810,9 @@ namespace kiwi void DocumentBrowserView::DriveView::listBoxItemDoubleClicked(int row, juce::MouseEvent const& e) { - openDocument(row); + if (!isShowingTrashedDocuments()) + { + openDocument(row); + } } } diff --git a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.h b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.h index 3d4d496e..751db569 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.h +++ b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.h @@ -182,7 +182,7 @@ namespace kiwi void setTrashMode(bool trash_mode); //! @brief Get current mode trash or default. - bool getTrashMode() const; + bool isShowingTrashedDocuments() const; //! @brief Creates document info tooltip. std::string createDocumentToolTip(DocumentBrowser::Drive::DocumentSession const& doc); @@ -302,7 +302,7 @@ namespace kiwi const juce::Image m_kiwi_filetype_img; - int m_row; + int m_row = -1; bool m_selected; bool m_mouseover = false; }; From f91742751bf485bd34c4cad639e93b2af97450a2 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Tue, 12 Jun 2018 23:59:31 +0200 Subject: [PATCH 15/41] initialize colors in LookAndFeel --- .../KiwiApp_General/KiwiApp_LookAndFeel.cpp | 59 +++++++++++++++---- .../KiwiApp_General/KiwiApp_LookAndFeel.h | 5 +- .../KiwiApp_Objects/KiwiApp_ObjectFrame.cpp | 39 +++++------- .../KiwiApp_Objects/KiwiApp_ObjectFrame.h | 14 ----- .../KiwiApp_Objects/KiwiApp_ObjectView.cpp | 8 --- .../KiwiApp_Objects/KiwiApp_ObjectView.h | 3 +- .../KiwiApp_Patcher/KiwiApp_PatcherView.cpp | 7 ++- .../KiwiApp_Patcher/KiwiApp_PatcherView.h | 12 +++- 8 files changed, 84 insertions(+), 63 deletions(-) diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp index f1ef5c6f..0baccec1 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp @@ -20,7 +20,11 @@ */ #include "../KiwiApp_General/KiwiApp_LookAndFeel.h" -#include "../KiwiApp_Ressources/KiwiApp_BinaryData.h" +#include "../KiwiApp_Ressources/KiwiApp_BinaryData.h" + +#include +#include +#include namespace bfonts = kiwi::binary_data::fonts; @@ -40,10 +44,11 @@ namespace kiwi return tl; } - LookAndFeel::LookAndFeel() : juce::LookAndFeel_V4(getGreyColourScheme()) - { - setColour(juce::ScrollBar::ColourIds::thumbColourId, juce::Colours::grey.withAlpha(0.7f)); - setUsingNativeAlertWindows(true); + LookAndFeel::LookAndFeel() + : juce::LookAndFeel_V4(getGreyColourScheme()) + { + setUsingNativeAlertWindows(true); + initColours(); } juce::Typeface::Ptr LookAndFeel::getTypefaceForFont(juce::Font const& font) @@ -226,12 +231,42 @@ namespace kiwi void LookAndFeel::paintToolbarButtonBackground(juce::Graphics& g, int /*width*/, int /*height*/, bool isMouseOver, bool isMouseDown, juce::ToolbarItemComponent& component) - { - /* - if (isMouseDown) - g.fillAll(component.findColour (Toolbar::buttonMouseDownBackgroundColourId, true)); - else if (isMouseOver) - g.fillAll(component.findColour (Toolbar::buttonMouseOverBackgroundColourId, true)); - */ + { + // don't draw toolbar button background + } + + void LookAndFeel::initColours() + { + // ------ Application + + setColour(juce::ScrollBar::ColourIds::thumbColourId, + juce::Colours::grey.withAlpha(0.7f)); + + // ------ patcherview colors + + const auto patcherview_bg = juce::Colour::fromFloatRGBA(0.8, 0.8, 0.8, 1.); + setColour(PatcherView::ColourIds::BackgroundUnlocked, patcherview_bg); + setColour(PatcherView::ColourIds::BackgroundLocked, patcherview_bg); + + const auto selection_color = juce::Colour::fromFloatRGBA(0., 0.5, 1., 1.); + setColour(PatcherView::ColourIds::Selection, selection_color); + setColour(PatcherView::ColourIds::SelectionOtherView, selection_color.contrasting(0.4)); + setColour(PatcherView::ColourIds::SelectionOtherUser, juce::Colour(0xFFFF8C00)); + + // ------ objectbox colors + + const juce::Colour box_bgcolor = juce::Colours::white; + setColour(ObjectView::ColourIds::Pin, juce::Colour(0.3, 0.3, 0.3)); + setColour(ObjectView::ColourIds::Error, juce::Colour::fromRGBA(223, 97, 94, 250)); + setColour(ObjectView::ColourIds::Background, box_bgcolor); + setColour(ObjectView::ColourIds::Text, juce::Colours::black); + setColour(ObjectView::ColourIds::Outline, box_bgcolor.contrasting(0.8)); + setColour(ObjectView::ColourIds::Highlight, juce::Colour::fromFloatRGBA(0., 0.5, 1., 0.4)); + setColour(ObjectView::ColourIds::Active, juce::Colour(0xff21ba90)); + + // ------ link colors + const auto link_bg = juce::Colour(0.2, 0.2, 0.2); + setColour(LinkView::ColourIds::ControlBackground, link_bg); + setColour(LinkView::ColourIds::SignalBackground, link_bg); } } diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h index 3c535e77..c95974e7 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h @@ -73,7 +73,10 @@ namespace kiwi static juce::TextLayout layoutTooltipText(juce::String const& text, juce::Colour colour = juce::Colours::black) noexcept; - private: // deleted methods + private: // deleted methods + + //! @brief Set the default Application components colors + void initColours(); LookAndFeel(LookAndFeel const& other) = delete; LookAndFeel(LookAndFeel && other) = delete; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp index 96ea29cf..00f6ec76 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp @@ -46,7 +46,6 @@ namespace kiwi setInterceptsMouseClicks(pv_locked, pv_locked); m_object_view->lockStatusChanged(pv_locked); - initColours(); updateBoundsFromModel(false); addChildComponent(m_outline); @@ -110,7 +109,7 @@ namespace kiwi { if(!isLocked()) { - g.setColour(findColour(ObjectFrame::ColourIds::Pin)); + g.setColour(findColour(ObjectView::ColourIds::Pin)); drawInletsOutlets(g); } } @@ -226,14 +225,6 @@ namespace kiwi return rect.intersects(bounds) || rect.contains(bounds) || bounds.contains(rect); } - void ObjectFrame::initColours() - { - setColour(ObjectFrame::ColourIds::Selection, juce::Colour::fromFloatRGBA(0., 0.5, 1., 0.8)); - setColour(ObjectFrame::ColourIds::SelectionOtherView, juce::Colour(0xAA9BFF71)); - setColour(ObjectFrame::ColourIds::SelectionDistant, juce::Colour(0xAAFF9B71)); - setColour(ObjectFrame::ColourIds::Pin, juce::Colour(0.3, 0.3, 0.3)); - } - bool ObjectFrame::isSelected() const { return getPatcherView().isSelected(*this); @@ -328,34 +319,36 @@ namespace kiwi if (selected) { - m_outline.setVisible(true); - - m_outline.setResizeColour(findColour(ObjectFrame::ColourIds::Selection)); + m_outline.setResizeColour(findColour(PatcherView::ColourIds::Selection)); if (distant_selected) { - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::SelectionDistant)); + m_outline.setInnerColour(findColour(PatcherView::ColourIds::SelectionOtherUser)); } else if (other_view_selected) { - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::SelectionOtherView)); + m_outline.setInnerColour(findColour(PatcherView::ColourIds::SelectionOtherView)); } else { - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::Selection)); - } + m_outline.setInnerColour(findColour(PatcherView::ColourIds::Selection)); + } + + m_outline.setVisible(true); } else if(distant_selected) - { + { + const auto color = findColour(PatcherView::ColourIds::SelectionOtherUser); + m_outline.setResizeColour(color); + m_outline.setInnerColour(color); m_outline.setVisible(true); - m_outline.setResizeColour(findColour(ObjectFrame::ColourIds::SelectionDistant)); - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::SelectionDistant)); } else if (other_view_selected) - { + { + const auto color = findColour(PatcherView::ColourIds::SelectionOtherView); + m_outline.setResizeColour(color); + m_outline.setInnerColour(color); m_outline.setVisible(true); - m_outline.setResizeColour(findColour(ObjectFrame::ColourIds::SelectionOtherView)); - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::SelectionOtherView)); } else { diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h index 3700cfa7..d16c0ae3 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h @@ -108,16 +108,6 @@ namespace kiwi //! @brief Validate the new width and height for the box void validateSize(int& new_width, int& new_height); - public: // classes - - enum ColourIds - { - Selection = 0x1100000, - SelectionOtherView = 0x1100001, - SelectionDistant = 0x1100002, - Pin = 0x1100003 - }; - public: // classes struct Outline : public juce::Component @@ -205,10 +195,6 @@ namespace kiwi //! @brief Called when the classic view enters its edition mode. void editorShown() override final; - //! @brief Initializes all colours with default values. - //! @todo Set colours in look and feel instead. - void initColours(); - //! @brief Called to update the bounds of the object. void updateBoundsFromModel(bool animate); diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp index 38ced89d..44e80da5 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp @@ -39,14 +39,6 @@ namespace kiwi , m_master(this, [](ObjectView*){}) { object_model.addListener(*this); - - setColour(ObjectView::ColourIds::Error, juce::Colour::fromRGBA(223, 97, 94, 250)); - const juce::Colour bgcolor = juce::Colours::white; - setColour(ObjectView::ColourIds::Background, bgcolor); - setColour(ObjectView::ColourIds::Text, juce::Colours::black); - setColour(ObjectView::ColourIds::Outline, bgcolor.contrasting(0.8)); - setColour(ObjectView::ColourIds::Highlight, juce::Colour::fromFloatRGBA(0., 0.5, 1., 0.4)); - setColour(ObjectView::ColourIds::Active, juce::Colour(0xff21ba90)); } ObjectView::~ObjectView() diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h index 422baac8..5585bd5b 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h @@ -47,7 +47,8 @@ namespace kiwi public: // classes enum ColourIds - { + { + Pin = 0x1100003, Background = 0x1100004, Error = 0x1100005, Text = 0x1100006, diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp index e866d00a..3e039bab 100755 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp @@ -94,9 +94,10 @@ namespace kiwi // ================================================================================ // void PatcherView::paint(juce::Graphics & g) - { - const juce::Colour bgcolor = juce::Colour::fromFloatRGBA(0.8, 0.8, 0.8, 1.); - + { + const auto bgcolor = findColour((isLocked() + ? PatcherView::ColourIds::BackgroundLocked + : PatcherView::ColourIds::BackgroundUnlocked)); if(!isLocked()) { const int grid_size = m_grid_size; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.h index 07892d6e..c3770ccb 100755 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.h @@ -60,7 +60,17 @@ namespace kiwi model::Patcher::View& view); //! @brief Destructor - ~PatcherView(); + ~PatcherView(); + + //! @brief PatcherView colors + enum ColourIds + { + BackgroundUnlocked = 0x2100010, + BackgroundLocked = 0x2100012, + Selection = 0x2100100, + SelectionOtherView = 0x2100105, + SelectionOtherUser = 0x2100110 + }; using ObjectFrames = std::vector>; using LinkViews = std::vector>; From aafa391811611f18638b7a8478868c34c2fa7e77 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Wed, 13 Jun 2018 10:39:37 +0200 Subject: [PATCH 16/41] update patcherview link - update path algorithm - draw a dashed line when more than one user selects the same link - clear selection when a new link is created --- .../KiwiApp_General/KiwiApp_LookAndFeel.cpp | 9 +- .../KiwiApp_Patcher/KiwiApp_LinkView.cpp | 216 ++++++++++++------ .../Source/KiwiApp_Patcher/KiwiApp_LinkView.h | 35 ++- .../KiwiApp_PatcherViewMouseHandler.cpp | 4 +- 4 files changed, 179 insertions(+), 85 deletions(-) diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp index 0baccec1..eb55b1aa 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp @@ -256,7 +256,8 @@ namespace kiwi // ------ objectbox colors const juce::Colour box_bgcolor = juce::Colours::white; - setColour(ObjectView::ColourIds::Pin, juce::Colour(0.3, 0.3, 0.3)); + const juce::Colour patcher_second_color = juce::Colour(0xff444444); + setColour(ObjectView::ColourIds::Pin, patcher_second_color); setColour(ObjectView::ColourIds::Error, juce::Colour::fromRGBA(223, 97, 94, 250)); setColour(ObjectView::ColourIds::Background, box_bgcolor); setColour(ObjectView::ColourIds::Text, juce::Colours::black); @@ -265,8 +266,8 @@ namespace kiwi setColour(ObjectView::ColourIds::Active, juce::Colour(0xff21ba90)); // ------ link colors - const auto link_bg = juce::Colour(0.2, 0.2, 0.2); - setColour(LinkView::ColourIds::ControlBackground, link_bg); - setColour(LinkView::ColourIds::SignalBackground, link_bg); + + setColour(LinkView::ColourIds::ControlBackground, patcher_second_color); + setColour(LinkView::ColourIds::SignalBackground, patcher_second_color); } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.cpp index dcdaf98a..1ef55687 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.cpp @@ -27,41 +27,60 @@ namespace kiwi { // ================================================================================ // // LINK VIEW BASE // - // ================================================================================ // - - void LinkViewBase::updateBounds() - { - const juce::Rectangle link_bounds(m_last_outlet_pos, m_last_inlet_pos); - - const juce::Point local_inlet_pos(m_last_inlet_pos ); - const juce::Point local_outlet_pos(m_last_outlet_pos); - - const juce::Point start_point = local_outlet_pos.translated(0.f, 2.f).toFloat(); - const juce::Point end_point = local_inlet_pos.translated(0.f, -1.f).toFloat(); - - const float max_shift = std::min(link_bounds.getWidth(), link_bounds.getHeight()); - const float shift = (max_shift < 10) ? max_shift * 0.2 : (max_shift * 0.5); - - const juce::Point ctrl_pt1 { start_point.x, static_cast(start_point.y + shift) }; - const juce::Point ctrl_pt2 { end_point.x, static_cast(end_point.y - shift) }; + // ================================================================================ // + + void LinkViewBase::updateBounds() + { + const auto inlet = m_last_inlet_pos; + const auto outlet = m_last_outlet_pos; + const juce::Rectangle link_bounds(outlet, inlet); + + juce::Point start = outlet.translated(0.f, 2.f).toFloat(); + juce::Point end = inlet.translated(0.f, -1.f).toFloat(); juce::Path path; - path.startNewSubPath(start_point.x, start_point.y); - path.cubicTo(ctrl_pt1, ctrl_pt2, end_point); - - setBounds(path.getBounds().toNearestInt().expanded(2, 2)); + if(link_bounds.getWidth() == 0) + { + path.startNewSubPath(start.x, start.y); + path.lineTo(end.x, end.y); + } + else + { + const float width = link_bounds.getWidth(); + const float height = link_bounds.getHeight(); + + const float min = std::min(width, height); + const float max = std::max(width, height); + + const float max_shift_y = 20.f; + const float max_shift_x = 20.f; + + const float shift_y = std::min(max_shift_y, max * 0.5); + + const float shift_x = ((start.y >= end.y) + ? std::min(max_shift_x, min * 0.5) + : 0.f) * ((start.x < end.x) ? -1. : 1.); + + const juce::Point ctrl_pt1 { start.x - shift_x, start.y + shift_y }; + const juce::Point ctrl_pt2 { end.x + shift_x, end.y - shift_y }; + + path.startNewSubPath(start); + path.cubicTo(ctrl_pt1, ctrl_pt2, end); + } + + setBounds(path.getBounds().toNearestInt().expanded(2, 2)); path.applyTransform(juce::AffineTransform::translation(-1 * getX(), -1 * getY())); - m_path = path; - } + m_path = path; + } // ================================================================================ // // LINK VIEW // // ================================================================================ // - LinkView::LinkView(PatcherView& patcherview, model::Link& link_m) : - m_patcherview(patcherview), - m_model(&link_m) + LinkView::LinkView(PatcherView& patcherview, model::Link& link_m) + : m_patcherview(patcherview) + , m_model(&link_m) { auto& sender_object_m = m_model->getSenderObject(); auto& receiver_object_m = m_model->getReceiverObject(); @@ -97,11 +116,16 @@ namespace kiwi { jbox_receiver->removeComponentListener(this); } + } + + model::Link& LinkView::getModel() const + { + return *m_model; } bool LinkView::isSelected() const noexcept { - return m_is_selected; + return m_selected.on_this_view; } void LinkView::linkChanged(model::Link& link) @@ -116,32 +140,68 @@ namespace kiwi void LinkView::localSelectionChanged(bool selected) { - if(m_is_selected != selected) + if(m_selected.on_this_view != selected) { - m_is_selected = selected; + m_selected.on_this_view = selected; repaint(); } } void LinkView::distantSelectionChanged(std::set distant_user_id_selection) - { - m_distant_selection = distant_user_id_selection; - repaint(); + { + const bool was_selected_in_another_view = m_selected.in_another_view; + const bool was_selected_by_another_user = m_selected.by_another_user; + + bool should_repaint = false; + if(distant_user_id_selection.empty()) + { + should_repaint = (was_selected_in_another_view || was_selected_by_another_user); + m_selected.in_another_view = false; + m_selected.by_another_user = false; + } + else + { + const auto user_id = KiwiApp::userID(); + for(auto distant_user_id : distant_user_id_selection) + { + if(distant_user_id == user_id) + { + m_selected.in_another_view = true; + } + else + { + m_selected.by_another_user = true; + } + + if(m_selected.in_another_view && m_selected.by_another_user) + { + break; + } + } + + should_repaint = ((was_selected_in_another_view != m_selected.in_another_view) + || (was_selected_in_another_view != m_selected.by_another_user)); + } + + if(should_repaint) + { + repaint(); + } } - void LinkView::componentMovedOrResized(Component& component, bool /*was_moved*/, bool /*was_resized*/) + void LinkView::componentMovedOrResized(Component& component, + bool /*was_moved*/, bool /*was_resized*/) { - ObjectFrame* jbox = dynamic_cast(&component); - if(jbox) + if(auto* box = dynamic_cast(&component)) { - if(&jbox->getModel() == &m_model->getSenderObject()) + if(&box->getModel() == &m_model->getSenderObject()) { - m_last_outlet_pos = jbox->getOutletPatcherPosition(m_model->getSenderIndex()); + m_last_outlet_pos = box->getOutletPatcherPosition(m_model->getSenderIndex()); updateBounds(); } - else if(&jbox->getModel() == &m_model->getReceiverObject()) + else if(&box->getModel() == &m_model->getReceiverObject()) { - m_last_inlet_pos = jbox->getInletPatcherPosition(m_model->getReceiverIndex()); + m_last_inlet_pos = box->getInletPatcherPosition(m_model->getReceiverIndex()); updateBounds(); } } @@ -150,39 +210,51 @@ namespace kiwi bool LinkView::isSignal() const { return m_model->isSignal(); - } - - void LinkView::paint(juce::Graphics & g) - { - const juce::Colour link_color = isSignal() ? - juce::Colour::fromFloatRGBA(0.2, 0.8, 0.2, 1.) : - juce::Colour::fromFloatRGBA(0.2, 0.2, 0.2, 1.); - - const juce::Colour selection_color = juce::Colour::fromFloatRGBA(0., 0.5, 1., 1.); - const juce::Colour other_view_selected_color = juce::Colour::fromFloatRGBA(0.8, 0.3, 0.3, 1.); - const juce::Colour distant_selected_color(0xAAFF9B71); - - const bool selected = m_is_selected; - const bool other_selected = ! m_distant_selection.empty(); - const bool other_view_selected = (other_selected && - m_distant_selection.find(KiwiApp::userID()) - != m_distant_selection.end()); - - const bool distant_selected = ((other_selected && (other_view_selected && m_distant_selection.size() > 1)) - || (other_selected && !other_view_selected)); - - g.setColour(link_color); - g.strokePath(m_path, juce::PathStrokeType(2.f)); - - juce::Colour inner_color = link_color.contrasting(0.4); - - if(selected || other_view_selected || distant_selected) - { - inner_color = selected ? selection_color : other_view_selected ? other_view_selected_color : distant_selected_color; - } - - g.setColour(inner_color); - g.strokePath(m_path, juce::PathStrokeType(1.f)); + } + + void LinkView::paint(juce::Graphics & g) + { + const bool selected_by_other = (m_selected.by_another_user + || m_selected.in_another_view); + + const float stroke = (isSignal() ? 2.f : 1.f); + + juce::Colour bgcolor; + if(m_selected.on_this_view) + { + bgcolor = findColour(PatcherView::ColourIds::Selection); + } + else if (m_selected.by_another_user) + { + bgcolor = findColour(PatcherView::ColourIds::SelectionOtherUser); + } + else if (m_selected.in_another_view) + { + bgcolor = findColour(PatcherView::ColourIds::SelectionOtherView); + } + else + { + bgcolor = findColour(isSignal() + ? LinkView::ColourIds::SignalBackground + : LinkView::ColourIds::ControlBackground); + } + + g.setColour(bgcolor); + g.strokePath(m_path, juce::PathStrokeType(stroke)); + + if(m_selected.on_this_view && selected_by_other) + { + g.setColour(findColour(m_selected.by_another_user + ? PatcherView::ColourIds::SelectionOtherUser + : PatcherView::ColourIds::SelectionOtherView) + .withAlpha(1.f)); + + juce::Path path; + const juce::PathStrokeType path_stroke(stroke * 0.5); + float const dashed_length[2] {10.f, 10.f}; + path_stroke.createDashedStroke(path, m_path, dashed_length, 2); + g.strokePath(path, path_stroke); + } } bool LinkView::hitTest(juce::Point const& pt, HitTester& hit) const diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.h index 8ec6e671..a271f162 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.h @@ -63,10 +63,17 @@ namespace kiwi LinkView(PatcherView& patcherview, model::Link& link_m); //! @brief Destructor - ~LinkView(); + ~LinkView(); + + //! @brief LinkView colors + enum ColourIds + { + ControlBackground = 0x2110010, + SignalBackground = 0x2110012, + }; //! @brief Get the Link model - model::Link& getModel() const {return *m_model;}; + model::Link& getModel() const; //! Returns true if the link is selected. bool isSelected() const noexcept; @@ -92,14 +99,26 @@ namespace kiwi //! @param component the component that was moved or resized //! @param wasMoved true if the component's top-left corner has just moved //! @param wasResized true if the component's width or height has just changed - void componentMovedOrResized(Component& component, bool was_moved, bool was_resized) override; - - private: // members + void componentMovedOrResized(Component& component, + bool was_moved, bool was_resized) override; + + private: // members + + struct Selection + { + bool on_this_view = false; + bool in_another_view = false; + bool by_another_user = false; + + operator bool () const + { + return (on_this_view || in_another_view || by_another_user); + } + }; PatcherView& m_patcherview; - model::Link* m_model; - bool m_is_selected = 0; - std::set m_distant_selection; + model::Link* m_model {nullptr}; + Selection m_selected {}; }; // ================================================================================ // diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp index ab22e50c..e12b5e41 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp @@ -83,7 +83,9 @@ namespace kiwi } case Action::CreateLink: { - ObjectFrame & object = *hit_tester.getObject(); + m_patcher_view.unselectAll(); + + auto& object = *hit_tester.getObject(); const size_t index = hit_tester.getIndex(); From d9b6b168728a52fd19fecfde78ad247729c0d144 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Wed, 13 Jun 2018 16:57:00 +0200 Subject: [PATCH 17/41] patcherview boxes polish --- .../KiwiApp_General/KiwiApp_LookAndFeel.cpp | 461 +++++++++--------- .../KiwiApp_General/KiwiApp_LookAndFeel.h | 169 ++++--- .../KiwiApp_Objects/KiwiApp_BangView.cpp | 18 +- .../KiwiApp_Objects/KiwiApp_ClassicView.cpp | 10 +- .../KiwiApp_Objects/KiwiApp_CommentView.cpp | 4 +- .../KiwiApp_Objects/KiwiApp_MessageView.cpp | 37 +- .../KiwiApp_MeterTildeView.cpp | 110 +++-- .../KiwiApp_Objects/KiwiApp_MeterTildeView.h | 23 +- .../KiwiApp_NumberViewBase.cpp | 2 - .../KiwiApp_Objects/KiwiApp_ObjectView.cpp | 9 +- .../KiwiApp_Objects/KiwiApp_ObjectView.h | 5 +- .../KiwiApp_Objects/KiwiApp_SliderView.cpp | 16 +- .../KiwiApp_Objects/KiwiApp_SliderView.h | 5 +- .../KiwiApp_Objects/KiwiApp_ToggleView.cpp | 41 +- .../KiwiApp_PatcherViewMouseHandler.cpp | 2 +- 15 files changed, 475 insertions(+), 437 deletions(-) diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp index eb55b1aa..eaea3b69 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp @@ -1,236 +1,242 @@ -/* - ============================================================================== - - This file is part of the KIWI library. - - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. - - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. - - Permission is granted to use this software under the terms of the GPL v3 - (or any later version). Details can be found at: www.gnu.org/licenses - - KIWI 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. - - ------------------------------------------------------------------------------ - - Contact : cicm.mshparisnord@gmail.com - - ============================================================================== - */ - -#include "../KiwiApp_General/KiwiApp_LookAndFeel.h" +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#include "../KiwiApp_General/KiwiApp_LookAndFeel.h" #include "../KiwiApp_Ressources/KiwiApp_BinaryData.h" #include #include -#include - -namespace bfonts = kiwi::binary_data::fonts; - -namespace kiwi -{ - juce::TextLayout LookAndFeel::layoutTooltipText(juce::String const& text, juce::Colour colour) noexcept - { - const float font_size = 13.0f; - const int max_width = 400; - - juce::AttributedString s; - s.setJustification(juce::Justification::centred); - s.append(text, juce::Font(font_size, juce::Font::bold), colour); - - juce::TextLayout tl; - tl.createLayoutWithBalancedLineLengths(s, (float) max_width); - return tl; - } - +#include + +namespace bfonts = kiwi::binary_data::fonts; + +namespace kiwi +{ LookAndFeel::LookAndFeel() - : juce::LookAndFeel_V4(getGreyColourScheme()) + : juce::LookAndFeel_V4(getGreyColourScheme()) { setUsingNativeAlertWindows(true); - initColours(); - } - - juce::Typeface::Ptr LookAndFeel::getTypefaceForFont(juce::Font const& font) - { - juce::Typeface::Ptr typeface; - - if (font.getTypefaceName() == juce::Font::getDefaultSansSerifFontName()) - { - if (font.getStyleFlags() & juce::Font::FontStyleFlags::bold) - { - if (font.getStyleFlags() & juce::Font::FontStyleFlags::italic) - { - typeface = - juce::Typeface::createSystemTypefaceFor(bfonts::open_sans::OpenSans_BoldItalic_ttf, - bfonts::open_sans::OpenSans_BoldItalic_ttf_size); - } - else - { - typeface = - juce::Typeface::createSystemTypefaceFor(bfonts::open_sans::OpenSans_Bold_ttf, - bfonts::open_sans::OpenSans_Bold_ttf_size); - } - } - else if(font.getStyleFlags() & juce::Font::FontStyleFlags::italic) - { - typeface = - juce::Typeface::createSystemTypefaceFor(bfonts::open_sans::OpenSans_SemiboldItalic_ttf, - bfonts::open_sans::OpenSans_SemiboldItalic_ttf_size); - } - else - { - typeface = - juce::Typeface::createSystemTypefaceFor(bfonts::open_sans::OpenSans_Semibold_ttf, - bfonts::open_sans::OpenSans_Semibold_ttf_size); - } - } - - if (typeface == nullptr) - { - typeface = juce::Font::getDefaultTypefaceForFont (font); - } - - return typeface; - } - - void LookAndFeel::drawPropertyPanelSectionHeader(juce::Graphics& g, - const juce::String& name, - bool is_open, int width, int height) - { - //@see drawPropertyPanelCategoryHeader - const float button_size = height * 0.75f; - const float button_indent = (height - button_size) * 0.5f; - - //@see drawConcertinaPanelHeader - const juce::Colour bkg(juce::Colours::grey); - g.setGradientFill(juce::ColourGradient(bkg.withAlpha(0.2f), 0.f, 0.f, - bkg.withAlpha(0.4f), 0., height, - false)); - g.fillAll(); - - g.setColour(bkg.contrasting(0.5).withAlpha(0.1f)); - g.drawHorizontalLine(0.f, 0.f, width); - g.drawHorizontalLine(height - 1, 0.f, width); - - const int text_indent = (int)(button_indent * 2.0f + button_size + 2.0f); - - g.setColour(bkg.contrasting()); - g.setFont(juce::Font(height * 0.6f).boldened()); - g.drawFittedText(name, text_indent, 0, width - text_indent - 4, height, - juce::Justification::centredLeft, 1); - - //@see drawTreeviewPlusMinusBox - const juce::Rectangle plusRect{button_indent, button_indent, button_size, button_size}; - - juce::Path p; - p.addTriangle(0.0f, 0.0f, 1.0f, is_open ? 0.0f : 0.5f, is_open ? 0.5f : 0.0f, 1.0f); - g.setColour(juce::Colours::white.contrasting().withAlpha(0.5f)); - g.fillPath(p, p.getTransformToScaleToFit(plusRect.reduced(2, plusRect.getHeight() / 4), true)); - } - + initColours(); + } + + float LookAndFeel::getObjectBorderSize() const + { + return m_box_border_size; + } + + juce::TextLayout LookAndFeel::layoutTooltipText(juce::String const& text, juce::Colour colour) noexcept + { + const float font_size = 13.0f; + const int max_width = 400; + + juce::AttributedString s; + s.setColour(colour); + s.setJustification(juce::Justification::centred); + s.append(text, juce::Font(font_size, juce::Font::bold), colour); + + juce::TextLayout tl; + tl.createLayoutWithBalancedLineLengths(s, (float) max_width); + return tl; + } + + juce::Typeface::Ptr LookAndFeel::getTypefaceForFont(juce::Font const& font) + { + juce::Typeface::Ptr typeface; + + if (font.getTypefaceName() == juce::Font::getDefaultSansSerifFontName()) + { + if (font.getStyleFlags() & juce::Font::FontStyleFlags::bold) + { + if (font.getStyleFlags() & juce::Font::FontStyleFlags::italic) + { + typeface = + juce::Typeface::createSystemTypefaceFor(bfonts::open_sans::OpenSans_BoldItalic_ttf, + bfonts::open_sans::OpenSans_BoldItalic_ttf_size); + } + else + { + typeface = + juce::Typeface::createSystemTypefaceFor(bfonts::open_sans::OpenSans_Bold_ttf, + bfonts::open_sans::OpenSans_Bold_ttf_size); + } + } + else if(font.getStyleFlags() & juce::Font::FontStyleFlags::italic) + { + typeface = + juce::Typeface::createSystemTypefaceFor(bfonts::open_sans::OpenSans_SemiboldItalic_ttf, + bfonts::open_sans::OpenSans_SemiboldItalic_ttf_size); + } + else + { + typeface = + juce::Typeface::createSystemTypefaceFor(bfonts::open_sans::OpenSans_Semibold_ttf, + bfonts::open_sans::OpenSans_Semibold_ttf_size); + } + } + + if (typeface == nullptr) + { + typeface = juce::Font::getDefaultTypefaceForFont (font); + } + + return typeface; + } + + void LookAndFeel::drawPropertyPanelSectionHeader(juce::Graphics& g, + const juce::String& name, + bool is_open, int width, int height) + { + //@see drawPropertyPanelCategoryHeader + const float button_size = height * 0.75f; + const float button_indent = (height - button_size) * 0.5f; + + //@see drawConcertinaPanelHeader + const juce::Colour bkg(juce::Colours::grey); + g.setGradientFill(juce::ColourGradient(bkg.withAlpha(0.2f), 0.f, 0.f, + bkg.withAlpha(0.4f), 0., height, + false)); + g.fillAll(); + + g.setColour(bkg.contrasting(0.5).withAlpha(0.1f)); + g.drawHorizontalLine(0.f, 0.f, width); + g.drawHorizontalLine(height - 1, 0.f, width); + + const int text_indent = (int)(button_indent * 2.0f + button_size + 2.0f); + + g.setColour(bkg.contrasting()); + g.setFont(juce::Font(height * 0.6f).boldened()); + g.drawFittedText(name, text_indent, 0, width - text_indent - 4, height, + juce::Justification::centredLeft, 1); + + //@see drawTreeviewPlusMinusBox + const juce::Rectangle plusRect{button_indent, button_indent, button_size, button_size}; + + juce::Path p; + p.addTriangle(0.0f, 0.0f, 1.0f, is_open ? 0.0f : 0.5f, is_open ? 0.5f : 0.0f, 1.0f); + g.setColour(juce::Colours::white.contrasting().withAlpha(0.5f)); + g.fillPath(p, p.getTransformToScaleToFit(plusRect.reduced(2, plusRect.getHeight() / 4), true)); + } + void LookAndFeel::drawTableHeaderColumn(juce::Graphics& g, juce::TableHeaderComponent&, - juce::String const& columnName, - int /*columnId*/, - int width, int height, - bool isMouseOver, bool isMouseDown, - int columnFlags) - { - if(isMouseDown) - g.fillAll(juce::Colours::white.withAlpha(0.2f)); - else if(isMouseOver) - g.fillAll(juce::Colours::white.withAlpha(0.1f)); - - juce::Rectangle area(width, height); - area.reduce(4, 0); - - if ((columnFlags & (juce::TableHeaderComponent::sortedForwards | juce::TableHeaderComponent::sortedBackwards)) != 0) - { - juce::Path sortArrow; - sortArrow.addTriangle (0.0f, 0.0f, - 0.5f, (columnFlags & juce::TableHeaderComponent::sortedForwards) != 0 ? -0.8f : 0.8f, - 1.0f, 0.0f); - - g.setColour(juce::Colour (0x99000000)); - g.fillPath(sortArrow, sortArrow.getTransformToScaleToFit(area.removeFromRight(height / 2).reduced (2).toFloat(), true)); - } - - g.setColour(getCurrentColourScheme() - .getUIColour(juce::LookAndFeel_V4::ColourScheme::UIColour::windowBackground) - .contrasting(0.9)); - - g.setFont(juce::Font(height * 0.5f, juce::Font::bold)); - g.drawFittedText(columnName, area, juce::Justification::centredLeft, 1); - } - - void LookAndFeel::drawTableHeaderBackground(juce::Graphics& g, juce::TableHeaderComponent& header) - { - auto r(header.getLocalBounds()); - - const auto bdcolor = juce::Colours::black.withAlpha(0.5f); - - g.setColour(getCurrentColourScheme() - .getUIColour(juce::LookAndFeel_V4::ColourScheme::UIColour::windowBackground)); - - g.fillRect(r); - - g.setColour(bdcolor); - g.fillRect(r.removeFromBottom(1)); - - g.setColour(bdcolor); - for(int i = header.getNumColumns(true) - 1; --i >= 0;) - { - g.fillRect(header.getColumnPosition(i).removeFromRight(1)); - } - } - - void LookAndFeel::drawButtonBackground(juce::Graphics& g, juce::Button& btn, - juce::Colour const& bgcolor, - bool mouse_over, bool mouse_down) - { - juce::Colour baseColour(bgcolor.withMultipliedSaturation(btn.hasKeyboardFocus (true) ? 1.3f : 0.9f).withMultipliedAlpha(btn.isEnabled() ? 0.9f : 0.5f)); - - if (mouse_down || mouse_over) - baseColour = baseColour.contrasting(mouse_down ? 0.2f : 0.1f); - - const bool flatOnLeft = btn.isConnectedOnLeft(); - const bool flatOnRight = btn.isConnectedOnRight(); - const bool flatOnTop = btn.isConnectedOnTop(); - const bool flatOnBottom = btn.isConnectedOnBottom(); - - const float width = btn.getWidth() - 1.0f; - const float height = btn.getHeight() - 1.0f; - - if(width > 0 && height > 0) - { - const float cornerSize = 1.0f; - - juce::Path outline; - outline.addRoundedRectangle(0.5f, 0.5f, width, height, cornerSize, cornerSize, - ! (flatOnLeft || flatOnTop), - ! (flatOnRight || flatOnTop), - ! (flatOnLeft || flatOnBottom), - ! (flatOnRight || flatOnBottom)); - - - - g.setColour(baseColour); - g.fillPath(outline); - - g.setColour(btn.hasKeyboardFocus(false) ? baseColour.contrasting(0.5) : baseColour.contrasting(0.2)); - g.strokePath(outline, juce::PathStrokeType(1.0f)); - } - } - - void LookAndFeel::paintToolbarBackground(juce::Graphics& g, int w, int h, juce::Toolbar& toolbar) - { - g.fillAll(toolbar.findColour(juce::Toolbar::backgroundColourId)); - } - - void LookAndFeel::paintToolbarButtonBackground(juce::Graphics& g, int /*width*/, int /*height*/, - bool isMouseOver, bool isMouseDown, - juce::ToolbarItemComponent& component) + juce::String const& columnName, + int /*columnId*/, + int width, int height, + bool isMouseOver, bool isMouseDown, + int columnFlags) + { + if(isMouseDown) + g.fillAll(juce::Colours::white.withAlpha(0.2f)); + else if(isMouseOver) + g.fillAll(juce::Colours::white.withAlpha(0.1f)); + + juce::Rectangle area(width, height); + area.reduce(4, 0); + + if ((columnFlags & (juce::TableHeaderComponent::sortedForwards | juce::TableHeaderComponent::sortedBackwards)) != 0) + { + juce::Path sortArrow; + sortArrow.addTriangle (0.0f, 0.0f, + 0.5f, (columnFlags & juce::TableHeaderComponent::sortedForwards) != 0 ? -0.8f : 0.8f, + 1.0f, 0.0f); + + g.setColour(juce::Colour (0x99000000)); + g.fillPath(sortArrow, sortArrow.getTransformToScaleToFit(area.removeFromRight(height / 2).reduced (2).toFloat(), true)); + } + + g.setColour(getCurrentColourScheme() + .getUIColour(juce::LookAndFeel_V4::ColourScheme::UIColour::windowBackground) + .contrasting(0.9)); + + g.setFont(juce::Font(height * 0.5f, juce::Font::bold)); + g.drawFittedText(columnName, area, juce::Justification::centredLeft, 1); + } + + void LookAndFeel::drawTableHeaderBackground(juce::Graphics& g, juce::TableHeaderComponent& header) + { + auto r(header.getLocalBounds()); + + const auto bdcolor = juce::Colours::black.withAlpha(0.5f); + + g.setColour(getCurrentColourScheme() + .getUIColour(juce::LookAndFeel_V4::ColourScheme::UIColour::windowBackground)); + + g.fillRect(r); + + g.setColour(bdcolor); + g.fillRect(r.removeFromBottom(1)); + + g.setColour(bdcolor); + for(int i = header.getNumColumns(true) - 1; --i >= 0;) + { + g.fillRect(header.getColumnPosition(i).removeFromRight(1)); + } + } + + void LookAndFeel::drawButtonBackground(juce::Graphics& g, juce::Button& btn, + juce::Colour const& bgcolor, + bool mouse_over, bool mouse_down) + { + juce::Colour baseColour(bgcolor.withMultipliedSaturation(btn.hasKeyboardFocus (true) ? 1.3f : 0.9f).withMultipliedAlpha(btn.isEnabled() ? 0.9f : 0.5f)); + + if (mouse_down || mouse_over) + baseColour = baseColour.contrasting(mouse_down ? 0.2f : 0.1f); + + const bool flatOnLeft = btn.isConnectedOnLeft(); + const bool flatOnRight = btn.isConnectedOnRight(); + const bool flatOnTop = btn.isConnectedOnTop(); + const bool flatOnBottom = btn.isConnectedOnBottom(); + + const float width = btn.getWidth() - 1.0f; + const float height = btn.getHeight() - 1.0f; + + if(width > 0 && height > 0) + { + const float cornerSize = 1.0f; + + juce::Path outline; + outline.addRoundedRectangle(0.5f, 0.5f, width, height, cornerSize, cornerSize, + ! (flatOnLeft || flatOnTop), + ! (flatOnRight || flatOnTop), + ! (flatOnLeft || flatOnBottom), + ! (flatOnRight || flatOnBottom)); + + + + g.setColour(baseColour); + g.fillPath(outline); + + g.setColour(btn.hasKeyboardFocus(false) ? baseColour.contrasting(0.5) : baseColour.contrasting(0.2)); + g.strokePath(outline, juce::PathStrokeType(1.0f)); + } + } + + void LookAndFeel::paintToolbarBackground(juce::Graphics& g, int w, int h, juce::Toolbar& toolbar) + { + g.fillAll(toolbar.findColour(juce::Toolbar::backgroundColourId)); + } + + void LookAndFeel::paintToolbarButtonBackground(juce::Graphics& g, int /*width*/, int /*height*/, + bool isMouseOver, bool isMouseDown, + juce::ToolbarItemComponent& component) { // don't draw toolbar button background } @@ -261,7 +267,7 @@ namespace kiwi setColour(ObjectView::ColourIds::Error, juce::Colour::fromRGBA(223, 97, 94, 250)); setColour(ObjectView::ColourIds::Background, box_bgcolor); setColour(ObjectView::ColourIds::Text, juce::Colours::black); - setColour(ObjectView::ColourIds::Outline, box_bgcolor.contrasting(0.8)); + setColour(ObjectView::ColourIds::Outline, box_bgcolor.contrasting(0.4)); setColour(ObjectView::ColourIds::Highlight, juce::Colour::fromFloatRGBA(0., 0.5, 1., 0.4)); setColour(ObjectView::ColourIds::Active, juce::Colour(0xff21ba90)); @@ -269,5 +275,6 @@ namespace kiwi setColour(LinkView::ColourIds::ControlBackground, patcher_second_color); setColour(LinkView::ColourIds::SignalBackground, patcher_second_color); - } -} + } +} + diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h index c95974e7..11f193ef 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h @@ -1,86 +1,95 @@ -/* - ============================================================================== - - This file is part of the KIWI library. - - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. - - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. - - Permission is granted to use this software under the terms of the GPL v3 - (or any later version). Details can be found at: www.gnu.org/licenses - - KIWI 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. - - ------------------------------------------------------------------------------ - - Contact : cicm.mshparisnord@gmail.com - - ============================================================================== - */ - -#pragma once - -#include - -namespace kiwi -{ - class LookAndFeel final : public juce::LookAndFeel_V4 - { - public: // methods - - //! @brief Constructor. - LookAndFeel(); - - //! @brief Destructor. - ~LookAndFeel() = default; - - //! @brief Returns the typeface for a certain font name and style. - //! @details LookAndFeel doesn't cache typeface because juce has its own caching mechanism - juce::Typeface::Ptr getTypefaceForFont(juce::Font const& font) override; - - //! @brief Overriden to draw a custom PropertyPanel section header - void drawPropertyPanelSectionHeader(juce::Graphics& g, - const juce::String& name, - bool is_open, int width, int height) override; - - //! @brief Overriden to draw a custom Table header background. - void drawTableHeaderBackground(juce::Graphics& g, juce::TableHeaderComponent& header) override; - - //! @brief Overriden to draw a custom Table header column. +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#pragma once + +#include + +namespace kiwi +{ + class LookAndFeel + : public juce::LookAndFeel_V4 + { + public: // methods + + //! @brief Constructor. + LookAndFeel(); + + //! @brief Destructor. + ~LookAndFeel() = default; + + //! @brief Return the default border size of an object. + float getObjectBorderSize() const; + + //! @brief Returns the typeface for a certain font name and style. + //! @details LookAndFeel doesn't cache typeface because juce has its own caching mechanism + juce::Typeface::Ptr getTypefaceForFont(juce::Font const& font) override; + + //! @brief Overriden to draw a custom PropertyPanel section header + void drawPropertyPanelSectionHeader(juce::Graphics& g, + const juce::String& name, + bool is_open, int width, int height) override; + + //! @brief Overriden to draw a custom Table header background. + void drawTableHeaderBackground(juce::Graphics& g, juce::TableHeaderComponent& header) override; + + //! @brief Overriden to draw a custom Table header column. void drawTableHeaderColumn(juce::Graphics& g, juce::TableHeaderComponent&, juce::String const& columnName, - int /*columnId*/, - int width, int height, - bool isMouseOver, bool isMouseDown, - int columnFlags) override; - - //! @brief Custom Button background drawing - void drawButtonBackground(juce::Graphics& g, juce::Button& b, - juce::Colour const& bgcolor, - bool mouse_over, bool mouse_down) override; - - //! @brief Custom Toolbar background drawing - void paintToolbarBackground(juce::Graphics& g, int w, int h, juce::Toolbar& toolbar) override; - - //! @brief Custom Toolbar Button background drawing - void paintToolbarButtonBackground(juce::Graphics& g, int /*width*/, int /*height*/, - bool isMouseOver, bool isMouseDown, - juce::ToolbarItemComponent& component) override; - - //! @brief Make a textLayout for tootips - static juce::TextLayout layoutTooltipText(juce::String const& text, - juce::Colour colour = juce::Colours::black) noexcept; - + int /*columnId*/, + int width, int height, + bool isMouseOver, bool isMouseDown, + int columnFlags) override; + + //! @brief Custom Button background drawing + void drawButtonBackground(juce::Graphics& g, juce::Button& b, + juce::Colour const& bgcolor, + bool mouse_over, bool mouse_down) override; + + //! @brief Custom Toolbar background drawing + void paintToolbarBackground(juce::Graphics& g, int w, int h, juce::Toolbar& toolbar) override; + + //! @brief Custom Toolbar Button background drawing + void paintToolbarButtonBackground(juce::Graphics& g, int /*width*/, int /*height*/, + bool isMouseOver, bool isMouseDown, + juce::ToolbarItemComponent& component) override; + + //! @brief Make a textLayout for tootips + static juce::TextLayout layoutTooltipText(juce::String const& text, + juce::Colour colour = juce::Colours::black) noexcept; + private: // deleted methods //! @brief Set the default Application components colors - void initColours(); - - LookAndFeel(LookAndFeel const& other) = delete; - LookAndFeel(LookAndFeel && other) = delete; - LookAndFeel& operator=(LookAndFeel const& other) = delete; - LookAndFeel& operator=(LookAndFeel && other) = delete; - }; -} + void initColours(); + + private: // variables + + float m_box_border_size = 1.f; + + LookAndFeel(LookAndFeel const& other) = delete; + LookAndFeel(LookAndFeel && other) = delete; + LookAndFeel& operator=(LookAndFeel const& other) = delete; + LookAndFeel& operator=(LookAndFeel && other) = delete; + }; +} + diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp index 8e67e40d..af242d47 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp @@ -57,23 +57,21 @@ namespace kiwi { void BangView::paint(juce::Graphics & g) { g.fillAll(findColour(ObjectView::ColourIds::Background)); + g.setColour(findColour(ObjectView::ColourIds::Outline)); + drawOutline(g); - g.setColour(findColour(ObjectView::ColourIds::Outline)); - - juce::Rectangle bounds = getLocalBounds(); - - double circle_outer = 80 * bounds.getWidth() / 100.; - - double circle_thickness = 10 * bounds.getWidth() / 100.; + const auto bounds = getLocalBounds().toFloat(); + const auto width = std::max(bounds.getWidth(), bounds.getHeight()); - g.drawEllipse(bounds.reduced(bounds.getWidth() - circle_outer).toFloat(), circle_thickness); + const float circle_outer = 80.f * (width * 0.01f); + const float circle_thickness = 10.f * (width * 0.01f); - drawOutline(g); + g.drawEllipse(bounds.reduced(width - circle_outer), circle_thickness); if (m_mouse_down || m_active) { g.setColour(findColour(ObjectView::ColourIds::Active)); - g.fillEllipse(bounds.reduced(bounds.getWidth() - circle_outer + circle_thickness).toFloat()); + g.fillEllipse(bounds.reduced(width - circle_outer + circle_thickness)); } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp index ed17e5ab..0bdb91e4 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp @@ -20,9 +20,9 @@ */ #include - + +#include #include - #include namespace kiwi @@ -44,7 +44,7 @@ namespace kiwi findColour(ObjectView::ColourIds::Background)); setColour(ObjectView::ColourIds::Background, bg_colour); - setColour(ObjectView::ColourIds::Outline, juce::Colours::transparentBlack); + setColour(ObjectView::ColourIds::Outline, bg_colour.contrasting(0.4)); label.setColour(juce::Label::backgroundColourId, bg_colour); label.setColour(juce::Label::backgroundWhenEditingColourId, bg_colour); @@ -66,9 +66,7 @@ namespace kiwi void ClassicView::paintOverChildren (juce::Graphics& g) { - g.setColour(findColour(EditableObjectView::ColourIds::Background).contrasting(0.4)); - g.drawLine(0, 0, getWidth(), 0, 6); - g.drawLine(0, getHeight(), getWidth(), getHeight(), 6); + drawOutline(g); } void ClassicView::textChanged() diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp index 4f5d7355..695004c8 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp @@ -81,11 +81,11 @@ namespace kiwi { // dashed outline juce::Path path; path.addRectangle(getLocalBounds()); - const juce::PathStrokeType path_stroke(1.0); + const juce::PathStrokeType path_stroke(1.f); float const dashed_length[2] {2.f, 2.f}; path_stroke.createDashedStroke(path, path, dashed_length, 2); - g.setColour(findColour (ObjectView::ColourIds::Outline).brighter(0.8).withAlpha(0.8f)); + g.setColour(findColour (ObjectView::ColourIds::Outline)); g.strokePath(path, path_stroke); } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp index 6f168613..570bad25 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp @@ -92,27 +92,36 @@ namespace kiwi { void MessageView::paint(juce::Graphics& g) { - g.setColour(findColour(EditableObjectView::ColourIds::Background).contrasting(m_active ? 0.1 : 0.)); - g.fillRoundedRectangle(getLocalBounds().toFloat(), 3.f); + const float roundness = 3.f; + const float bordersize = 1.f; + const auto bounds = getLocalBounds().toFloat().reduced(bordersize*0.5f); + const auto bgcolor = findColour(ObjectView::ColourIds::Background); + const auto active_color = findColour(ObjectView::ColourIds::Active); + const bool clicked = m_active; - g.setColour(findColour(EditableObjectView::ColourIds::Background).contrasting(0.3)); - g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5), 3.f, 1.5f); + g.setColour(bgcolor.contrasting(clicked ? 0.1 : 0.)); + g.fillRoundedRectangle(bounds, roundness); + + g.setColour(clicked ? active_color : bgcolor.contrasting(0.25)); + g.drawRoundedRectangle(bounds, roundness, bordersize); } void MessageView::paintOverChildren (juce::Graphics& g) - { - g.setColour(findColour(EditableObjectView::ColourIds::Background).contrasting(0.3)); - auto bounds = getLocalBounds(); + { + if(getLabel().isBeingEdited()) + return; // abort - if (m_active) - { - g.setColour(findColour(ObjectView::ColourIds::Active)); - } + g.setColour(m_active + ? findColour(ObjectView::ColourIds::Active) + : findColour(ObjectView::ColourIds::Background).contrasting(0.25)); + + const auto bounds = getLocalBounds(); + const auto topright = bounds.getTopRight().toFloat(); juce::Path corner; - corner.addTriangle(bounds.getTopRight().toFloat() - juce::Point(10, 0), - bounds.getTopRight().toFloat(), - bounds.getTopRight().toFloat() + juce::Point(0, 10)); + corner.addTriangle(topright.translated(-10.f, 0.f), + topright, + topright.translated(0, 10)); g.fillPath(corner); } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp index f08342af..9a253271 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp @@ -24,6 +24,7 @@ #include +#include #include #include @@ -43,60 +44,69 @@ namespace kiwi return std::make_unique(model); } - MeterTildeView::MeterTildeView(model::Object & object_model): - ObjectView(object_model), - m_leds(), - m_active_led(-1), - m_cold_colour(juce::Colour(0xff04047f)), - m_hot_colour(juce::Colour(0xffca2423)), - m_border(5), - m_padding(1), - m_connection(object_model.getSignal(model::MeterTilde::Signal::PeakChanged) - .connect(std::bind(&MeterTildeView::peakChanged, this, std::placeholders::_1))) + MeterTildeView::MeterTildeView(model::Object & object_model) + : ObjectView(object_model) + , m_connection(object_model.getSignal(model::MeterTilde::Signal::PeakChanged) + .connect([this](auto new_peak){ peakChanged(new_peak); })) { - size_t num_leds = 12; + const size_t num_leds = 12; + m_leds.resize(num_leds); - for(int i = 0; i < num_leds; ++i) + for(size_t i = 0; i < num_leds; ++i) { - float min_db = - 3. * (num_leds - (i + 1)); - - m_leds.push_back({min_db, - computeGradientColour(( static_cast(i) / (num_leds - 1))), - juce::Rectangle()}); + auto& led = m_leds[i]; + led.min_db = -3.f * (num_leds - (i + 1.f)); + led.colour = computeGradientColour(static_cast(i) / (num_leds - 1.f)); } + + computeActiveLed(-120.f); } MeterTildeView::~MeterTildeView() + {} + + void MeterTildeView::validateSize(int& new_width, int& new_height) { + const int min = 40; + const bool vertical = (new_width <= new_height); + if(vertical) + { + new_height = std::max(min, new_height); + } + else + { + new_width = std::max(min, new_width); + } } void MeterTildeView::resized() { - juce::AffineTransform transform; - - if (getHeight() > getWidth()) - { - transform = juce::AffineTransform::translation(-getLocalBounds().getCentre()); - transform = transform.followedBy(juce::AffineTransform(0, -1, 0, 1, 0, 0)); - transform = transform.followedBy(juce::AffineTransform::translation(getLocalBounds().getCentre())); - } - - juce::Rectangle bounds = getLocalBounds().transformedBy(transform); - - float led_width = (bounds.getWidth() - (m_leds.size() - 1.) * m_padding - 2. * m_border) / m_leds.size(); + const auto bounds = getLocalBounds().toFloat(); + const bool vertical = (bounds.getWidth() <= bounds.getHeight()); - int led_height = bounds.getHeight() - 2 * m_border; + const float num_leds = m_leds.size(); + const float padding = m_padding; + const float sep = m_led_distance; - int led_y = bounds.getY() + (bounds.getHeight() / 2.) - (led_height / 2.); + auto space = bounds.reduced(padding, padding); - for(int i = 0; i < m_leds.size(); ++i) + if(vertical) { - juce::Rectangle led_bounds(bounds.getX() + m_border + (i + 1) * m_padding + i * led_width, - led_y, - led_width, - led_height); - - m_leds[i].m_bounds = led_bounds.transformedBy(transform.inverted()); + const auto led_space = (space.getHeight() / num_leds); + for(auto& led : m_leds) + { + led.bounds = (space.removeFromBottom(led_space) + .removeFromTop(led_space - sep)); + } + } + else + { + const auto led_space = (space.getWidth() / num_leds); + for(auto& led : m_leds) + { + led.bounds = (space.removeFromLeft(led_space) + .removeFromLeft(led_space - sep)); + } } } @@ -122,7 +132,7 @@ namespace kiwi { auto it = std::find_if(m_leds.rbegin(), m_leds.rend(), [peak_db](Led const& led) { - return peak_db >= led.m_min_db; + return peak_db >= led.min_db; }); m_active_led = it != m_leds.rend() ? m_leds.rend() - (it + 1) : -1; @@ -132,7 +142,7 @@ namespace kiwi { defer([this, new_peak]() { - float peak_db = 20 * log10(new_peak); + float peak_db = 20. * log10(new_peak); computeActiveLed(peak_db); repaint(); }); @@ -141,21 +151,25 @@ namespace kiwi void MeterTildeView::paint(juce::Graphics & g) { g.fillAll(findColour(ObjectView::ColourIds::Background)); + const auto led_border_size = 0.5f; + const auto outline_color = findColour(ObjectView::ColourIds::Outline); - for(int i = 0; i < m_leds.size(); ++i) + for(int i = 0; i < m_leds.size(); i++) { - g.setColour(findColour(ObjectView::ColourIds::Outline)); - g.drawRect(m_leds[i].m_bounds.toFloat(), 1); - + const auto& led = m_leds[i]; if (i <= m_active_led) { - g.setColour(m_leds[i].m_colour); - g.fillRect(m_leds[i].m_bounds.reduced(1).toFloat()); + g.setColour(led.colour); + g.fillRect(led.bounds); + } + else + { + g.setColour(outline_color); + g.drawRect(led.bounds, led_border_size); } } - g.setColour(findColour(ObjectView::ColourIds::Outline)); - + g.setColour(outline_color); drawOutline(g); } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.h index 30cc5294..cb4f6c58 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.h @@ -39,9 +39,9 @@ namespace kiwi { struct Led { - float m_min_db; - juce::Colour m_colour; - juce::Rectangle m_bounds; + float min_db {0.f}; + juce::Colour colour {}; + juce::Rectangle bounds {}; }; public: // methods @@ -56,6 +56,9 @@ namespace kiwi { private: // methods + //! @brief Validate the new width and height for the box + void validateSize(int& new_width, int& new_height) override; + void resized() override final; void paint(juce::Graphics & g) override final; @@ -68,13 +71,13 @@ namespace kiwi { private: // members - std::vector m_leds; - int m_active_led; - juce::Colour m_cold_colour; - juce::Colour m_hot_colour; - int m_border; - int m_padding; - flip::SignalConnection m_connection; + std::vector m_leds {}; + int m_active_led = -1; + juce::Colour m_cold_colour = juce::Colour(0xff04047f); + juce::Colour m_hot_colour = juce::Colour(0xffca2423); + float m_led_distance = 1.f; + float m_padding = 4.f; + flip::SignalConnection m_connection {}; private: // deleted methods diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp index d95cb647..96e92c22 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp @@ -65,8 +65,6 @@ namespace kiwi { void NumberViewBase::paintOverChildren (juce::Graphics& g) { - g.setColour (findColour (ObjectView::ColourIds::Outline)); - drawOutline(g); drawIcon(g); } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp index 44e80da5..8b6fda66 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp @@ -34,8 +34,7 @@ namespace kiwi // ================================================================================ // ObjectView::ObjectView(model::Object & object_model) - : m_model(object_model) - , m_border_size(1.5) + : m_model(object_model) , m_master(this, [](ObjectView*){}) { object_model.addListener(*this); @@ -94,8 +93,10 @@ namespace kiwi } void ObjectView::drawOutline(juce::Graphics & g) - { - g.drawRect(getOutline(), m_border_size); + { + g.setColour(findColour(ObjectView::ColourIds::Outline)); + const auto border_size = KiwiApp::useLookAndFeel().getObjectBorderSize(); + g.drawRect(getOutline().toFloat(), border_size); } void ObjectView::lockStatusChanged(bool is_locked) diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h index 5585bd5b..2d76ce01 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h @@ -94,7 +94,7 @@ namespace kiwi void schedule(std::function call_back, tool::Scheduler<>::duration_t delay); //! @brief Draws the outlines of the object. - void drawOutline(juce::Graphics & g); + virtual void drawOutline(juce::Graphics & g); //! @brief Changes one of the data model's attribute. void setAttribute(std::string const& name, tool::Parameter const& param); @@ -117,8 +117,7 @@ namespace kiwi private: // members - model::Object& m_model; - int m_border_size; + model::Object& m_model; std::shared_ptr m_master; private: // deleted methods diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.cpp index 4f3078cd..952fdf40 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.cpp @@ -46,7 +46,7 @@ namespace kiwi findColour(ObjectView::ColourIds::Outline)); m_slider.setColour(juce::Slider::ColourIds::trackColourId, - findColour(ObjectView::ColourIds::Active).darker(0.5)); + findColour(ObjectView::ColourIds::Active).contrasting(0.2f)); m_slider.setColour(juce::Slider::ColourIds::thumbColourId, findColour(ObjectView::ColourIds::Active)); @@ -63,6 +63,20 @@ namespace kiwi SliderView::~SliderView() { m_slider.removeListener(this); + } + + void SliderView::validateSize(int& new_width, int& new_height) + { + const int min = 40; + const bool vertical = (new_width <= new_height); + if(vertical) + { + new_height = std::max(min, new_height); + } + else + { + new_width = std::max(min, new_width); + } } void SliderView::declare() diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.h index c1d65e1b..f2e2b778 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.h @@ -49,7 +49,10 @@ namespace kiwi static std::unique_ptr create(model::Object & object); - private: // methods + private: // methods + + //! @brief Validate the new width and height for the box + void validateSize(int& new_width, int& new_height) override; void sliderValueChanged(juce::Slider * slider) override final; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ToggleView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ToggleView.cpp index 7ceac3bf..f58f717d 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ToggleView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ToggleView.cpp @@ -67,33 +67,18 @@ namespace kiwi { void ToggleView::paint(juce::Graphics & g) { g.fillAll(findColour(ObjectView::ColourIds::Background)); - - g.setColour(findColour(ObjectView::ColourIds::Outline)); - - drawOutline(g); - - if (m_is_on) - { - g.setColour(findColour(ObjectView::ColourIds::Active)); - } - - juce::Rectangle bounds = getLocalBounds(); - - double cross_stroke_width = 10. * (bounds.getWidth() / 100.); - - juce::Rectangle inner_bounds = bounds.reduced(30. * bounds.getWidth() / 100., - 30. * bounds.getHeight() / 100.); - - g.drawLine(inner_bounds.getBottomLeft().getX(), - inner_bounds.getBottomLeft().getY(), - inner_bounds.getTopRight().getX(), - inner_bounds.getTopRight().getY(), - cross_stroke_width); - - g.drawLine(inner_bounds.getTopLeft().getX(), - inner_bounds.getTopLeft().getY(), - inner_bounds.getBottomRight().getX(), - inner_bounds.getBottomRight().getY(), - cross_stroke_width); + drawOutline(g); + + g.setColour(m_is_on + ? findColour(ObjectView::ColourIds::Active) + : findColour(ObjectView::ColourIds::Outline)); + + const auto local_bounds = getLocalBounds().toFloat(); + const auto max = std::max(local_bounds.getWidth(), local_bounds.getHeight()); + const auto cross_stroke_width = max * 0.1; + const auto cross_bounds = local_bounds.reduced(max * 0.3); + + g.drawLine({cross_bounds.getTopLeft(), cross_bounds.getBottomRight()}, cross_stroke_width); + g.drawLine({cross_bounds.getBottomLeft(), cross_bounds.getTopRight()}, cross_stroke_width); } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp index e12b5e41..306fd2f7 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp @@ -237,7 +237,7 @@ namespace kiwi constrainer.setFixedAspectRatio(1 / ratio); } - juce::Rectangle limits = m_patcher_view.getBounds(); + const juce::Rectangle limits {}; juce::Rectangle previous_bounds(model.getX(), model.getY(), model.getWidth(), model.getHeight()); From 9571200f118cbc3b90943f32c79997598d348970 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Wed, 13 Jun 2018 22:52:03 +0200 Subject: [PATCH 18/41] correct some text displayed --- .../Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp index a0362a26..ff96cdfd 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp @@ -199,7 +199,7 @@ namespace kiwi if (!m_drive_view.isShowingTrashedDocuments()) { m.addItem(1, "Rename"); - m.addItem(2, "Delete"); + m.addItem(2, "Move to trash"); m.addItem(10, "Upload"); m.addItem(11, "Duplicate"); } From f6550c32156b55820c7d9232316920c125f5a342 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Wed, 13 Jun 2018 22:54:08 +0200 Subject: [PATCH 19/41] correct a text and clean some code --- .../KiwiApp_Application/KiwiApp_SettingsPanel.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Client/Source/KiwiApp_Application/KiwiApp_SettingsPanel.cpp b/Client/Source/KiwiApp_Application/KiwiApp_SettingsPanel.cpp index c2da9fc8..ef759003 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_SettingsPanel.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_SettingsPanel.cpp @@ -33,8 +33,8 @@ namespace kiwi SettingsPanel::SettingsPanel(): m_settings(getAppSettings().network().getServerAddress()), m_pannel("Application settings"), - m_apply_button("apply"), - m_reset_button("reset") + m_apply_button("Apply"), + m_reset_button("Reset") { juce::Array props { @@ -50,9 +50,6 @@ namespace kiwi m_pannel.addSection("Network config", props, true, 0); - m_apply_button.setButtonText("Apply"); - m_reset_button.setButtonText("Reset"); - m_apply_button.addListener(this); m_reset_button.addListener(this); @@ -85,15 +82,15 @@ namespace kiwi m_reset_button.changeWidthToFitText(); } - void SettingsPanel::buttonClicked(juce::Button * button) + void SettingsPanel::buttonClicked(juce::Button* button) { - if (button->getName() == "apply") + if (button == &m_apply_button) { getAppSettings().network().setServerAddress(m_settings[Ids::host].toString().toStdString(), m_settings[Ids::api_port].operator int(), m_settings[Ids::session_port].operator int()); } - else if(button->getName() == "reset") + else if(button == &m_reset_button) { NetworkSettings & network = getAppSettings().network(); From d838682cdef91a7d920ea5bb51abfc7d739d97c8 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Wed, 13 Jun 2018 22:58:16 +0200 Subject: [PATCH 20/41] update [sf.play~] and [sf.record~] objects - add support for multi-channel recording/playback --- .../KiwiEngine_SfPlayTilde.cpp | 58 +++++++++++------- .../KiwiEngine_SfRecordTilde.cpp | 61 +++++++++++++------ .../KiwiModel_SfPlayTilde.cpp | 4 +- .../KiwiModel_SfRecordTilde.cpp | 4 +- docs/software/objects.md | 4 +- 5 files changed, 84 insertions(+), 47 deletions(-) diff --git a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp index 4890040b..514cd584 100644 --- a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp @@ -28,6 +28,9 @@ namespace kiwi { namespace engine { // SOUNDFILE PLAYER // // ================================================================================ // + //! @note the SoundFilePlayer class is based on the juce tutorial : + // https://docs.juce.com/master/tutorial_playing_sound_files.html + SoundFilePlayer::SoundFilePlayer() : m_buffer() , m_audio_source_channel_info(m_buffer) @@ -64,10 +67,17 @@ namespace kiwi { namespace engine { } int64_t length = end_sample - start_sample; - auto susection_reader = new juce::AudioSubsectionReader(reader.release(), start_sample, length, true); + auto subsection_reader = new juce::AudioSubsectionReader(reader.release(), start_sample, length, true); + + auto new_source = std::make_unique(subsection_reader, + true); + int read_ahead = 0; + m_transport_source.setSource(new_source.get(), + read_ahead, + nullptr, + subsection_reader->sampleRate, + getNumberOfChannels()); - auto new_source = std::make_unique(susection_reader, true); - m_transport_source.setSource(new_source.get(), 0, nullptr, susection_reader->sampleRate); m_reader_source.reset(new_source.release()); setLoop(is_looping); changeState(Starting); @@ -98,7 +108,7 @@ namespace kiwi { namespace engine { void SoundFilePlayer::setNumberOfChannels(size_t channels) { - m_channels = channels > 0 ? channels : m_channels; + m_channels = channels > 0 ? channels : 0; } size_t SoundFilePlayer::getNumberOfChannels() const @@ -239,7 +249,7 @@ namespace kiwi { namespace engine { void SfPlayTilde::declare() { - Factory::add("sfplay~", &SfPlayTilde::create); + Factory::add("sf.play~", &SfPlayTilde::create); } std::unique_ptr SfPlayTilde::create(model::Object const& model, Patcher & patcher) @@ -284,27 +294,31 @@ namespace kiwi { namespace engine { openFileDialog(); } } - else if ((args[0].isNumber() && args[0].getInt() == 1) - || args[0].getString() == "start") - { - m_player.start(m_file_to_read); - } - else if ((args[0].isNumber() && args[0].getInt() == 0) - || args[0].getString() == "stop") + else if (args[0].isNumber()) { - m_player.stop(); + auto num = args[0].getInt(); + if(num == 1) + m_player.start(m_file_to_read); + else if(num == 0) + m_player.stop(); + else + warning("sf.play~: use 1 to play, 0 to stop"); } - else if (args[0].getString() == "seek") + else if (args[0].getString() == "play") { if(args.size() > 1 && args[1].isNumber()) { - double start_ms = args[1].getFloat(); - double end_ms = ((args.size() > 2 && args[2].isNumber()) - ? args[2].getFloat() - : -1.); + const double start_ms = args[1].getFloat(); + const double end_ms = ((args.size() > 2 && args[2].isNumber()) + ? args[2].getFloat() + : -1.); m_player.start(m_file_to_read, start_ms, end_ms); } + else + { + warning("sf.play~: the \"play\" method requires a start position in ms, pass a second argument to specify the ending position"); + } } else if (args[0].getString() == "loop") { @@ -314,7 +328,7 @@ namespace kiwi { namespace engine { } else { - warning("sfplay~: loop message must be followed by 0 or 1"); + warning("sf.play~: loop message must be followed by 0 or 1"); } } else if (args[0].getString() == "print") @@ -340,19 +354,19 @@ namespace kiwi { namespace engine { const auto file_path = file.getFullPathName(); if(!juce::File::isAbsolutePath(file_path)) { - warning("sfplay~: is not an absolute path"); + warning("sf.play~: is not an absolute path"); return false; } if(file.isDirectory()) { - warning("sfplay~: invalid file path"); + warning("sf.play~: invalid file path"); return false; } if(!file.exists()) { - warning("sfplay~: file doesn't exist \"" + warning("sf.play~: file doesn't exist \"" + file_path.toStdString() + "\""); return false; } diff --git a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.cpp b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.cpp index 8c039c7b..c914523b 100644 --- a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.cpp +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.cpp @@ -28,6 +28,7 @@ namespace kiwi { namespace engine { // SOUNDFILE RECORDER // // ================================================================================ // + //! @note the SoundFileRecorder class is based on the juce Demo App SoundFileRecorder::SoundFileRecorder() : m_background_thread("SoundFile Recorder Thread") { @@ -55,7 +56,25 @@ namespace kiwi { namespace engine { // Now create a WAV writer object that writes to our output stream... juce::WavAudioFormat wav_format; - juce::AudioFormatWriter* writer = wav_format.createWriterFor(file_stream.get(), m_sample_rate, getNumberOfChannels(), 16, {}, 0); + + const int bit_per_sample = 16; + const int quality_option = 0; + const juce::StringPairArray metadata {}; + const int nchan = getNumberOfChannels(); + auto channel_set = juce::AudioChannelSet::canonicalChannelSet(nchan); + + if(!wav_format.isChannelLayoutSupported(channel_set)) + { + std::cerr << "channel layout not supported\n"; + return false; + } + + auto* writer = wav_format.createWriterFor(file_stream.get(), + m_sample_rate, + channel_set, + bit_per_sample, + metadata, + quality_option); if (writer == nullptr) return false; // abort @@ -65,9 +84,9 @@ namespace kiwi { namespace engine { // Now we'll create one of these helper objects which will act as a FIFO buffer, and will // write the data to disk on our background thread. - m_threaded_writer = std::make_unique(writer, - m_background_thread, - 32768); + m_threaded_writer.reset(new juce::AudioFormatWriter::ThreadedWriter(writer, + m_background_thread, + 32768)); // And now, swap over our active writer pointer so that the audio callback will start using it.. const std::lock_guard lock (m_writer_lock); @@ -136,7 +155,7 @@ namespace kiwi { namespace engine { void SfRecordTilde::declare() { - Factory::add("sfrecord~", &SfRecordTilde::create); + Factory::add("sf.record~", &SfRecordTilde::create); } std::unique_ptr SfRecordTilde::create(model::Object const& model, Patcher & patcher) @@ -163,25 +182,29 @@ namespace kiwi { namespace engine { { if(args.size() > 1) { - if(args[0].isString()) + if(args[1].isString()) { openFile(juce::File(args[1].getString())); } + else + { + error("sf.record~: bad argument for open"); + } } else { openFileDialog(); } } - else if ((args[0].isNumber() && args[0].getInt() == 1) - || args[0].getString() == "start") - { - record(); - } - else if ((args[0].isNumber() && args[0].getInt() == 0) - || args[0].getString() == "stop") + else if (args[0].isNumber()) { - stop(); + auto num = args[0].getInt(); + if(num == 1) + record(); + else if(num == 0) + stop(); + else + warning("sf.record~: use 1 to start recording, 0 to stop"); } else if (args[0].getString() == "record") { @@ -202,13 +225,13 @@ namespace kiwi { namespace engine { const auto path = file.getFullPathName(); if(!juce::File::isAbsolutePath(path)) { - warning("sfrecord~: is not an absolute path"); + warning("sf.record~: is not an absolute path"); return false; } if(file.isDirectory()) { - warning("sfrecord~: invalid file path"); + warning("sf.record~: invalid file path"); return false; } @@ -216,7 +239,7 @@ namespace kiwi { namespace engine { if(!file.hasWriteAccess()) { - warning("sfrecord~: no write access to file \"" + warning("sf.record~: no write access to file \"" + path.toStdString() + "\""); return false; } @@ -234,13 +257,13 @@ namespace kiwi { namespace engine { { if(!file.create()) { - warning("sfrecord~: can't create file \"" + path.toStdString() + "\""); + warning("sf.record~: can't create file \"" + path.toStdString() + "\""); return false; } } else { - warning("sfrecord~: file will be overwritten \"" + warning("sf.record~: file will be overwritten \"" + path.toStdString() + "\""); } diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.cpp index abe1a816..9a285e52 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfPlayTilde.cpp @@ -31,7 +31,7 @@ namespace kiwi { namespace model { void SfPlayTilde::declare() { - auto kiwi_class = std::make_unique("sfplay~", &SfPlayTilde::create); + auto kiwi_class = std::make_unique("sf.play~", &SfPlayTilde::create); auto& flip_class = DataModel::declare() .name(kiwi_class->getModelName().c_str()) @@ -72,7 +72,7 @@ namespace kiwi { namespace model { { if(index == 0) { - return "(msg) Open file, start, stop, int"; + return "(msg) Open file, 0 to stop, 1 to start"; } } else diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.cpp index 47c8ec33..55f7ab7d 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.cpp @@ -31,7 +31,7 @@ namespace kiwi { namespace model { void SfRecordTilde::declare() { - auto kiwi_class = std::make_unique("sfrecord~", &SfRecordTilde::create); + auto kiwi_class = std::make_unique("sf.record~", &SfRecordTilde::create); auto& flip_class = DataModel::declare() .name(kiwi_class->getModelName().c_str()) @@ -69,7 +69,7 @@ namespace kiwi { namespace model { { if(index == 0) { - return "open, start, stop recording, Audio Channel 1"; + return "Audio Channel 1, open a file and start/stop recording"; } else { diff --git a/docs/software/objects.md b/docs/software/objects.md index f73fdd39..5a90a736 100644 --- a/docs/software/objects.md +++ b/docs/software/objects.md @@ -72,5 +72,5 @@ A list of help patches describing how each object works is getDocumentName() +"\" offline", - "Ok", - "Cancel"); + "Yes", + "No"); if (!keep_patcher) { diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp index 9a253271..2bb7b730 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp @@ -47,7 +47,7 @@ namespace kiwi MeterTildeView::MeterTildeView(model::Object & object_model) : ObjectView(object_model) , m_connection(object_model.getSignal(model::MeterTilde::Signal::PeakChanged) - .connect([this](auto new_peak){ peakChanged(new_peak); })) + .connect([this](float new_peak){ peakChanged(new_peak); })) { const size_t num_leds = 12; m_leds.resize(num_leds); diff --git a/Modules/KiwiServer/KiwiServer_Server.cpp b/Modules/KiwiServer/KiwiServer_Server.cpp index 925efa96..589f2062 100644 --- a/Modules/KiwiServer/KiwiServer_Server.cpp +++ b/Modules/KiwiServer/KiwiServer_Server.cpp @@ -144,13 +144,15 @@ namespace kiwi void Server::onConnected(flip::PortBase & port) { - uint64_t session_id = port.session(); - - if(m_sessions.find(port.session()) == m_sessions.end()) + const auto session_id = port.session(); + const auto session = m_sessions.find(session_id); + if(session == m_sessions.end()) { - juce::File session_file = getSessionFile(port.session()); + juce::File session_file = getSessionFile(session_id); + + const auto session_hex_id = hexadecimal_convert(session_id); - m_logger.log("creating new session for session_id : " + hexadecimal_convert(session_id)); + m_logger.log("creating new session for session_id : " + session_hex_id); auto session = m_sessions .insert(std::make_pair(session_id, @@ -158,12 +160,12 @@ namespace kiwi if (session_file.exists()) { - m_logger.log("loading session file for session_id : " + hexadecimal_convert(session_id)); + m_logger.log("loading session file for session_id : " + session_hex_id); if (!(*session.first).second.load()) { m_logger.log("opening document document session : " - + hexadecimal_convert(session_id) + " failed"); + + session_hex_id + " failed"); m_sessions.erase(session_id); @@ -175,7 +177,7 @@ namespace kiwi } else { - m_sessions.find(port.session())->second.bind(port); + session->second.bind(port); } } @@ -276,13 +278,14 @@ namespace kiwi // ================================================================================ // Server::Session::Session(Session && other) - : m_identifier(std::move(other.m_identifier)) + : m_identifier(other.m_identifier) + , m_hex_id(other.m_hex_id) , m_validator(std::move(other.m_validator)) , m_document(std::move(other.m_document)) , m_signal_connections(std::move(other.m_signal_connections)) , m_backend_file(std::move(other.m_backend_file)) - , m_token(other.m_token) - , m_kiwi_version(other.m_kiwi_version) + , m_token(std::move(other.m_token)) + , m_kiwi_version(std::move(other.m_kiwi_version)) , m_logger(other.m_logger) { ; @@ -294,6 +297,7 @@ namespace kiwi std::string const& kiwi_version, Server::Logger & logger) : m_identifier(identifier) + , m_hex_id(hexadecimal_convert(identifier)) , m_validator(new model::PatcherValidator()) , m_document(new flip::DocumentServer(model::DataModel::use(), *m_validator, m_identifier)) , m_signal_connections() @@ -313,11 +317,9 @@ namespace kiwi { if (m_document) { - std::set ports = m_document->ports(); - - for (std::set::iterator port = ports.begin(); port != ports.end(); ++port) + for (auto* port : m_document->ports()) { - m_document->port_factory_remove(**port); + m_document->port_factory_remove(*port); } } } @@ -447,7 +449,7 @@ namespace kiwi void Server::Session::unbind(flip::PortBase & port) { m_logger.log("User " + std::to_string(port.user()) - + " disconnecting from session" + hexadecimal_convert(m_identifier)); + + " disconnecting from session" + m_hex_id); std::set ports = m_document->ports(); @@ -470,7 +472,7 @@ namespace kiwi m_backend_file.create(); } - m_logger.log("saving session : " + hexadecimal_convert(m_identifier) + m_logger.log("saving session : " + m_hex_id + " in file : " + m_backend_file.getFileName().toStdString()); if (!save()) diff --git a/Modules/KiwiServer/KiwiServer_Server.h b/Modules/KiwiServer/KiwiServer_Server.h index e1dab500..51237069 100644 --- a/Modules/KiwiServer/KiwiServer_Server.h +++ b/Modules/KiwiServer/KiwiServer_Server.h @@ -207,6 +207,7 @@ namespace kiwi private: // members const uint64_t m_identifier; + const std::string m_hex_id; std::unique_ptr m_validator; std::unique_ptr m_document; std::vector m_signal_connections; From 1c58ece93834a56fc7a4a36fb7b2876582839e41 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Thu, 14 Jun 2018 19:21:20 +0200 Subject: [PATCH 22/41] GUI and UX enhancements: - Enhance contrast between local and distant selections. - Add contrast between control and signal pins --- .../KiwiApp_General/KiwiApp_LookAndFeel.cpp | 14 +- .../KiwiApp_Patcher/KiwiApp_LinkView.cpp | 2 +- .../KiwiApp_Objects/KiwiApp_ObjectFrame.cpp | 307 ++++++------ .../KiwiApp_Objects/KiwiApp_ObjectFrame.h | 469 +++++++++--------- .../KiwiApp_Objects/KiwiApp_ObjectView.h | 15 +- 5 files changed, 401 insertions(+), 406 deletions(-) diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp index eaea3b69..a8659d6f 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp @@ -262,19 +262,23 @@ namespace kiwi // ------ objectbox colors const juce::Colour box_bgcolor = juce::Colours::white; - const juce::Colour patcher_second_color = juce::Colour(0xff444444); - setColour(ObjectView::ColourIds::Pin, patcher_second_color); + const auto box_bdcolor = box_bgcolor.contrasting(0.4); + const auto link_control_color = box_bdcolor.contrasting(0.1f); + const auto link_signal_color = juce::Colour(0xff444444); + + setColour(ObjectView::ColourIds::PinControl, link_control_color); + setColour(ObjectView::ColourIds::PinSignal, link_signal_color); setColour(ObjectView::ColourIds::Error, juce::Colour::fromRGBA(223, 97, 94, 250)); setColour(ObjectView::ColourIds::Background, box_bgcolor); setColour(ObjectView::ColourIds::Text, juce::Colours::black); - setColour(ObjectView::ColourIds::Outline, box_bgcolor.contrasting(0.4)); + setColour(ObjectView::ColourIds::Outline, box_bdcolor); setColour(ObjectView::ColourIds::Highlight, juce::Colour::fromFloatRGBA(0., 0.5, 1., 0.4)); setColour(ObjectView::ColourIds::Active, juce::Colour(0xff21ba90)); // ------ link colors - setColour(LinkView::ColourIds::ControlBackground, patcher_second_color); - setColour(LinkView::ColourIds::SignalBackground, patcher_second_color); + setColour(LinkView::ColourIds::ControlBackground, link_control_color); + setColour(LinkView::ColourIds::SignalBackground, link_signal_color); } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.cpp index 1ef55687..863665e4 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.cpp @@ -217,7 +217,7 @@ namespace kiwi const bool selected_by_other = (m_selected.by_another_user || m_selected.in_another_view); - const float stroke = (isSignal() ? 2.f : 1.f); + const float stroke = (isSignal() ? 2.f : 1.5f); juce::Colour bgcolor; if(m_selected.on_this_view) diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp index 00f6ec76..52e79b21 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp @@ -38,10 +38,12 @@ namespace kiwi , m_patcher_view(patcher_view) , m_io_width(6) , m_io_height(3) - , m_inlets(getModel().getNumberOfInlets()) - , m_outlets(getModel().getNumberOfOutlets()) - , m_outline(10, 4, 1) + , m_outline(10, 4) { + const auto& model_object = getModel(); + updateInlets(model_object); + updateOutlets(model_object); + const bool pv_locked = isLocked(); setInterceptsMouseClicks(pv_locked, pv_locked); m_object_view->lockStatusChanged(pv_locked); @@ -55,8 +57,16 @@ namespace kiwi } ObjectFrame::~ObjectFrame() - { - ; + {} + + size_t ObjectFrame::getNumberOfInlets() const noexcept + { + return m_inlets.size(); + } + + size_t ObjectFrame::getNumberOfOutlets() const noexcept + { + return m_outlets.size(); } void ObjectFrame::childBoundsChanged(juce::Component * child) @@ -106,32 +116,70 @@ namespace kiwi } void ObjectFrame::paintOverChildren (juce::Graphics& g) - { - if(!isLocked()) + { + const bool locked = isLocked(); + if(!locked) { - g.setColour(findColour(ObjectView::ColourIds::Pin)); drawInletsOutlets(g); + } + + const auto distant_selection = getDistantSelection(); + const bool other_selected = ! distant_selection.empty(); + const bool other_view_selected = (other_selected && + distant_selection.find(KiwiApp::userID()) + != distant_selection.end()); + + const bool other_user_selected = ((other_selected && (other_view_selected + && distant_selection.size() > 1)) + || (other_selected && !other_view_selected)); + + if(other_user_selected || other_view_selected) + { + const bool selected = isSelected(); + + g.setColour(findColour(other_user_selected + ? PatcherView::ColourIds::SelectionOtherUser + : PatcherView::ColourIds::SelectionOtherView) + .withAlpha(0.5f)); + + if(!locked) + { + g.fillRoundedRectangle(getLocalBounds().toFloat() + .reduced(selected ? 5.f : 1.f), + selected ? 0.f : 5.f); + } + else + { + const float amount = 3.f; + g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(amount), amount, amount); + } } } void ObjectFrame::drawInletsOutlets(juce::Graphics & g) { - const juce::Rectangle bounds = m_object_view->getBounds(); + const juce::Rectangle bounds = m_object_view->getBounds(); + + const auto pin_control_color = findColour(ObjectView::ColourIds::PinControl); + const auto pin_signal_color = findColour(ObjectView::ColourIds::PinSignal); - for(unsigned int i = 0; i < m_inlets; ++i) - { + for(unsigned int i = 0; i < getNumberOfInlets(); ++i) + { + g.setColour(m_inlets[i].is_signal ? pin_signal_color : pin_control_color); g.fillRect(getInletLocalBounds(i)); } - for(unsigned int i = 0; i < m_outlets; ++i) - { + for(unsigned int i = 0; i < getNumberOfOutlets(); ++i) + { + g.setColour(m_outlets[i].is_signal ? pin_signal_color : pin_control_color); g.fillRect(getOutletLocalBounds(i)); } } juce::Rectangle ObjectFrame::getObjectBounds() const - { - return m_object_view->getBounds().withPosition(getPosition() + m_object_view->getBounds().getPosition()); + { + const auto object_view_bounds = m_object_view->getBounds(); + return object_view_bounds.withPosition(getPosition() + object_view_bounds.getPosition()); } void ObjectFrame::validateSize(int& new_width, int& new_height) @@ -172,11 +220,14 @@ namespace kiwi // test body and iolets if(box_bounds.contains(pt)) - { + { + const auto ninlets = getNumberOfInlets(); + const auto noutlets = getNumberOfOutlets(); + // test inlets - if(m_inlets > 0 && pt.getY() > m_io_height) + if(ninlets > 0 && pt.getY() > getPinHeight()) { - for(size_t i = 0; i < m_inlets; ++i) + for(size_t i = 0; i < ninlets; ++i) { if(getInletLocalBounds(i).contains(pt)) { @@ -188,9 +239,9 @@ namespace kiwi } // test outlets - if(m_outlets > 0 && pt.getY() > box_bounds.getHeight() - m_io_height) + if(noutlets > 0 && pt.getY() > box_bounds.getHeight() - getPinHeight()) { - for(size_t i = 0; i < m_outlets; ++i) + for(size_t i = 0; i < noutlets; ++i) { if(getOutletLocalBounds(i).contains(pt)) { @@ -206,12 +257,10 @@ namespace kiwi hit.m_index = 0; return true; } - else if(isSelected()) // test borders + else if(m_outline.isVisible() + && m_outline.hitTest(pt, hit)) // test borders { - if (m_outline.hitTest(pt, hit)) - { - return true; - } + return true; } hit.m_zone = HitTester::Zone::Outside; @@ -238,19 +287,37 @@ namespace kiwi bool ObjectFrame::isLocked() const { return getPatcherView().isLocked(); + } + + void ObjectFrame::updateInlets(model::Object const& object) + { + m_inlets.clear(); + for(auto& inlet : object.getInlets()) + { + m_inlets.emplace_back(inlet.hasType(kiwi::model::PinType::IType::Signal)); + } + } + + void ObjectFrame::updateOutlets(model::Object const& object) + { + m_outlets.clear(); + for(auto& outlet : object.getOutlets()) + { + m_outlets.emplace_back(outlet.getType() == kiwi::model::PinType::IType::Signal); + } } void ObjectFrame::objectChanged(model::Patcher::View& view, model::Object& object) { if(object.inletsChanged()) - { - m_inlets = object.getNumberOfInlets(); + { + updateInlets(object); repaint(); } if(object.outletsChanged()) - { - m_outlets = object.getNumberOfOutlets(); + { + updateOutlets(object); repaint(); } @@ -305,55 +372,8 @@ namespace kiwi } void ObjectFrame::updateOutline() - { - std::set const& distant_selection = getDistantSelection(); - - const bool selected = isSelected(); - const bool other_selected = ! distant_selection.empty(); - const bool other_view_selected = (other_selected && - distant_selection.find(KiwiApp::userID()) - != distant_selection.end()); - - const bool distant_selected = ((other_selected && (other_view_selected && distant_selection.size() > 1)) - || (other_selected && !other_view_selected)); - - if (selected) - { - m_outline.setResizeColour(findColour(PatcherView::ColourIds::Selection)); - - if (distant_selected) - { - m_outline.setInnerColour(findColour(PatcherView::ColourIds::SelectionOtherUser)); - } - else if (other_view_selected) - { - m_outline.setInnerColour(findColour(PatcherView::ColourIds::SelectionOtherView)); - } - else - { - m_outline.setInnerColour(findColour(PatcherView::ColourIds::Selection)); - } - - m_outline.setVisible(true); - } - else if(distant_selected) - { - const auto color = findColour(PatcherView::ColourIds::SelectionOtherUser); - m_outline.setResizeColour(color); - m_outline.setInnerColour(color); - m_outline.setVisible(true); - } - else if (other_view_selected) - { - const auto color = findColour(PatcherView::ColourIds::SelectionOtherView); - m_outline.setResizeColour(color); - m_outline.setInnerColour(color); - m_outline.setVisible(true); - } - else - { - m_outline.setVisible(false); - } + { + m_outline.setVisible(isSelected()); } void ObjectFrame::selectionChanged() @@ -364,7 +384,7 @@ namespace kiwi void ObjectFrame::lockStatusChanged() { - const bool locked = isLocked(); + const bool locked = isLocked(); setInterceptsMouseClicks(locked, locked); m_object_view->lockStatusChanged(locked); repaint(); @@ -403,23 +423,28 @@ namespace kiwi juce::Rectangle ObjectFrame::getInletLocalBounds(const size_t index) const { juce::Rectangle inlet_bounds = m_object_view->getBounds(); + + const auto space_to_remove = m_outline.getResizeLength() - m_outline.getBorderThickness(); + inlet_bounds.removeFromLeft(space_to_remove); + inlet_bounds.removeFromRight(space_to_remove); - inlet_bounds.removeFromLeft(m_outline.getResizeLength() - m_outline.getBorderThickness()); - inlet_bounds.removeFromRight(m_outline.getResizeLength() - m_outline.getBorderThickness()); - - juce::Rectangle rect; - - if(m_inlets > 0 && index < m_inlets) + juce::Rectangle rect; + const auto ninlets = getNumberOfInlets(); + + if(ninlets > 0 && index < ninlets) { - if(m_inlets == 1 && index == 0) + if(ninlets == 1 && index == 0) { - rect.setBounds(inlet_bounds.getX(), inlet_bounds.getY(), getPinWidth(), getPinHeight()); + rect.setBounds(inlet_bounds.getX(), + inlet_bounds.getY(), + getPinWidth(), getPinHeight()); } - if(m_inlets > 1) + if(ninlets > 1) { - const double ratio = (inlet_bounds.getWidth() - getPinWidth()) / (double)(m_inlets - 1); - rect.setBounds(inlet_bounds.getX() + ratio * index, inlet_bounds.getY(), + const double ratio = (inlet_bounds.getWidth() - getPinWidth()) / (double)(ninlets - 1); + rect.setBounds(inlet_bounds.getX() + ratio * index, + inlet_bounds.getY(), getPinWidth(), getPinHeight()); } } @@ -434,20 +459,21 @@ namespace kiwi outlet_bounds.removeFromLeft(m_outline.getResizeLength() - m_outline.getBorderThickness()); outlet_bounds.removeFromRight(m_outline.getResizeLength() - m_outline.getBorderThickness()); - juce::Rectangle rect; + juce::Rectangle rect; + const auto noutlets = getNumberOfOutlets(); - if(m_outlets > 0 && index < m_outlets) + if(noutlets > 0 && index < noutlets) { - if(m_outlets == 1 && index == 0) + if(noutlets == 1 && index == 0) { rect.setBounds(outlet_bounds.getX(), outlet_bounds.getY() + outlet_bounds.getHeight() - getPinHeight(), getPinWidth(), getPinHeight()); } - if(m_outlets > 1) + if(noutlets > 1) { - const double ratio = (outlet_bounds.getWidth() - getPinWidth()) / (double)(m_outlets - 1); + const double ratio = (outlet_bounds.getWidth() - getPinWidth()) / (double)(noutlets - 1); rect.setBounds(outlet_bounds.getX() + ratio * index, outlet_bounds.getY() + outlet_bounds.getHeight() - getPinHeight(), getPinWidth(), getPinHeight()); @@ -472,17 +498,9 @@ namespace kiwi return static_cast(static_cast(l_border) | static_cast(r_border)); } - ObjectFrame::Outline::Outline(int resize_length, - int resize_thickness, - int inner_thickness) - : juce::Component() - , m_resize_length(resize_length) + ObjectFrame::Outline::Outline(int resize_length, int resize_thickness) + : m_resize_length(resize_length) , m_resize_thickness(resize_thickness) - , m_inner_thickness(inner_thickness) - , m_corners() - , m_borders() - , m_resize_colour() - , m_inner_colour() {} ObjectFrame::Outline::~Outline() @@ -500,9 +518,9 @@ namespace kiwi void ObjectFrame::Outline::updateCorners() { - juce::Rectangle bounds = getLocalBounds(); + juce::Rectangle bounds = getLocalBounds().toFloat(); - std::array, 3> corner; + std::array, 3> corner; corner[0].setBounds(bounds.getX(), bounds.getY(), @@ -541,39 +559,14 @@ namespace kiwi m_corners[Border::Bottom | Border::Left][2] = corner[2].transformedBy(transform); } - void ObjectFrame::Outline::updateBorders() - { - juce::Rectangle bounds = getLocalBounds(); - - m_borders[Border::Top].setBounds(m_resize_length, - m_resize_thickness - m_inner_thickness, - bounds.getWidth() - 2 * m_resize_length, - m_inner_thickness); - - m_borders[Border::Right].setBounds(bounds.getRight() - m_resize_thickness, - m_resize_length, - m_inner_thickness, - bounds.getHeight() - 2 * m_resize_length); - - m_borders[Border::Bottom].setBounds(m_resize_length, - getBottom() - m_resize_thickness, - bounds.getWidth() - 2 * m_resize_length, - m_inner_thickness); - - m_borders[Border::Left].setBounds(m_resize_thickness - m_inner_thickness, - m_resize_length, - m_inner_thickness, - bounds.getHeight() - 2 * m_resize_length); - } - void ObjectFrame::Outline::resized() { - updateBorders(); updateCorners(); } - bool ObjectFrame::Outline::hitTest(juce::Point const& pt, HitTester& hit_tester) const - { + bool ObjectFrame::Outline::hitTest(juce::Point const& _pt, HitTester& hit_tester) const + { + const auto pt = _pt.toFloat(); bool success = false; if (m_corners.at(Border::Left | Border::Top)[1].contains(pt) @@ -619,39 +612,33 @@ namespace kiwi void ObjectFrame::Outline::drawCorner(juce::Graphics & g, Border border) { - g.setColour(m_resize_colour); - g.fillRect(m_corners[border][2]); g.fillRect(m_corners[border][1]); } - void ObjectFrame::Outline::drawBorder(juce::Graphics & g, Border border) - { - g.setColour(m_inner_colour); - - g.fillRect(m_borders[border]); - } - void ObjectFrame::Outline::paint(juce::Graphics & g) - { - drawBorder(g, Border::Left); - drawBorder(g, Border::Top); - drawBorder(g, Border::Right); - drawBorder(g, Border::Bottom); - + { + g.setColour(findColour(PatcherView::ColourIds::Selection)); + + const auto outline_bounds = getLocalBounds().toFloat(); + const auto object_bounds = outline_bounds.reduced(m_resize_thickness); + + const float thickness = m_resize_thickness * 0.5f; + g.drawRect(outline_bounds.reduced(thickness), thickness); + + /* // The following code do the same + juce::RectangleList rectangles (outline_bounds); + rectangles.subtract(object_bounds); + rectangles.subtract(object_bounds.reduced(m_resize_thickness, -m_resize_thickness)); + rectangles.subtract(object_bounds.reduced(-m_resize_thickness, m_resize_thickness)); + rectangles.consolidate(); + const juce::PathStrokeType stroke (1.f); + g.strokePath(path, stroke); + */ + drawCorner(g, Border::Left | Border::Top); drawCorner(g, Border::Top | Border::Right); drawCorner(g, Border::Right | Border::Bottom); drawCorner(g, Border::Bottom | Border::Left); } - - void ObjectFrame::Outline::setResizeColour(juce::Colour colour) - { - m_resize_colour = colour; - } - - void ObjectFrame::Outline::setInnerColour(juce::Colour colour) - { - m_inner_colour = colour; - } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h index d16c0ae3..86aa6397 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h @@ -1,108 +1,108 @@ -/* - ============================================================================== - - This file is part of the KIWI library. - - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. - - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. - - Permission is granted to use this software under the terms of the GPL v3 - (or any later version). Details can be found at: www.gnu.org/licenses - - KIWI 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. - - ------------------------------------------------------------------------------ - - Contact : cicm.mshparisnord@gmail.com - - ============================================================================== - */ - -#pragma once - -#include -#include - -#include - -#include - -#include -#include - -namespace kiwi -{ - // ================================================================================ // - // OBJECT FRAME // - // ================================================================================ // - - //! @brief A juce component holding the object's graphical representation. - //! @details ObjectFrame is implemented as a wrapper around an object view that displays - //! selections and outlet and handle certain interactions. +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#pragma once + +#include +#include + +#include + +#include + +#include +#include + +namespace kiwi +{ + // ================================================================================ // + // OBJECT FRAME // + // ================================================================================ // + + //! @brief A juce component holding the object's graphical representation. + //! @details ObjectFrame is implemented as a wrapper around an object view that displays + //! selections and outlet and handle certain interactions. class ObjectFrame : public juce::Component - , public EditableObjectView::Listener - { - public: // methods - - //! @brief Constructor. - ObjectFrame(PatcherView& patcher_view, std::unique_ptr object_view); - - //! @brief Destructor. - ~ObjectFrame(); - - //! @brief Called whenever an object's model has changed. - void objectChanged(model::Patcher::View& view, model::Object& object); - - //! @brief Updates the inner object's view attributes. - void attributeChanged(std::string const& name, tool::Parameter const& parameter); - - //! @brief Called whenever the client wants to edit an object. - //! @details Will only edit the object if its a textual object. - void editObject(); - - //! @brief Called by the patcher every time the selection status of this object has changed. - //! @details Function called when local selection or distant selection has changed. - void selectionChanged(); - - //! @brief Called every time a patcher is locked or unlocked. - void lockStatusChanged(); - - //! @brief Called when the patcher's origin changes. - void patcherViewOriginPositionChanged(); - - //! @brief Returns The object's bounds relative to the patcher position. - juce::Rectangle getObjectBounds() const; - - //! @brief Returns the inlet position relative to the parent PatcherView component for a given index. - juce::Point getInletPatcherPosition(const size_t index) const; - - //! @brief Returns the outlet position relative to the parent PatcherView component for a given index. - juce::Point getOutletPatcherPosition(const size_t index) const; - - //! @brief Returns the object's model. - model::Object& getModel() const; - - //! @brief Overloaded from juce::Component to exclude border size. - bool hitTest(int x, int y) override; - - //! @brief Internal kiwi PatcherView HitTesting. - bool hitTest(juce::Point const& pt, HitTester& result) const; - - //! @brief Internal kiwi PatcherView HitTesting (overlaps a rectangle). - bool hitTest(juce::Rectangle const& rect) const; - - //! @brief Returns true if the object is selected. - bool isSelected() const; - - //! @brief Called when object's frame is clicked. - void mouseDown(juce::MouseEvent const& e) override final; - - //! @brief Called when object's frame is clicked. - void mouseUp(juce::MouseEvent const& e) override final; - - //! @brief Called when object's frame is clicked. + , public EditableObjectView::Listener + { + public: // methods + + //! @brief Constructor. + ObjectFrame(PatcherView& patcher_view, std::unique_ptr object_view); + + //! @brief Destructor. + ~ObjectFrame(); + + //! @brief Called whenever an object's model has changed. + void objectChanged(model::Patcher::View& view, model::Object& object); + + //! @brief Updates the inner object's view attributes. + void attributeChanged(std::string const& name, tool::Parameter const& parameter); + + //! @brief Called whenever the client wants to edit an object. + //! @details Will only edit the object if its a textual object. + void editObject(); + + //! @brief Called by the patcher every time the selection status of this object has changed. + //! @details Function called when local selection or distant selection has changed. + void selectionChanged(); + + //! @brief Called every time a patcher is locked or unlocked. + void lockStatusChanged(); + + //! @brief Called when the patcher's origin changes. + void patcherViewOriginPositionChanged(); + + //! @brief Returns The object's bounds relative to the patcher position. + juce::Rectangle getObjectBounds() const; + + //! @brief Returns the inlet position relative to the parent PatcherView component for a given index. + juce::Point getInletPatcherPosition(const size_t index) const; + + //! @brief Returns the outlet position relative to the parent PatcherView component for a given index. + juce::Point getOutletPatcherPosition(const size_t index) const; + + //! @brief Returns the object's model. + model::Object& getModel() const; + + //! @brief Overloaded from juce::Component to exclude border size. + bool hitTest(int x, int y) override; + + //! @brief Internal kiwi PatcherView HitTesting. + bool hitTest(juce::Point const& pt, HitTester& result) const; + + //! @brief Internal kiwi PatcherView HitTesting (overlaps a rectangle). + bool hitTest(juce::Rectangle const& rect) const; + + //! @brief Returns true if the object is selected. + bool isSelected() const; + + //! @brief Called when object's frame is clicked. + void mouseDown(juce::MouseEvent const& e) override final; + + //! @brief Called when object's frame is clicked. + void mouseUp(juce::MouseEvent const& e) override final; + + //! @brief Called when object's frame is clicked. void mouseDrag(juce::MouseEvent const& e) override final; //! @brief Validate the new width and height for the box @@ -114,138 +114,141 @@ namespace kiwi { public: // classes - enum class Border : int + enum Border : int { Top = 1 << 0, Bottom = 1 << 1, Left = 1 << 2, Right = 1 << 3 - }; - - public: // methods - - //! @brief Constructor. - //! @details Defines the resizable corner size, its thickness and the inner border thickness? - Outline(int resize_length, - int resize_thickness, - int inner_thickness); - - //! @brief Tests if the point reaches an interactive resiable corner. - bool hitTest(juce::Point const& pt, HitTester& hit_tester) const; - - //! @brief Returns the corner border width. - int getBorderThickness() const; - - //! @brief Returns the corner border length. - int getResizeLength() const; - - //! @brief Sets the corner colour. - void setResizeColour(juce::Colour colour); - - //! @brief Sets the inner border colour. - void setInnerColour(juce::Colour colour); - - ~Outline(); - - private: // methods - - //! @brief Draws a corner. - void drawCorner(juce::Graphics & g, Border border); - - //! @brief Draws a border. - void drawBorder(juce::Graphics & g, Border border); - - //! @brief Graphical rendering method. - void paint(juce::Graphics & g) override final; - - //! @brief Call once size changed. Recompute borders and corners position. - void resized() override final; - - //! @brief Update the corners position - void updateCorners(); - - //! @brief Update the borders position. - void updateBorders(); - - private: // members - - int m_resize_length; - int m_resize_thickness; - int m_inner_thickness; - std::map, 3>> m_corners; - std::map> m_borders; - juce::Colour m_resize_colour; - juce::Colour m_inner_colour; - }; - - private: // methods - - //! @brief Paints outlets, inlets over child component. - void paintOverChildren (juce::Graphics& g) override final; - - //! @brief Called whenever the object's size changes. - void resized() override final; - - //! @brief Called once a ClassicView's text has changed. - void textChanged(std::string const& new_text) override final; - - //! @brief Called when the classic view ends its edition. - void editorHidden() override final; - - //! @brief Called when the classic view enters its edition mode. - void editorShown() override final; - - //! @brief Called to update the bounds of the object. - void updateBoundsFromModel(bool animate); - - //! @brief Updates the outline according to the selection status. - //! @details Makes it visible or not and updates its colour. - void updateOutline(); - - //! @brief Called when the object's view size has changed. - //! @details The obect frame adapts to fit the underlying object's size. - void childBoundsChanged(juce::Component * child) override final; - - //! @brief Returns the patcher view component. - PatcherView& getPatcherView() const; - - //! @brief Returns a list of Users that selected an object. - std::set getDistantSelection() const; - - //! @brief Draws the inlets/outlets of the object. - void drawInletsOutlets(juce::Graphics & g); - - //! @brief Returns the inlet local bounds for a given index. - juce::Rectangle getInletLocalBounds(const size_t index) const; - - //! @brief Returns the outlet local bounds for a given index. - juce::Rectangle getOutletLocalBounds(const size_t index) const; - - //! @brief Returns the width of any outlet/inlet represented in the object's frame. - size_t getPinWidth() const; - - //! @brief Returns the height of any outlet/inlet represented in the object's frame. - size_t getPinHeight() const; - - //! @brief Returns true if the object is locked. - bool isLocked() const; - - private: // members - - std::unique_ptr m_object_view; - PatcherView& m_patcher_view; - const size_t m_io_width; - const size_t m_io_height; - size_t m_inlets; - size_t m_outlets; - Outline m_outline; - - private: // deleted methods - - ObjectFrame() = delete; - ObjectFrame(ObjectFrame const& other) = delete; - ObjectFrame(ObjectFrame && other) = delete; - ObjectFrame& operator=(ObjectFrame const& other) = delete; - ObjectFrame& operator=(ObjectFrame && other) = delete; - }; -} + }; + + public: // methods + + //! @brief Constructor. + Outline(int resize_length, int resize_thickness); + + //! @brief Tests if the point reaches an interactive resiable corner. + bool hitTest(juce::Point const& pt, HitTester& hit_tester) const; + + //! @brief Returns the corner border width. + int getBorderThickness() const; + + //! @brief Returns the corner border length. + int getResizeLength() const; + + ~Outline(); + + private: // methods + + //! @brief Draws a corner. + void drawCorner(juce::Graphics & g, Border border); + + //! @brief Graphical rendering method. + void paint(juce::Graphics & g) override final; + + //! @brief Call once size changed. Recompute borders and corners position. + void resized() override final; + + //! @brief Update the corners position + void updateCorners(); + + private: // members + + int m_resize_length; + int m_resize_thickness; + std::map, 3>> m_corners; + }; + + private: // methods + + //! @brief Paints outlets, inlets over child component. + void paintOverChildren (juce::Graphics& g) override final; + + //! @brief Called whenever the object's size changes. + void resized() override final; + + //! @brief Called once a ClassicView's text has changed. + void textChanged(std::string const& new_text) override final; + + //! @brief Called when the classic view ends its edition. + void editorHidden() override final; + + //! @brief Called when the classic view enters its edition mode. + void editorShown() override final; + + //! @brief Called to update the bounds of the object. + void updateBoundsFromModel(bool animate); + + //! @brief Updates the outline according to the selection status. + //! @details Makes it visible or not and updates its colour. + void updateOutline(); + + //! @brief Called when the object's view size has changed. + //! @details The obect frame adapts to fit the underlying object's size. + void childBoundsChanged(juce::Component * child) override final; + + //! @brief Returns the patcher view component. + PatcherView& getPatcherView() const; + + //! @brief Returns a list of Users that selected an object. + std::set getDistantSelection() const; + + //! @brief Returns the number of inlets + size_t getNumberOfInlets() const noexcept; + + //! @brief Returns the number of outlets + size_t getNumberOfOutlets() const noexcept; + + //! @brief Update the inlets + void updateInlets(model::Object const& object); + + //! @brief Update the outlets + void updateOutlets(model::Object const& object); + + //! @brief Draws the inlets/outlets of the object. + void drawInletsOutlets(juce::Graphics & g); + + //! @brief Returns the inlet local bounds for a given index. + juce::Rectangle getInletLocalBounds(const size_t index) const; + + //! @brief Returns the outlet local bounds for a given index. + juce::Rectangle getOutletLocalBounds(const size_t index) const; + + //! @brief Returns the width of any outlet/inlet represented in the object's frame. + size_t getPinWidth() const; + + //! @brief Returns the height of any outlet/inlet represented in the object's frame. + size_t getPinHeight() const; + + //! @brief Returns true if the object is locked. + bool isLocked() const; + + private: // members + + struct Pin + { + Pin(bool pin_is_signal) + : is_signal(pin_is_signal) + {} + + bool is_signal {false}; + }; + + std::unique_ptr m_object_view; + PatcherView& m_patcher_view; + const size_t m_io_width; + const size_t m_io_height; + std::vector m_inlets; + std::vector m_outlets; + Outline m_outline; + + private: // deleted methods + + ObjectFrame() = delete; + ObjectFrame(ObjectFrame const& other) = delete; + ObjectFrame(ObjectFrame && other) = delete; + ObjectFrame& operator=(ObjectFrame const& other) = delete; + ObjectFrame& operator=(ObjectFrame && other) = delete; + }; +} + diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h index 2d76ce01..d8ebe30e 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h @@ -48,13 +48,14 @@ namespace kiwi enum ColourIds { - Pin = 0x1100003, - Background = 0x1100004, - Error = 0x1100005, - Text = 0x1100006, - Outline = 0x1100007, - Highlight = 0x1100008, - Active = 0x1100009 + PinControl = 0x1100013, + PinSignal = 0x1100015, + Background = 0x1100024, + Error = 0x1100025, + Text = 0x1100026, + Outline = 0x1100027, + Highlight = 0x1100028, + Active = 0x1100029 }; public: // methods From be10d9f87a30b99413df07ac7709dfd239584b21 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Thu, 14 Jun 2018 21:42:53 +0200 Subject: [PATCH 23/41] DocumentBrowserView: - Add a dialog box to set the name of the patch to create - trashed file can be opened --- .../KiwiApp_DocumentBrowserView.cpp | 77 ++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp index ff96cdfd..42b54bb4 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp @@ -33,6 +33,8 @@ #include "../KiwiApp_Ressources/KiwiApp_BinaryData.h" #include "../KiwiApp.h" +#include + namespace kiwi { // ================================================================================ // @@ -177,6 +179,7 @@ namespace kiwi { m_open_btn.setBounds(bounds.reduced(5).withLeft(bounds.getWidth() - 40)); } + m_name_label.setBounds(bounds.reduced(5).withRight(m_open_btn.getX() - 5).withLeft(40)); } @@ -368,53 +371,46 @@ namespace kiwi if (!m_drive_view.isShowingTrashedDocuments()) m_drive_view.createDocument(); }); + m_create_document_btn.setSize(40, 40); - m_create_document_btn.setTooltip("Create a new patcher on this drive"); - m_create_document_btn.setColour(ImageButton::ColourIds::textColourId, juce::Colours::whitesmoke); + m_create_document_btn.setTooltip("Create a new hosted patcher"); addAndMakeVisible(m_create_document_btn); - m_refresh_btn.setCommand([this](){m_drive_view.refresh();}); + m_refresh_btn.setCommand([this](){ + m_drive_view.refresh(); + }); + m_refresh_btn.setSize(40, 40); - m_refresh_btn.setTooltip("Refresh Document list"); - m_refresh_btn.setColour(ImageButton::ColourIds::textColourId, juce::Colours::whitesmoke); + m_refresh_btn.setTooltip("Refresh list"); addAndMakeVisible(m_refresh_btn); m_trash_btn.setCommand([this]() { - bool new_trash_mode = !m_drive_view.isShowingTrashedDocuments(); - m_drive_view.setTrashMode(new_trash_mode); - m_trash_btn.setAlpha(new_trash_mode ? 1. : 0.5); - m_create_document_btn.setAlpha(new_trash_mode ? 0.5 : 1.); + const bool was_in_trash_mode = m_drive_view.isShowingTrashedDocuments(); + const bool is_in_trash_mode = !was_in_trash_mode; + m_drive_view.setTrashMode(is_in_trash_mode); + m_trash_btn.setTooltip(juce::String(is_in_trash_mode ? "Hide" : "Show") + + " trashed documents"); + + m_create_document_btn.setEnabled(!is_in_trash_mode); + m_trash_btn.setToggleState(is_in_trash_mode, + juce::NotificationType::dontSendNotification); }); - m_trash_btn.setAlpha(m_drive_view.isShowingTrashedDocuments() ? 1. : 0.5); + + m_trash_btn.setToggleState(m_drive_view.isShowingTrashedDocuments(), + juce::NotificationType::dontSendNotification); + m_trash_btn.setSize(40, 40); m_trash_btn.setTooltip("Display trashed documents"); addAndMakeVisible(m_trash_btn); - } void DocumentBrowserView::DriveView::Header::enablementChanged() { - if (isEnabled()) - { - m_refresh_btn.setCommand([this](){m_drive_view.refresh();}); - m_refresh_btn.setAlpha(1); - - m_create_document_btn.setCommand([this]() - { - if (!m_drive_view.isShowingTrashedDocuments()) - m_drive_view.createDocument(); - }); - m_create_document_btn.setAlpha(1); - } - else - { - m_refresh_btn.setCommand([](){}); - m_refresh_btn.setAlpha(0.5); - - m_create_document_btn.setCommand([](){}); - m_create_document_btn.setAlpha(0.5); - } + const bool enabled = isEnabled(); + m_create_document_btn.setEnabled(enabled); + m_refresh_btn.setEnabled(enabled); + m_trash_btn.setEnabled(enabled); } void DocumentBrowserView::DriveView::Header::resized() @@ -595,7 +591,19 @@ namespace kiwi void DocumentBrowserView::DriveView::createDocument() { - m_drive.createNewDocument(); + juce::AlertWindow alert("Create a new hosted Patcher", "name:", + juce::AlertWindow::AlertIconType::NoIcon); + + alert.addButton("Cancel", 0); + alert.addButton("Ok", 1); + alert.addTextEditor("title", "Untitled"); + + if(alert.runModalLoop()) + { + auto& text_editor = *alert.getTextEditor("title"); + auto text = text_editor.getText(); + m_drive.createNewDocument(text.toStdString()); + } } void DocumentBrowserView::DriveView::setTrashMode(bool trash_mode) @@ -810,9 +818,6 @@ namespace kiwi void DocumentBrowserView::DriveView::listBoxItemDoubleClicked(int row, juce::MouseEvent const& e) { - if (!isShowingTrashedDocuments()) - { - openDocument(row); - } + openDocument(row); } } From 53885319bad7293d7066aca1075117d034bfea9d Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Thu, 14 Jun 2018 21:42:53 +0200 Subject: [PATCH 24/41] fixup! DocumentBrowserView: - Add a dialog box to set the name of the patch to create - trashed file can be opened --- .../KiwiApp_DocumentBrowserView.cpp | 77 ++++++++++--------- .../KiwiApp_ImageButton.cpp | 6 +- .../KiwiApp_Components/KiwiApp_ImageButton.h | 2 +- .../KiwiApp_DocumentBrowser.cpp | 4 +- .../KiwiApp_Network/KiwiApp_DocumentBrowser.h | 2 +- 5 files changed, 50 insertions(+), 41 deletions(-) diff --git a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp index ff96cdfd..42b54bb4 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp @@ -33,6 +33,8 @@ #include "../KiwiApp_Ressources/KiwiApp_BinaryData.h" #include "../KiwiApp.h" +#include + namespace kiwi { // ================================================================================ // @@ -177,6 +179,7 @@ namespace kiwi { m_open_btn.setBounds(bounds.reduced(5).withLeft(bounds.getWidth() - 40)); } + m_name_label.setBounds(bounds.reduced(5).withRight(m_open_btn.getX() - 5).withLeft(40)); } @@ -368,53 +371,46 @@ namespace kiwi if (!m_drive_view.isShowingTrashedDocuments()) m_drive_view.createDocument(); }); + m_create_document_btn.setSize(40, 40); - m_create_document_btn.setTooltip("Create a new patcher on this drive"); - m_create_document_btn.setColour(ImageButton::ColourIds::textColourId, juce::Colours::whitesmoke); + m_create_document_btn.setTooltip("Create a new hosted patcher"); addAndMakeVisible(m_create_document_btn); - m_refresh_btn.setCommand([this](){m_drive_view.refresh();}); + m_refresh_btn.setCommand([this](){ + m_drive_view.refresh(); + }); + m_refresh_btn.setSize(40, 40); - m_refresh_btn.setTooltip("Refresh Document list"); - m_refresh_btn.setColour(ImageButton::ColourIds::textColourId, juce::Colours::whitesmoke); + m_refresh_btn.setTooltip("Refresh list"); addAndMakeVisible(m_refresh_btn); m_trash_btn.setCommand([this]() { - bool new_trash_mode = !m_drive_view.isShowingTrashedDocuments(); - m_drive_view.setTrashMode(new_trash_mode); - m_trash_btn.setAlpha(new_trash_mode ? 1. : 0.5); - m_create_document_btn.setAlpha(new_trash_mode ? 0.5 : 1.); + const bool was_in_trash_mode = m_drive_view.isShowingTrashedDocuments(); + const bool is_in_trash_mode = !was_in_trash_mode; + m_drive_view.setTrashMode(is_in_trash_mode); + m_trash_btn.setTooltip(juce::String(is_in_trash_mode ? "Hide" : "Show") + + " trashed documents"); + + m_create_document_btn.setEnabled(!is_in_trash_mode); + m_trash_btn.setToggleState(is_in_trash_mode, + juce::NotificationType::dontSendNotification); }); - m_trash_btn.setAlpha(m_drive_view.isShowingTrashedDocuments() ? 1. : 0.5); + + m_trash_btn.setToggleState(m_drive_view.isShowingTrashedDocuments(), + juce::NotificationType::dontSendNotification); + m_trash_btn.setSize(40, 40); m_trash_btn.setTooltip("Display trashed documents"); addAndMakeVisible(m_trash_btn); - } void DocumentBrowserView::DriveView::Header::enablementChanged() { - if (isEnabled()) - { - m_refresh_btn.setCommand([this](){m_drive_view.refresh();}); - m_refresh_btn.setAlpha(1); - - m_create_document_btn.setCommand([this]() - { - if (!m_drive_view.isShowingTrashedDocuments()) - m_drive_view.createDocument(); - }); - m_create_document_btn.setAlpha(1); - } - else - { - m_refresh_btn.setCommand([](){}); - m_refresh_btn.setAlpha(0.5); - - m_create_document_btn.setCommand([](){}); - m_create_document_btn.setAlpha(0.5); - } + const bool enabled = isEnabled(); + m_create_document_btn.setEnabled(enabled); + m_refresh_btn.setEnabled(enabled); + m_trash_btn.setEnabled(enabled); } void DocumentBrowserView::DriveView::Header::resized() @@ -595,7 +591,19 @@ namespace kiwi void DocumentBrowserView::DriveView::createDocument() { - m_drive.createNewDocument(); + juce::AlertWindow alert("Create a new hosted Patcher", "name:", + juce::AlertWindow::AlertIconType::NoIcon); + + alert.addButton("Cancel", 0); + alert.addButton("Ok", 1); + alert.addTextEditor("title", "Untitled"); + + if(alert.runModalLoop()) + { + auto& text_editor = *alert.getTextEditor("title"); + auto text = text_editor.getText(); + m_drive.createNewDocument(text.toStdString()); + } } void DocumentBrowserView::DriveView::setTrashMode(bool trash_mode) @@ -810,9 +818,6 @@ namespace kiwi void DocumentBrowserView::DriveView::listBoxItemDoubleClicked(int row, juce::MouseEvent const& e) { - if (!isShowingTrashedDocuments()) - { - openDocument(row); - } + openDocument(row); } } diff --git a/Client/Source/KiwiApp_Components/KiwiApp_ImageButton.cpp b/Client/Source/KiwiApp_Components/KiwiApp_ImageButton.cpp index 51b4a5cc..370ddaf1 100644 --- a/Client/Source/KiwiApp_Components/KiwiApp_ImageButton.cpp +++ b/Client/Source/KiwiApp_Components/KiwiApp_ImageButton.cpp @@ -36,7 +36,11 @@ namespace kiwi m_current_image(nullptr), m_edge_indent(3) { - setImages(drawable.get()); + setImages(drawable.get()); + const auto transparent = juce::Colours::transparentWhite; + setColour(ImageButton::ColourIds::backgroundColourId, transparent); + setColour(ImageButton::ColourIds::backgroundOnColourId, transparent); + setColour(ImageButton::ColourIds::textColourId, juce::Colours::whitesmoke); } ImageButton::~ImageButton() diff --git a/Client/Source/KiwiApp_Components/KiwiApp_ImageButton.h b/Client/Source/KiwiApp_Components/KiwiApp_ImageButton.h index ebb8626d..d0421d10 100644 --- a/Client/Source/KiwiApp_Components/KiwiApp_ImageButton.h +++ b/Client/Source/KiwiApp_Components/KiwiApp_ImageButton.h @@ -114,7 +114,7 @@ namespace kiwi int m_edge_indent; - std::function m_command; + std::function m_command = nullptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImageButton) }; diff --git a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp index dd021f21..4fe07c87 100644 --- a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp +++ b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp @@ -163,11 +163,11 @@ namespace kiwi }); } - void DocumentBrowser::Drive::createNewDocument() + void DocumentBrowser::Drive::createNewDocument(std::string const& name) { std::weak_ptr drive(m_drive); - KiwiApp::useApi().createDocument("", [drive](Api::Response res, Api::Document document) + KiwiApp::useApi().createDocument(name, [drive](Api::Response res, Api::Document document) { KiwiApp::useScheduler().schedule([drive, res, document]() { diff --git a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h index f098e9f4..e625766d 100644 --- a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h +++ b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h @@ -113,7 +113,7 @@ namespace kiwi void uploadDocument(std::string const& name, std::string const& data); //! @brief Creates and opens a new document on this drive. - void createNewDocument(); + void createNewDocument(std::string const& document_name); //! @brief Changes the way documents are sorted. void setSort(Comp comp); From 72bdb51e3c27adbdba8b61756324758656597e52 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Fri, 15 Jun 2018 19:02:09 +0200 Subject: [PATCH 25/41] fix object resizing behavior --- .../KiwiApp_Objects/KiwiApp_BangView.cpp | 8 +- .../KiwiApp_Objects/KiwiApp_ClassicView.cpp | 9 +- .../KiwiApp_Objects/KiwiApp_ClassicView.h | 5 +- .../KiwiApp_Objects/KiwiApp_CommentView.cpp | 13 +- .../KiwiApp_Objects/KiwiApp_CommentView.h | 3 - .../KiwiApp_EditableObjectView.cpp | 40 ++- .../KiwiApp_EditableObjectView.h | 9 + .../KiwiApp_Objects/KiwiApp_MessageView.cpp | 10 +- .../KiwiApp_Objects/KiwiApp_MessageView.h | 5 +- .../KiwiApp_MeterTildeView.cpp | 20 +- .../KiwiApp_Objects/KiwiApp_MeterTildeView.h | 3 - .../KiwiApp_NumberViewBase.cpp | 6 +- .../KiwiApp_Objects/KiwiApp_NumberViewBase.h | 3 - .../KiwiApp_Objects/KiwiApp_ObjectFrame.cpp | 95 ++++--- .../KiwiApp_Objects/KiwiApp_ObjectFrame.h | 8 +- .../KiwiApp_Objects/KiwiApp_ObjectView.cpp | 57 +++- .../KiwiApp_Objects/KiwiApp_ObjectView.h | 25 +- .../KiwiApp_Objects/KiwiApp_SliderView.cpp | 25 +- .../KiwiApp_Objects/KiwiApp_SliderView.h | 5 +- .../KiwiApp_Objects/KiwiApp_ToggleView.cpp | 4 +- .../KiwiApp_Patcher/KiwiApp_PatcherView.cpp | 10 +- .../KiwiApp_PatcherViewMouseHandler.cpp | 247 ++++++++++-------- .../KiwiApp_PatcherViewMouseHandler.h | 44 ++-- Modules/KiwiModel/KiwiModel_ObjectClass.h | 4 +- .../KiwiModel_Objects/KiwiModel_Bang.cpp | 4 - .../KiwiModel_Objects/KiwiModel_Comment.cpp | 5 +- .../KiwiModel_Objects/KiwiModel_Message.cpp | 2 - .../KiwiModel_MeterTilde.cpp | 4 - .../KiwiModel_Objects/KiwiModel_Number.cpp | 3 - .../KiwiModel_NumberTilde.cpp | 3 - .../KiwiModel_Objects/KiwiModel_Slider.cpp | 6 - .../KiwiModel_Objects/KiwiModel_Toggle.cpp | 4 - 32 files changed, 381 insertions(+), 308 deletions(-) diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp index af242d47..c122da4d 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp @@ -48,11 +48,13 @@ namespace kiwi { , m_flash_connection(m_flash_signal.connect(std::bind(&BangView::signalTriggered, this))) , m_active(false) , m_mouse_down(false) - {} + { + setMinimumSize(20.f, 20.f); + setFixedAspectRatio(1.f); + } BangView::~BangView() - { - } + {} void BangView::paint(juce::Graphics & g) { diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp index 0bdb91e4..c9d6da3a 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.cpp @@ -55,14 +55,7 @@ namespace kiwi } ClassicView::~ClassicView() - {} - - void ClassicView::validateSize(int& new_width, int& new_height) - { - new_width = std::max(new_width, getMinWidth()); - const auto text_bounds = getTextBoundingBox(getLabel().getText(), new_width); - new_height = std::max(text_bounds.getHeight(), getMinHeight()); - } + {} void ClassicView::paintOverChildren (juce::Graphics& g) { diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h index 0a7fe50a..badab6df 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h @@ -49,10 +49,7 @@ namespace kiwi //! @brief Destructor. ~ClassicView(); - private: // methods - - //! @brief Validate the new width and height for the box - void validateSize(int& new_width, int& new_height) override; + private: // methods //! @brief Constructs the label's text editor. //! @brief Overrides EditableObjectView::createTextEditor. diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp index 695004c8..f01d781e 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp @@ -88,13 +88,6 @@ namespace kiwi { g.setColour(findColour (ObjectView::ColourIds::Outline)); g.strokePath(path, path_stroke); } - } - - void CommentView::validateSize(int& new_width, int& new_height) - { - new_width = std::max(new_width, getMinWidth()); - const auto text_bounds = getTextBoundingBox(getLabel().getText(), new_width); - new_height = std::max(text_bounds.getHeight(), getMinHeight()); } void CommentView::textEditorTextChanged(juce::TextEditor& editor) @@ -111,11 +104,7 @@ namespace kiwi { { const auto new_text = param[0].getString(); getLabel().setText(new_text, juce::NotificationType::dontSendNotification); - - const auto width = getWidth(); - const auto text_bounds = getTextBoundingBox(new_text, width); - setSize(std::max(width, getMinWidth()), - std::max(text_bounds.getHeight(), getMinHeight())); + checkComponentBounds(this); } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.h index 13091b75..1c556bf9 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.h @@ -54,9 +54,6 @@ namespace kiwi { private: // methods - //! @brief Validate the new width and height for the box - void validateSize(int& new_width, int& new_height) override; - //! @brief Called every time a patcher is locked or unlocked. void lockStatusChanged(bool is_locked) override; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp index 7db7e280..24b879fb 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp @@ -36,10 +36,48 @@ namespace kiwi { m_label.setInterceptsMouseClicks(false, false); m_label.setBorderSize(juce::BorderSize(getPadding())); m_label.setJustificationType(juce::Justification::topLeft); + + setMinimumWidth(20); + setMinimumHeight(20); + + canGrowVertically(false); + canGrowHorizontally(true); } EditableObjectView::~EditableObjectView() - {} + {} + + void EditableObjectView::checkBounds(juce::Rectangle& bounds, + juce::Rectangle const& prev_bounds, + juce::Rectangle const& limits, + bool stretching_top, + bool stretching_left, + bool stretching_bottom, + bool stretching_right) + { + // the resize routine could impose a different ratio to our box + // but we do not support fixed ratio for text box now + // Todo: handle fixed ratio by resizing font height. + setFixedAspectRatio(0); + + juce::ComponentBoundsConstrainer::checkBounds(bounds, prev_bounds, + limits, + stretching_top, stretching_left, + stretching_bottom, stretching_right); + if(!canGrowVertically()) + { + if(stretching_top) + { + bounds.setY(prev_bounds.getY()); + } + + bounds.setHeight(prev_bounds.getHeight()); + } + + const auto new_width = std::max(bounds.getWidth(), getMinWidth()); + const auto text_bounds = getTextBoundingBox(getLabel().getText(), new_width); + bounds.setHeight(std::max(text_bounds.getHeight(), getMinHeight())); + } void EditableObjectView::setEditable(bool editable) { diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.h index dda39863..7e05ca80 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.h @@ -75,6 +75,15 @@ namespace kiwi //! @brief Get the current padding value. int getPadding() const; + //! @brief Check Component bounds. + void checkBounds (juce::Rectangle& bounds, + juce::Rectangle const& previousBounds, + juce::Rectangle const& limits, + bool isStretchingTop, + bool isStretchingLeft, + bool isStretchingBottom, + bool isStretchingRight) override; + //! @brief Get the minimum height. //! @details The minimum height is (font_height + 2 * padding) int getMinHeight() const; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp index 570bad25..f029cd5c 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp @@ -68,14 +68,7 @@ namespace kiwi { } MessageView::~MessageView() - {} - - void MessageView::validateSize(int& new_width, int& new_height) - { - new_width = std::max(new_width, getMinWidth()); - const auto text_bounds = getTextBoundingBox(getLabel().getText(), new_width); - new_height = std::max(text_bounds.getHeight(), getMinHeight()); - } + {} void MessageView::mouseDown(juce::MouseEvent const& e) { @@ -145,6 +138,7 @@ namespace kiwi { if (name == name_text) { setText(param[0].getString()); + checkComponentBounds(this); } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.h index 38e4b90b..3f443e8d 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.h @@ -52,10 +52,7 @@ namespace kiwi { //! @brief Destructor. ~MessageView(); - private: // methods - - //! @brief Validate the new width and height for the box - void validateSize(int& new_width, int& new_height) override; + private: // methods //! @brief Called when the message is clicked. void mouseDown(juce::MouseEvent const& e) override; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp index 2bb7b730..00c5fb60 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp @@ -65,24 +65,14 @@ namespace kiwi MeterTildeView::~MeterTildeView() {} - void MeterTildeView::validateSize(int& new_width, int& new_height) - { - const int min = 40; - const bool vertical = (new_width <= new_height); - if(vertical) - { - new_height = std::max(min, new_height); - } - else - { - new_width = std::max(min, new_width); - } - } - void MeterTildeView::resized() { const auto bounds = getLocalBounds().toFloat(); - const bool vertical = (bounds.getWidth() <= bounds.getHeight()); + + const int min = 10; + const int max = 30; + const bool vertical = (getWidth() <= getHeight()); + setMinimumSize(vertical ? min : max, vertical ? max : min); const float num_leds = m_leds.size(); const float padding = m_padding; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.h index cb4f6c58..2bbae9c9 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.h @@ -56,9 +56,6 @@ namespace kiwi { private: // methods - //! @brief Validate the new width and height for the box - void validateSize(int& new_width, int& new_height) override; - void resized() override final; void paint(juce::Graphics & g) override final; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp index 96e92c22..4c9cc696 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp @@ -48,16 +48,12 @@ namespace kiwi { label.setInterceptsMouseClicks(false, false); addAndMakeVisible(label); + setMinimumSize(20., getMinHeight()); } NumberViewBase::~NumberViewBase() {} - void NumberViewBase::validateSize(int& new_width, int& new_height) - { - new_height = getMinHeight(); - } - void NumberViewBase::paint(juce::Graphics & g) { g.fillAll(findColour(ObjectView::ColourIds::Background)); diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.h index 604c062a..aed12c4c 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.h @@ -59,9 +59,6 @@ namespace kiwi { private: // methods - //! @brief Validate the new width and height for the box - void validateSize(int& new_width, int& new_height) override; - //! @brief Called when the object is resized. void resized() override final; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp index 52e79b21..8f2fc338 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp @@ -89,16 +89,15 @@ namespace kiwi if(!model.removed()) { - const juce::Point origin = m_patcher_view.getOriginPosition(); - - int width = model.getWidth(); - int height = model.getHeight(); - - validateSize(width, height); + const juce::Point origin = m_patcher_view.getOriginPosition(); - const juce::Rectangle object_bounds(model.getX() + origin.getX(), - model.getY() + origin.getY(), - width, height); + juce::Rectangle object_bounds(model.getX() + origin.getX(), + model.getY() + origin.getY(), + model.getWidth(), model.getHeight()); + + m_object_view->checkBounds(object_bounds, + m_object_view->getBounds(), {}, + false, false, false, false); const juce::Rectangle frame_bounds = object_bounds.expanded(m_outline.getBorderThickness()); @@ -182,10 +181,15 @@ namespace kiwi return object_view_bounds.withPosition(getPosition() + object_view_bounds.getPosition()); } - void ObjectFrame::validateSize(int& new_width, int& new_height) + juce::ComponentBoundsConstrainer* ObjectFrame::getBoundsConstrainer() const { - m_object_view->validateSize(new_width, new_height); - } + return m_object_view.get(); + } + + int ObjectFrame::getResizingFlags() const + { + return m_object_view->getResizingFlags(); + } void ObjectFrame::mouseDown(juce::MouseEvent const& e) { @@ -422,30 +426,39 @@ namespace kiwi juce::Rectangle ObjectFrame::getInletLocalBounds(const size_t index) const { - juce::Rectangle inlet_bounds = m_object_view->getBounds(); - - const auto space_to_remove = m_outline.getResizeLength() - m_outline.getBorderThickness(); - inlet_bounds.removeFromLeft(space_to_remove); - inlet_bounds.removeFromRight(space_to_remove); - juce::Rectangle rect; + const auto ninlets = getNumberOfInlets(); if(ninlets > 0 && index < ninlets) - { + { + juce::Rectangle inlet_bounds = m_object_view->getBounds(); + + auto pin_width = getPinWidth(); + + const auto border_width = (m_outline.getResizeLength() + - m_outline.getBorderThickness()); + + const auto space_to_remove = juce::jlimit(0, border_width, inlet_bounds.getWidth() - (pin_width * ninlets) - border_width); + + if(space_to_remove > 0) + { + inlet_bounds.removeFromLeft(space_to_remove); + inlet_bounds.removeFromRight(space_to_remove); + } + if(ninlets == 1 && index == 0) { rect.setBounds(inlet_bounds.getX(), inlet_bounds.getY(), - getPinWidth(), getPinHeight()); + pin_width, getPinHeight()); } - - if(ninlets > 1) + else if(ninlets > 1) { - const double ratio = (inlet_bounds.getWidth() - getPinWidth()) / (double)(ninlets - 1); + const double ratio = (inlet_bounds.getWidth() - pin_width) / (double)(ninlets - 1); rect.setBounds(inlet_bounds.getX() + ratio * index, inlet_bounds.getY(), - getPinWidth(), getPinHeight()); + pin_width, getPinHeight()); } } @@ -453,22 +466,32 @@ namespace kiwi } juce::Rectangle ObjectFrame::getOutletLocalBounds(const size_t index) const - { - juce::Rectangle outlet_bounds = m_object_view->getBounds(); - - outlet_bounds.removeFromLeft(m_outline.getResizeLength() - m_outline.getBorderThickness()); - outlet_bounds.removeFromRight(m_outline.getResizeLength() - m_outline.getBorderThickness()); - + { juce::Rectangle rect; - const auto noutlets = getNumberOfOutlets(); + const auto noutlets = getNumberOfOutlets(); if(noutlets > 0 && index < noutlets) - { + { + juce::Rectangle outlet_bounds = m_object_view->getBounds(); + + auto pin_width = getPinWidth(); + + const auto border_width = (m_outline.getResizeLength() + - m_outline.getBorderThickness()); + + const auto space_to_remove = juce::jlimit(0, border_width, outlet_bounds.getWidth() - (pin_width * noutlets) - border_width); + + if(space_to_remove > 0) + { + outlet_bounds.removeFromLeft(space_to_remove); + outlet_bounds.removeFromRight(space_to_remove); + } + if(noutlets == 1 && index == 0) { rect.setBounds(outlet_bounds.getX(), outlet_bounds.getY() + outlet_bounds.getHeight() - getPinHeight(), - getPinWidth(), getPinHeight()); + pin_width, getPinHeight()); } if(noutlets > 1) @@ -476,7 +499,7 @@ namespace kiwi const double ratio = (outlet_bounds.getWidth() - getPinWidth()) / (double)(noutlets - 1); rect.setBounds(outlet_bounds.getX() + ratio * index, outlet_bounds.getY() + outlet_bounds.getHeight() - getPinHeight(), - getPinWidth(), getPinHeight()); + pin_width, getPinHeight()); } } @@ -623,8 +646,8 @@ namespace kiwi const auto outline_bounds = getLocalBounds().toFloat(); const auto object_bounds = outline_bounds.reduced(m_resize_thickness); - const float thickness = m_resize_thickness * 0.5f; - g.drawRect(outline_bounds.reduced(thickness), thickness); + const float thickness = m_resize_thickness; + g.drawRect(outline_bounds.reduced(thickness*0.75), thickness*0.25); /* // The following code do the same juce::RectangleList rectangles (outline_bounds); diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h index 86aa6397..ceaf9348 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h @@ -105,8 +105,12 @@ namespace kiwi //! @brief Called when object's frame is clicked. void mouseDrag(juce::MouseEvent const& e) override final; - //! @brief Validate the new width and height for the box - void validateSize(int& new_width, int& new_height); + //! @brief Get the ComponentBoundsConstrainer. + juce::ComponentBoundsConstrainer* getBoundsConstrainer() const; + + //! @brief Get the resizing Flag as a set of border. + //! @see HitTester::Border + int getResizingFlags() const; public: // classes diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp index 8b6fda66..11cbc51b 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp @@ -35,9 +35,12 @@ namespace kiwi ObjectView::ObjectView(model::Object & object_model) : m_model(object_model) - , m_master(this, [](ObjectView*){}) + , m_master(this, [](ObjectView*){}) + , m_resizing_flags(HitTester::Border::None) { - object_model.addListener(*this); + object_model.addListener(*this); + canGrowHorizontally(true); + canGrowVertically(true); } ObjectView::~ObjectView() @@ -85,11 +88,6 @@ namespace kiwi juce::Rectangle ObjectView::getOutline() const { return getLocalBounds(); - } - - void ObjectView::validateSize(int& new_width, int& new_height) - { - ; } void ObjectView::drawOutline(juce::Graphics & g) @@ -102,6 +100,51 @@ namespace kiwi void ObjectView::lockStatusChanged(bool is_locked) { // nothing to do by default + } + + int ObjectView::getResizingFlags() const + { + return m_resizing_flags; + } + + bool ObjectView::canGrowHorizontally() const + { + return ((m_resizing_flags & HitTester::Border::Left) + || m_resizing_flags & HitTester::Border::Right); + } + + bool ObjectView::canGrowVertically() const + { + return ((m_resizing_flags & HitTester::Border::Top) + || m_resizing_flags & HitTester::Border::Bottom); + } + + void ObjectView::canGrowHorizontally(bool can) + { + if(can) + { + m_resizing_flags |= HitTester::Border::Left; + m_resizing_flags |= HitTester::Border::Right; + } + else + { + m_resizing_flags &= ~HitTester::Border::Left; + m_resizing_flags &= ~HitTester::Border::Right; + } + } + + void ObjectView::canGrowVertically(bool can) + { + if(can) + { + m_resizing_flags |= HitTester::Border::Top; + m_resizing_flags |= HitTester::Border::Bottom; + } + else + { + m_resizing_flags &= ~HitTester::Border::Top; + m_resizing_flags &= ~HitTester::Border::Bottom; + } } // ================================================================================ // diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h index d8ebe30e..fdaa3be4 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h @@ -42,6 +42,7 @@ namespace kiwi //! @brief Abstract for objects graphical representation. class ObjectView : public juce::Component + , public juce::ComponentBoundsConstrainer , public model::Object::Listener { public: // classes @@ -75,13 +76,26 @@ namespace kiwi //! @brief Called when a parameter has changed. void modelParameterChanged(std::string const& name, tool::Parameter const& param) override final; - //! @brief Validate the new width and height for the box - virtual void validateSize(int& new_width, int& new_height); - //! @brief Called every time a patcher is locked or unlocked. - virtual void lockStatusChanged(bool is_locked); + virtual void lockStatusChanged(bool is_locked); + + //! @brief Get the resizing Flag as a set of border. + //! @see HitTester::Border + int getResizingFlags() const; + + //! @brief Returns true if the box can grow horizontally + bool canGrowHorizontally() const; + + //! @brief Returns true if the box can grow vertically + bool canGrowVertically() const; protected: // methods + + //! @brief Pass true if the box can grow horizontally + void canGrowHorizontally(bool can); + + //! @brief Pass true if the box can grow vertically + void canGrowVertically(bool can); //! @biref Returns the main scheduler. tool::Scheduler<> & getScheduler() const; @@ -119,7 +133,8 @@ namespace kiwi private: // members model::Object& m_model; - std::shared_ptr m_master; + std::shared_ptr m_master; + int m_resizing_flags; private: // deleted methods diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.cpp index 952fdf40..ba6ce920 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.cpp @@ -63,20 +63,6 @@ namespace kiwi SliderView::~SliderView() { m_slider.removeListener(this); - } - - void SliderView::validateSize(int& new_width, int& new_height) - { - const int min = 40; - const bool vertical = (new_width <= new_height); - if(vertical) - { - new_height = std::max(min, new_height); - } - else - { - new_width = std::max(min, new_width); - } } void SliderView::declare() @@ -120,13 +106,18 @@ namespace kiwi } void SliderView::resized() - { - if (getWidth() > getHeight() && m_slider.isVertical()) + { + const int min = 20; + const int max = 40; + const bool vertical = (getWidth() <= getHeight()); + setMinimumSize(vertical ? min : max, vertical ? max : min); + + if (!vertical && m_slider.isVertical()) { m_slider.setSliderStyle(juce::Slider::SliderStyle::LinearHorizontal); } - if (getHeight() > getWidth() && m_slider.isHorizontal()) + if (vertical && m_slider.isHorizontal()) { m_slider.setSliderStyle(juce::Slider::SliderStyle::LinearVertical); } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.h b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.h index f2e2b778..c1d65e1b 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_SliderView.h @@ -49,10 +49,7 @@ namespace kiwi static std::unique_ptr create(model::Object & object); - private: // methods - - //! @brief Validate the new width and height for the box - void validateSize(int& new_width, int& new_height) override; + private: // methods void sliderValueChanged(juce::Slider * slider) override final; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ToggleView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ToggleView.cpp index f58f717d..0cfb7210 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ToggleView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ToggleView.cpp @@ -42,7 +42,9 @@ namespace kiwi { ObjectView(model), m_signal(model.getSignal<>(model::Toggle::Signal::OutputValue)), m_is_on(false) - { + { + setMinimumSize(20.f, 20.f); + setFixedAspectRatio(1.f); } ToggleView::~ToggleView() diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp index 3e039bab..95863bef 100755 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp @@ -647,8 +647,8 @@ namespace kiwi bool PatcherView::keyPressed(const juce::KeyPress& key) { - if(m_mouse_handler.getCurrentAction() == MouseHandler::Action::MoveObject || - m_mouse_handler.getCurrentAction() == MouseHandler::Action::ResizeObject) + if(m_mouse_handler.getCurrentAction() == MouseHandler::Action::MoveObjects || + m_mouse_handler.getCurrentAction() == MouseHandler::Action::ResizeObjects) return false; // abort if(key.isKeyCode(juce::KeyPress::deleteKey) || key.isKeyCode(juce::KeyPress::backspaceKey)) @@ -729,7 +729,7 @@ namespace kiwi } m_view_model.setLock(locked); - model::DocumentManager::commit(m_patcher_model, "Edit mode switch"); + model::DocumentManager::commit(m_patcher_model); } bool PatcherView::isLocked() const @@ -1005,8 +1005,8 @@ namespace kiwi if(object.boundsChanged() && !patcher_area_uptodate && !view.removed() - && m_mouse_handler.getCurrentAction() != MouseHandler::Action::MoveObject - && m_mouse_handler.getCurrentAction() != MouseHandler::Action::ResizeObject) + && m_mouse_handler.getCurrentAction() != MouseHandler::Action::MoveObjects + && m_mouse_handler.getCurrentAction() != MouseHandler::Action::ResizeObjects) { m_viewport.updatePatcherArea(true); patcher_area_uptodate = true; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp index 306fd2f7..f90d094a 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp @@ -106,7 +106,7 @@ namespace kiwi break; } - case Action::MoveObject: + case Action::MoveObjects: { KiwiApp::commandStatusChanged(); @@ -191,7 +191,7 @@ namespace kiwi } break; } - case Action::ResizeObject: + case Action::ResizeObjects: { m_direction = getResizeDirection(hit_tester); @@ -218,45 +218,6 @@ namespace kiwi } } - void MouseHandler::applyNewBounds(model::Object & model, juce::Rectangle new_bounds, double ratio) const - { - juce::ComponentBoundsConstrainer constrainer; - - const double default_min_width = 20.; - const double default_min_height = 20.; - - constrainer.setMinimumWidth((model.getMinWidth() > 0) ? model.getMinWidth() : default_min_width); - constrainer.setMinimumHeight((model.getMinHeight() > 0) ? model.getMinHeight() : default_min_height); - - if (model.getRatio() != 0) - { - constrainer.setFixedAspectRatio(1 / model.getRatio()); - } - else if (ratio != 0) - { - constrainer.setFixedAspectRatio(1 / ratio); - } - - const juce::Rectangle limits {}; - - juce::Rectangle previous_bounds(model.getX(), model.getY(), - model.getWidth(), model.getHeight()); - - juce::Rectangle target_bounds = new_bounds; - - constrainer.checkBounds(target_bounds, - previous_bounds, - limits, - m_direction & Direction::Up, - m_direction & Direction::Left, - m_direction & Direction::Down, - m_direction & Direction::Right); - - model.setPosition(target_bounds.getX(), target_bounds.getY()); - model.setWidth(target_bounds.getWidth()); - model.setHeight(target_bounds.getHeight()); - } - void MouseHandler::continueAction(juce::MouseEvent const& e) { HitTester& hit_tester = m_patcher_view.m_hittester; @@ -270,7 +231,7 @@ namespace kiwi m_patcher_view.copySelectionToClipboard(); m_patcher_view.pasteFromClipboard({0, 0}); - startAction(Action::MoveObject, e); + startAction(Action::MoveObjects, e); } break; } @@ -326,7 +287,7 @@ namespace kiwi break; } - case Action::MoveObject: + case Action::MoveObjects: { if (m_patcher_view.isAnyObjectSelected()) { @@ -346,7 +307,7 @@ namespace kiwi { if (hit_tester.objectTouched() && e.getMouseDownPosition() != e.getPosition()) { - startAction(Action::MoveObject, e); + startAction(Action::MoveObjects, e); } break; } @@ -354,7 +315,7 @@ namespace kiwi { if (hit_tester.objectTouched() && e.getMouseDownPosition() != e.getPosition()) { - startAction(Action::MoveObject, e); + startAction(Action::MoveObjects, e); } break; } @@ -362,61 +323,34 @@ namespace kiwi { if (hit_tester.objectTouched() && e.getMouseDownPosition() != e.getPosition()) { - startAction(Action::MoveObject, e); + startAction(Action::MoveObjects, e); } break; } - case Action::ResizeObject: + case Action::ResizeObjects: { juce::Point delta = e.getPosition() - e.getMouseDownPosition(); + auto& patcher_model = m_patcher_view.m_patcher_model; for (auto bounds_it : m_mousedown_bounds) { - juce::Rectangle new_bounds = bounds_it.second; + auto& document = patcher_model.entity().use(); + const auto object_ref = bounds_it.first; - double ratio = 0.; - - if (e.mods.isShiftDown() && new_bounds.getWidth() != 0) - { - ratio = static_cast(new_bounds.getHeight()) / static_cast(new_bounds.getWidth()); - } - - if (m_direction & Direction::Left) + if(auto model = document.get(object_ref)) { - new_bounds.setLeft(std::min(new_bounds.getX() + delta.getX(), new_bounds.getRight())); - } - - if (m_direction & Direction::Up) - { - new_bounds.setTop(std::min(new_bounds.getY() + delta.getY(), new_bounds.getBottom())); - } - - if (m_direction & Direction::Right) - { - new_bounds.setRight(std::max(new_bounds.getRight() + delta.getX(), new_bounds.getX())); - } - - if (m_direction & Direction::Down) - { - new_bounds.setBottom(std::max(new_bounds.getBottom() + delta.getY(), new_bounds.getY())); - } - - if(auto* box = hit_tester.getObject()) - { - int new_width = new_bounds.getWidth(); - int new_height = new_bounds.getHeight(); - box->validateSize(new_width, new_height); - new_bounds.setSize(new_width, new_height); - } - - auto& document = m_patcher_view.m_patcher_model.entity().use(); - if(auto* model = document.get(bounds_it.first)) - { - applyNewBounds(*model, new_bounds, ratio); + if(auto* box = m_patcher_view.getObject(*model)) + { + resizeModelObjectBounds(*model, *box, + bounds_it.second, + delta, e.mods.isShiftDown()); + } } } - model::DocumentManager::commitGesture(m_patcher_view.m_patcher_model, "Resize object"); + std::string commit_msg = "Resize object"; + commit_msg += (m_mousedown_bounds.size() > 1 ? "s" : ""); + model::DocumentManager::commitGesture(patcher_model, commit_msg); break; } @@ -481,16 +415,14 @@ namespace kiwi } break; } - case Action::MoveObject: + case Action::MoveObjects: { model::DocumentManager::endCommitGesture(m_patcher_view.m_patcher_model); - KiwiApp::commandStatusChanged(); - m_patcher_view.m_viewport.updatePatcherArea(true); - m_patcher_view.m_viewport.jumpViewToObject(*hit_tester.getObject()); + KiwiApp::commandStatusChanged(); break; } case Action::ObjectEdition: @@ -499,17 +431,16 @@ namespace kiwi m_patcher_view.editObject(object); break; } - case Action::ResizeObject: + case Action::ResizeObjects: { + model::DocumentManager::endCommitGesture(m_patcher_view.m_patcher_model); + m_direction = Direction::None; m_mousedown_bounds.clear(); - model::DocumentManager::endCommitGesture(m_patcher_view.m_patcher_model); - m_patcher_view.m_viewport.updatePatcherArea(true); - m_patcher_view.m_viewport.jumpViewToObject(*hit_tester.getObject()); - + KiwiApp::commandStatusChanged(); break; } case Action::SwitchLock: @@ -578,7 +509,7 @@ namespace kiwi } else if (hit_tester.getZone() == HitTester::Zone::Border) { - startAction(Action::ResizeObject, e); + startAction(Action::ResizeObjects, e); } } else if(hit_tester.linkTouched()) @@ -669,26 +600,43 @@ namespace kiwi int direction = Direction::None; - model::Object const& object_model = hit_tester.getObject()->getModel(); + int resize_flags = HitTester::Border::None; - if ((border & HitTester::Border::Top) && object_model.hasFlag(model::ObjectClass::Flag::ResizeHeight)) + if(auto* box = hit_tester.getObject()) { - direction |= Direction::Up; + resize_flags = box->getResizingFlags(); } - if ((border & HitTester::Border::Right)) // && object_model.hasFlag(model::ObjectClass::Flag::ResizeWidth)) - { - direction |= Direction::Right; - } + const bool grow_x = ((resize_flags & HitTester::Border::Left) + || resize_flags & HitTester::Border::Right); - if ((border & HitTester::Border::Bottom) && object_model.hasFlag(model::ObjectClass::Flag::ResizeHeight)) + const bool grow_y = ((resize_flags & HitTester::Border::Top) + || resize_flags & HitTester::Border::Bottom); + + if(grow_x) { - direction |= Direction::Down; + if (border & HitTester::Border::Right) + { + direction |= Direction::Right; + } + + if (border & HitTester::Border::Left) + { + direction |= Direction::Left; + } } - if ((border & HitTester::Border::Left) ) // && object_model.hasFlag(model::ObjectClass::Flag::ResizeWidth)) + if(grow_y) { - direction |= Direction::Left; + if (border & HitTester::Border::Top) + { + direction |= Direction::Up; + } + + if ((border & HitTester::Border::Bottom)) + { + direction |= Direction::Down; + } } return direction; @@ -700,6 +648,22 @@ namespace kiwi int direction = getResizeDirection(hit_tester); + int resize_flags = HitTester::Border::None; + + if(auto* box = hit_tester.getObject()) + { + resize_flags = box->getResizingFlags(); + } + + const bool grow_y = ((resize_flags & HitTester::Border::Top) + || resize_flags & HitTester::Border::Bottom); + + if(!grow_y && + (direction == Direction::Left || direction == Direction::Right)) + { + return juce::MouseCursor::LeftRightResizeCursor; + } + switch(direction) { case (Direction::Up) : @@ -789,4 +753,71 @@ namespace kiwi m_patcher_view.setMouseCursor(mc); } + + void MouseHandler::resizeModelObjectBounds(model::Object& model, + ObjectFrame& box, + juce::Rectangle prev_bounds, + juce::Point delta, bool fixed_ratio) + { + juce::Rectangle new_bounds = prev_bounds; + + double ratio = 0.; + const bool stretching_top = m_direction & Direction::Up; + const bool stretching_left = m_direction & Direction::Left; + const bool stretching_bottom = m_direction & Direction::Down; + const bool stretching_right = m_direction & Direction::Right; + + if (fixed_ratio && new_bounds.getWidth() != 0) + { + ratio = (static_cast(new_bounds.getWidth()) + / static_cast(new_bounds.getHeight())); + } + + if (stretching_left) + { + new_bounds.setLeft(std::min(new_bounds.getX() + delta.getX(), + new_bounds.getRight())); + } + + if (stretching_top) + { + new_bounds.setTop(std::min(new_bounds.getY() + delta.getY(), + new_bounds.getBottom())); + } + + if (stretching_right) + { + new_bounds.setRight(std::max(new_bounds.getRight() + delta.getX(), + new_bounds.getX())); + } + + if (stretching_bottom) + { + new_bounds.setBottom(std::max(new_bounds.getBottom() + delta.getY(), + new_bounds.getY())); + } + + const juce::Rectangle limits {}; + + juce::Rectangle target_bounds = new_bounds; + + auto* box_constrainer = box.getBoundsConstrainer(); + + juce::ComponentBoundsConstrainer constrainer; + constrainer.setFixedAspectRatio(box_constrainer->getFixedAspectRatio() == 0 ? ratio : box_constrainer->getFixedAspectRatio()); + + auto box_ratio = box_constrainer->getFixedAspectRatio(); + + // impose ratio if not set + box_constrainer->setFixedAspectRatio(box_ratio == 0 ? ratio : box_ratio); + box_constrainer->checkBounds(target_bounds, prev_bounds, limits, + stretching_top, stretching_left, + stretching_bottom, stretching_right); + // restore ratio + box_constrainer->setFixedAspectRatio(box_ratio); + + model.setPosition(target_bounds.getX(), target_bounds.getY()); + model.setWidth(target_bounds.getWidth()); + model.setHeight(target_bounds.getHeight()); + } } diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.h b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.h index 950bb2c0..636343ea 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.h @@ -50,27 +50,27 @@ namespace kiwi enum class Action { - None = 0, - CopyOnDrag = 1, - Object = 2, - CreateLink = 3, - Lasso = 4, - MoveObject = 5, - PopupMenu = 6, - ObjectEdition = 7, - SwitchSelection = 8, - Selection = 9, - SwitchLock = 10, - ResizeObject = 11 + None = 0, + CopyOnDrag, + Object, + CreateLink, + Lasso, + MoveObjects, + ResizeObjects, + PopupMenu, + ObjectEdition, + SwitchSelection, + Selection, + SwitchLock }; enum Direction : int { - None = 0, - Up = 1 << 0, - Down = 1 << 1, - Left = 1 << 2, - Right = 1 << 3 + None = 0, + Up = 1 << 0, + Down = 1 << 1, + Left = 1 << 2, + Right = 1 << 3 }; public: // methods @@ -113,13 +113,15 @@ namespace kiwi //! @brief Returns the resize direction according to the hit_tester. int getResizeDirection(HitTester const& hit_tester) const; - //! @brief Applies new bounds to a object model. - //! @details If ratio is set, the function will keep this ratio will resizing. - void applyNewBounds(model::Object & object_model, juce::Rectangle new_bounds, double ratio = 0.) const; - //! @brief Returns the right resize mouse cursor. juce::MouseCursor::StandardCursorType getMouseCursorForBorder(HitTester const& hit_tester) const; + //! @brief Resize the model object's bounds + void resizeModelObjectBounds(model::Object& model, + ObjectFrame& box, + juce::Rectangle prev_bounds, + juce::Point delta, bool fixed_ratio); + private: // members PatcherView & m_patcher_view; diff --git a/Modules/KiwiModel/KiwiModel_ObjectClass.h b/Modules/KiwiModel/KiwiModel_ObjectClass.h index 35dffebc..b23c9e1d 100755 --- a/Modules/KiwiModel/KiwiModel_ObjectClass.h +++ b/Modules/KiwiModel/KiwiModel_ObjectClass.h @@ -103,9 +103,7 @@ namespace kiwi { namespace model { //! @brief A list of flags that defines the object's behavior. enum class Flag { - Internal, //! Internal objects are hidden from objects's dionnary. - ResizeWidth, //! Set this flag to resize object horizontally. - ResizeHeight, //! Set this flag to resize object vertically. + Internal, //! Internal objects do not appears in object list. DefinedSize //! If the object has a predefined size. }; diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Bang.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Bang.cpp index ec7bdc9f..dfec102f 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Bang.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Bang.cpp @@ -34,8 +34,6 @@ namespace kiwi { namespace model { std::unique_ptr bang_class(new ObjectClass("bang", &Bang::create)); bang_class->setFlag(ObjectClass::Flag::DefinedSize); - bang_class->setFlag(ObjectClass::Flag::ResizeWidth); - bang_class->setFlag(ObjectClass::Flag::ResizeHeight); flip::Class & bang_model = DataModel::declare().name(bang_class->getModelName().c_str()).inherit(); @@ -57,8 +55,6 @@ namespace kiwi { namespace model { addSignal<>(Signal::TriggerBang, *this); addSignal<>(Signal::FlashBang, *this); - setRatio(1.); - setMinWidth(20.); setWidth(20); pushInlet({PinType::IType::Control}); pushOutlet(PinType::IType::Control); diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp index c09a4050..67c17294 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp @@ -40,7 +40,6 @@ namespace kiwi { namespace model { // Flags comment_class->setFlag(ObjectClass::Flag::DefinedSize); - comment_class->setFlag(ObjectClass::Flag::ResizeWidth); // DataModel flip::Class & comment_model = DataModel::declare() @@ -62,9 +61,7 @@ namespace kiwi { namespace model { { if (args.size() > 0) throw Error("comment too many arguments"); - - setMinWidth(20.); - setMinHeight(20.); + setWidth(120.); setHeight(20.); } diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Message.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Message.cpp index 2e83830f..68b22311 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Message.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Message.cpp @@ -64,8 +64,6 @@ namespace kiwi { namespace model { Object(), m_message_text("") { - setMinWidth(20.); - setMinHeight(20.); setWidth(40); setHeight(20.); pushInlet({PinType::IType::Control}); diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_MeterTilde.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_MeterTilde.cpp index 5baa9368..ddfe029a 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_MeterTilde.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_MeterTilde.cpp @@ -34,8 +34,6 @@ namespace kiwi { namespace model { std::unique_ptr metertilde_class(new ObjectClass("meter~", &MeterTilde::create)); metertilde_class->setFlag(ObjectClass::Flag::DefinedSize); - metertilde_class->setFlag(ObjectClass::Flag::ResizeWidth); - metertilde_class->setFlag(ObjectClass::Flag::ResizeHeight); flip::Class & metertilde_model = DataModel::declare() .name(metertilde_class->getModelName().c_str()) @@ -63,8 +61,6 @@ namespace kiwi { namespace model { throw Error("meter~ too many arguments"); } - setMinWidth(20); - setMinHeight(20); setWidth(100); setHeight(20); pushInlet({PinType::IType::Signal}); diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Number.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Number.cpp index 871bbbfb..74d56f8b 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Number.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Number.cpp @@ -41,7 +41,6 @@ namespace kiwi { namespace model { // flags - number_class->setFlag(ObjectClass::Flag::ResizeWidth); number_class->setFlag(ObjectClass::Flag::DefinedSize); // data model @@ -68,8 +67,6 @@ namespace kiwi { namespace model { addSignal<>(Signal::OutputValue, *this); - setMinWidth(20.); - setMinHeight(20.); setWidth(50.); setHeight(20.); pushInlet({PinType::IType::Control}); diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_NumberTilde.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_NumberTilde.cpp index 1cbf5e4b..1674aebc 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_NumberTilde.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_NumberTilde.cpp @@ -41,7 +41,6 @@ namespace kiwi { namespace model { // flags - numbertilde_class->setFlag(ObjectClass::Flag::ResizeWidth); numbertilde_class->setFlag(ObjectClass::Flag::DefinedSize); // data model @@ -66,8 +65,6 @@ namespace kiwi { namespace model { throw Error("number tilde doesn't take any arguments"); } - setMinWidth(20.); - setMinHeight(20.); setWidth(50.); setHeight(20.); pushInlet({PinType::IType::Signal}); diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Slider.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Slider.cpp index ee86b148..0035be9f 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Slider.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Slider.cpp @@ -42,13 +42,9 @@ namespace kiwi { namespace model { // flags - - slider_class->setFlag(ObjectClass::Flag::ResizeWidth); - slider_class->setFlag(ObjectClass::Flag::ResizeHeight); slider_class->setFlag(ObjectClass::Flag::DefinedSize); // data model - flip::Class & slider_model = DataModel::declare() .name(slider_class->getModelName().c_str()) .inherit(); @@ -71,8 +67,6 @@ namespace kiwi { namespace model { { addSignal<>(Signal::OutputValue, *this); - setMinWidth(20.); - setMinHeight(20.); setWidth(20); setHeight(50.); pushInlet({PinType::IType::Control}); diff --git a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Toggle.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Toggle.cpp index cec2f17d..316c49b8 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Toggle.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Toggle.cpp @@ -34,8 +34,6 @@ namespace kiwi { namespace model { std::unique_ptr toggle_class(new ObjectClass("toggle", &Toggle::create)); toggle_class->setFlag(ObjectClass::Flag::DefinedSize); - toggle_class->setFlag(ObjectClass::Flag::ResizeWidth); - toggle_class->setFlag(ObjectClass::Flag::ResizeHeight); std::unique_ptr param_value(new ParameterClass(tool::Parameter::Type::Int)); @@ -62,8 +60,6 @@ namespace kiwi { namespace model { } addSignal<>(Signal::OutputValue, *this); - setRatio(1.); - setMinWidth(20.); setWidth(20); pushInlet({PinType::IType::Control}); pushOutlet(PinType::IType::Control); From cdf14de2a8da78e9b35972e6dd57bf5f1b6aad39 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 03:55:11 +0200 Subject: [PATCH 26/41] update model::Converter --- .../KiwiModel_Converter.cpp | 175 +++++------------- .../KiwiModel_Converter.h | 29 ++- .../KiwiModel_ConverterBase.h | 64 +++++++ .../KiwiModel_Converter_v1_v2.cpp | 45 +++++ .../KiwiModel_Converter_v2_v3.cpp | 38 ++++ .../KiwiModel_Converter_v3_v4.cpp | 147 +++++++++++++++ 6 files changed, 364 insertions(+), 134 deletions(-) create mode 100755 Modules/KiwiModel/KiwiModel_Converters/KiwiModel_ConverterBase.h create mode 100755 Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v1_v2.cpp create mode 100755 Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v2_v3.cpp create mode 100755 Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v3_v4.cpp diff --git a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp index 1b1c4457..e851572e 100755 --- a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp +++ b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp @@ -24,7 +24,9 @@ #include #include -#include +#include +#include +#include namespace kiwi { namespace model { @@ -32,152 +34,75 @@ namespace kiwi { namespace model { // CONVERTER // // ================================================================================ // - void Converter::convert_v1_v2(flip::BackEndIR & backend) + Converter::Converter() { - flip::BackEndIR::Type& patcher = backend.root; - - // removing patcher name. - patcher.members.remove_if([](std::pair const& member) - { - return member.first == "patcher_name"; - }); - - backend.complete_conversion("v2"); + addConverter(); + addConverter(); + addConverter(); } - void Converter::convert_v3_v4(flip::BackEndIR & backend) + Converter::~Converter() + {} + + Converter& Converter::use() { - flip::BackEndIR::Type& patcher = backend.root; - - // random object conversion: - // - remove seed inlet (was third inlet in < v4) - // - remove range inlet (second) if range argument is defined - - walk(patcher, [](flip::BackEndIR::Type& type) { - - if (type.get_class () != "cicm.kiwi.object.random") - return; // abort - - auto const& text_value = type.member("text").second.value.blob; - const std::string text { text_value.begin(), text_value.end() }; - const auto parsed_text = tool::AtomHelper::parse(text); - - auto& inlets = type.member("inlets").second.array; - - if(inlets.size() == 3) - { - inlets.erase(inlets.begin()); - - const bool has_range_arg = (parsed_text.size() > 1 - && parsed_text[1].isNumber()); - if(has_range_arg) - { - inlets.erase(inlets.begin()); - } - } - }); - - backend.complete_conversion("v4"); + static Converter instance; + return instance; } - void Converter::remove_invalid_links(flip::BackEndIR& backend) + std::string const& Converter::getLatestVersion() { - flip::BackEndIR::Type& patcher = backend.root; + static const std::string latest_version = KIWI_MODEL_VERSION_STRING; + return latest_version; + } + + bool Converter::canConvertToLatestFrom(std::string const& version) + { + auto& converters = use().converters(); - auto& objects = patcher.member("objects").second.array; + if(version == getLatestVersion()) + { + return true; + } - // A link is considered invalid if sender or receiver object does not exist, - // or if it's bound to a pin that does not exist. - auto is_invalid_link = [&objects](flip::BackEndIR::Type& type) { - - auto const& sender_ref = type.member("sender_obj").second.value.ref; - auto const& receiver_ref = type.member("receiver_obj").second.value.ref; - auto const& sender_outlet = type.member("outlet_index").second.value.int_num; - auto const& receiver_inlet = type.member("inlet_index").second.value.int_num; - - bool sender_found = false; - bool receiver_found = false; - - // check sender validity - for(auto& object : objects) + for(auto& conversion : converters) + { + if(conversion->v_from == version) { - auto& type = object.second; - - if(!sender_found && type.ref == sender_ref) - { - sender_found = true; - - const auto outlet_count = type.member("outlets").second.array.size(); - if(sender_outlet >= outlet_count) - { - // bad outlet - return true; - } - } - - if(!receiver_found && type.ref == receiver_ref) - { - receiver_found = true; - - const auto inlet_count = type.member("inlets").second.array.size(); - if(receiver_inlet >= inlet_count) - { - // bad inlet - return true; - } - } - - if(sender_found && receiver_found) + if(canConvertToLatestFrom(conversion->v_to)) { - break; + return true; } } - - return (!sender_found || !receiver_found); - }; - - auto& links = patcher.member("links").second.array; - - auto iter = links.begin(); - const auto end_iter = links.cend(); - for(; iter != end_iter;) - { - if (is_invalid_link(iter->second)) - { - links.erase(iter++); - } - else - { - ++iter; - } } + + return false; + } + + template + bool Converter::addConverter() + { + m_converters.emplace_back(std::make_unique()); + return true; + } + + auto Converter::converters() -> converters_t& + { + return m_converters; } bool Converter::process(flip::BackEndIR & backend) { - std::string current_version(KIWI_MODEL_VERSION_STRING); - - if (current_version.compare(backend.version) >= 0) + auto& conversions = use().converters(); + for(auto& conversion : conversions) { - if (backend.version == "v1") - { - convert_v1_v2(backend); - } - - // no change from v2 to v3 ! - - if (backend.version == "v2") - { - backend.complete_conversion("v3"); - } - - if (backend.version == "v3") + conversion->process(backend); + if(backend.version == getLatestVersion()) { - convert_v3_v4(backend); - remove_invalid_links(backend); + return true; } } - return (backend.version == current_version); + return false; } }} diff --git a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.h b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.h index e39e491e..bd4e3a96 100755 --- a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.h +++ b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.h @@ -21,12 +21,12 @@ #pragma once -#include +#include namespace kiwi { namespace model { // ================================================================================ // - // CONVERTER // + // KIWI CONVERTER // // ================================================================================ // //! @brief Converts a document's backend representation to meet current version representation. @@ -34,21 +34,32 @@ namespace kiwi { namespace model { { public: // methods + //! @brief Returns the current version of the converter. + static std::string const& getLatestVersion(); + + //! @brief Returns true if a given version can be converted from. + static bool canConvertToLatestFrom(std::string const& version); + //! @brief Tries converting current data model version. //! @details Returns true if the conversion was successful, false otherwise. Call this function //! after reading from data provider. - static bool process(flip::BackEndIR & backend); + static bool process(flip::BackEndIR& backend); private: // methods - //! @brief Converts a v1 data model to a v2 data model. - static void convert_v1_v2(flip::BackEndIR& backend); + Converter(); + ~Converter(); + static Converter& use(); + + using converters_t = std::vector>; + converters_t& converters(); + + template + bool addConverter(); - //! @brief Converts a v3 data model to a v4 data model. - static void convert_v3_v4(flip::BackEndIR& backend); + private: // variables - //! @brief Removes invalid links - static void remove_invalid_links(flip::BackEndIR& backend); + converters_t m_converters; }; }} diff --git a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_ConverterBase.h b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_ConverterBase.h new file mode 100755 index 00000000..e96fffd2 --- /dev/null +++ b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_ConverterBase.h @@ -0,0 +1,64 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#pragma once + +#include + +namespace kiwi { namespace model { + + // ================================================================================ // + // CONVERTER // + // ================================================================================ // + + struct ConverterBase + { + ConverterBase(std::string const& from_version, + std::string const& to_version) + : v_from(from_version) + , v_to(to_version) + {} + + virtual ~ConverterBase() = default; + + bool process(flip::BackEndIR& backend) + { + if(backend.version == v_from) + { + if(operator()(backend)) + { + backend.complete_conversion(v_to); + return true; + } + } + + return false; + } + + const std::string v_from; + const std::string v_to; + + protected: + + virtual bool operator () (flip::BackEndIR& backend) const = 0; + }; + +}} diff --git a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v1_v2.cpp b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v1_v2.cpp new file mode 100755 index 00000000..793603de --- /dev/null +++ b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v1_v2.cpp @@ -0,0 +1,45 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#include +#include + +namespace kiwi { namespace model { + + //! @brief Removes "patcher_name" member from the patcher + struct Converter_v1_v2 : public ConverterBase + { + Converter_v1_v2() : ConverterBase("v1", "v2") {} + + bool operator () (flip::BackEndIR& backend) const override + { + flip::BackEndIR::Type& patcher = backend.root; + + // removing patcher name. + patcher.members.remove_if([](std::pair const& member) { + return member.first == "patcher_name"; + }); + + return true; + } + }; +}} diff --git a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v2_v3.cpp b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v2_v3.cpp new file mode 100755 index 00000000..f3c90f0f --- /dev/null +++ b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v2_v3.cpp @@ -0,0 +1,38 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#include +#include + +namespace kiwi { namespace model { + + //! @brief Nothing to do from v2 to v3 + struct Converter_v2_v3 : public ConverterBase + { + Converter_v2_v3() : ConverterBase("v2", "v3") {} + + bool operator () (flip::BackEndIR& backend) const override + { + // nothing to do from v2 to v3 + return true; + } + }; +}} diff --git a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v3_v4.cpp b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v3_v4.cpp new file mode 100755 index 00000000..1552e5e6 --- /dev/null +++ b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v3_v4.cpp @@ -0,0 +1,147 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#include +#include + +#include + +namespace kiwi { namespace model { + + //! @brief v3 to v4 converter + //! @details random object conversion: + // - remove seed inlet (was third inlet in < v4) + // - remove range inlet (second) if range argument is defined + // - diconnect invalids links + + struct Converter_v3_v4 : public ConverterBase + { + Converter_v3_v4() : ConverterBase("v3", "v4") {} + + bool operator () (flip::BackEndIR& backend) const override + { + flip::BackEndIR::Type& patcher = backend.root; + + walk(patcher, [](flip::BackEndIR::Type& type) { + + if (type.get_class () != "cicm.kiwi.object.random") + return; // abort + + auto const& text_value = type.member("text").second.value.blob; + const std::string text { text_value.begin(), text_value.end() }; + const auto parsed_text = tool::AtomHelper::parse(text); + + auto& inlets = type.member("inlets").second.array; + + if(inlets.size() == 3) + { + inlets.erase(inlets.begin()); + + const bool has_range_arg = (parsed_text.size() > 1 + && parsed_text[1].isNumber()); + if(has_range_arg) + { + inlets.erase(inlets.begin()); + } + } + }); + + remove_invalid_links(backend); + return true; + } + + bool remove_invalid_links(flip::BackEndIR& backend) const + { + flip::BackEndIR::Type& patcher = backend.root; + + auto& objects = patcher.member("objects").second.array; + + // A link is considered invalid if sender or receiver object does not exist, + // or if it's bound to a pin that does not exist. + auto is_invalid_link = [&objects](flip::BackEndIR::Type& type) { + + auto const& sender_ref = type.member("sender_obj").second.value.ref; + auto const& receiver_ref = type.member("receiver_obj").second.value.ref; + auto const& sender_outlet = type.member("outlet_index").second.value.int_num; + auto const& receiver_inlet = type.member("inlet_index").second.value.int_num; + + bool sender_found = false; + bool receiver_found = false; + + // check sender validity + for(auto& object : objects) + { + auto& type = object.second; + + if(!sender_found && type.ref == sender_ref) + { + sender_found = true; + + const auto outlet_count = type.member("outlets").second.array.size(); + if(sender_outlet >= outlet_count) + { + // bad outlet + return false; + } + } + + if(!receiver_found && type.ref == receiver_ref) + { + receiver_found = true; + + const auto inlet_count = type.member("inlets").second.array.size(); + if(receiver_inlet >= inlet_count) + { + // bad inlet + return false; + } + } + + if(sender_found && receiver_found) + { + break; + } + } + + return (!sender_found || !receiver_found); + }; + + auto& links = patcher.member("links").second.array; + + auto iter = links.begin(); + const auto end_iter = links.cend(); + for(; iter != end_iter;) + { + if (is_invalid_link(iter->second)) + { + links.erase(iter++); + } + else + { + ++iter; + } + } + + return true; + } + }; + +}} From 34353a7a50594ab405fb71c3fcbe584c7e35538e Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 04:14:11 +0200 Subject: [PATCH 27/41] Add conversion error messages to console --- .../KiwiApp_Application/KiwiApp_Instance.cpp | 84 ++++++----- .../KiwiApp_PatcherManager.cpp | 142 +++++++++++------- .../KiwiApp_Patcher/KiwiApp_PatcherManager.h | 31 ++-- 3 files changed, 153 insertions(+), 104 deletions(-) diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp b/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp index 2112515a..5d417e61 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp @@ -106,11 +106,11 @@ namespace kiwi } void Instance::login() - { - m_browser.setDriveName(KiwiApp::getCurrentUser().getName()); + { + m_browser.setDriveName(KiwiApp::getCurrentUser().getName()); - m_windows[std::size_t(WindowId::DocumentBrowser)]->getContentComponent()->setEnabled(true); - } + m_windows[std::size_t(WindowId::DocumentBrowser)]->getContentComponent()->setEnabled(true); + } void Instance::logout() { @@ -171,47 +171,57 @@ namespace kiwi } bool Instance::openFile(juce::File const& file) - { - bool open_succeeded = false; - - if(file.hasFileExtension("kiwi")) + { + if(!file.hasFileExtension("kiwi")) + { + KiwiApp::error("can't open file (bad file extension)"); + return false; + } + { auto manager_it = getPatcherManagerForFile(file); - if (manager_it == m_patcher_managers.end()) + // If a patcher already manages this file, just brings it to front + if (manager_it != m_patcher_managers.end()) { - std::string patcher_name = file.getFileNameWithoutExtension().toStdString(); - - std::unique_ptr patcher_manager (new PatcherManager(*this, patcher_name)); - - if (patcher_manager->loadFromFile(file)) - { - auto manager_it = m_patcher_managers.emplace(m_patcher_managers.end(), - std::move(patcher_manager)); - - open_succeeded = true; - - if((*manager_it)->getNumberOfView() == 0) - { - (*manager_it)->newView(); - } - } - else - { - KiwiApp::error("Can't open document. Version is not up to date. Please download latest Kiwi version."); - } + (*manager_it)->bringsFirstViewToFront(); + return true; } - else + } + + // there is no manager for this file so lets create one + + std::unique_ptr temp_manager = nullptr; + + try + { + temp_manager = PatcherManager::createFromFile(*this, file); + } + catch (std::runtime_error const& err) + { + const std::string error = err.what(); + const std::string filename = file.getFileName().toStdString(); + KiwiApp::error("Can't open document \"" + filename + "\""); + KiwiApp::error("error: " + error); + KiwiApp::error("Please download latest Kiwi version."); + return false; + } + + if(temp_manager) + { + auto manager_it = m_patcher_managers.emplace(m_patcher_managers.end(), + std::move(temp_manager)); + auto& manager = *(manager_it->get()); + + if(manager.getNumberOfView() == 0) { - (*manager_it)->bringsFirstViewToFront(); + manager.newView(); } - } - else - { - KiwiApp::error("can't open file (bad file extension)"); - } + + return true; + } - return open_succeeded; + return false; } void Instance::askUserToOpenPatcherDocument() diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp index 9b571f98..0c60e7b1 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp @@ -51,23 +51,33 @@ namespace kiwi // PATCHER MANAGER // // ================================================================================ // - PatcherManager::PatcherManager(Instance& instance, std::string const& name) : - m_name(name), - m_instance(instance), - m_validator(), - m_document(model::DataModel::use(), *this, m_validator, - m_instance.getUserId(), 'cicm', 'kpat'), - m_file(), - m_socket(m_document), - m_need_saving_flag(false), - m_session(nullptr) - { - ; - } + PatcherManager::PatcherManager(Instance& instance, std::string const& name) + : m_name(name) + , m_instance(instance) + , m_validator() + , m_document(model::DataModel::use(), *this, m_validator, + m_instance.getUserId(), 'cicm', 'kpat') + , m_socket(m_document) + , m_session(nullptr) + {} PatcherManager::~PatcherManager() { disconnect(); + } + + std::unique_ptr PatcherManager::createFromFile(Instance& instance, + juce::File const& file) + { + auto filename = file.getFileNameWithoutExtension().toStdString(); + auto manager = std::make_unique(instance, filename); + + if(manager && manager->loadFromFile(file)) + { + return manager; + } + + return nullptr; } void PatcherManager::addListener(Listener& listener) @@ -216,70 +226,88 @@ namespace kiwi return m_socket.isConnected() && patcher_loaded; } - bool PatcherManager::readDocument() - { - bool loading_succeeded = false; - - flip::DataProviderFile provider(m_file.getFullPathName().toStdString().c_str()); - flip::BackEndIR back_end; - - back_end.register_backend(); + bool PatcherManager::readBackEndBinary(flip::DataProviderBase& data_provider) + { + flip::BackEndIR binary_backend; + binary_backend.register_backend(); + //binary_backend.register_backend(); - if (back_end.read(provider)) + if (binary_backend.read(data_provider)) { - if (model::Converter::process(back_end)) + const auto current_version = model::Converter::getLatestVersion(); + const auto backend_version = binary_backend.version; + + if(!model::Converter::canConvertToLatestFrom(backend_version)) + { + throw std::runtime_error("bad version: no conversion available from " + + backend_version + " to " + current_version); + return false; + } + + if (model::Converter::process(binary_backend)) { try { - m_document.read(back_end); + m_document.read(binary_backend); } catch (...) { + std::runtime_error("document failed to read"); return false; } - - loading_succeeded = true; } + else + { + throw std::runtime_error("failed to convert document from " + + backend_version + " to " + current_version); + } + } + else + { + throw std::runtime_error("backend failed to read"); } - return loading_succeeded; + return true; } bool PatcherManager::loadFromFile(juce::File const& file) { - bool success = false; - - if (file.hasFileExtension("kiwi")) - { - m_file = file; - - if (readDocument()) + if(!file.hasFileExtension("kiwi")) + { + throw std::runtime_error("bad extension"); + return false; + } + + std::string filepath = file.getFullPathName().toStdString(); + + // assuming that the file is in in a binary format + + flip::DataProviderFile provider(filepath.c_str()); + + if(readBackEndBinary(provider)) + { + model::Patcher& patcher = getPatcher(); + patcher.useSelfUser(); + + try { - model::Patcher& patcher = getPatcher(); - - patcher.useSelfUser(); - - try - { - model::DocumentManager::commit(patcher); - } - catch (...) - { - return false; - } - - setName(file.getFileNameWithoutExtension().toStdString()); - - setNeedSaving(false); - - updateTitleBars(); - - patcher.entity().use().sendLoadbang(); - success = true; - } + model::DocumentManager::commit(patcher); + } + catch (...) + { + throw std::runtime_error("document failed to load"); + return false; + } + + m_file = file; + setName(file.getFileNameWithoutExtension().toStdString()); + setNeedSaving(false); + updateTitleBars(); + + patcher.entity().use().sendLoadbang(); } - return success; + return true; } model::Patcher& PatcherManager::getPatcher() diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.h b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.h index a2ce396c..d45f4292 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.h @@ -47,8 +47,9 @@ namespace kiwi //! @brief The main DocumentObserver. //! @details The Instance dispatch changes to all other DocumentObserver objects - class PatcherManager : public flip::DocumentObserver, - public DocumentBrowser::Drive::Listener + class PatcherManager + : public flip::DocumentObserver + , public DocumentBrowser::Drive::Listener { public: // nested classes @@ -60,7 +61,13 @@ namespace kiwi PatcherManager(Instance& instance, std::string const& name); //! @brief Destructor. - ~PatcherManager(); + ~PatcherManager(); + + //! @brief Try to create a patcher manager from a file + //! @details This method throws a std::runtime exception if error occurs during read. + //! @return A PatcherManager if succeed, nullptr otherwise. + static std::unique_ptr createFromFile(Instance& instance, + juce::File const& file); //! @brief Try to connect this patcher to a remote server. bool connect(std::string const& host, uint16_t port, DocumentBrowser::Drive::DocumentSession& session); @@ -71,9 +78,6 @@ namespace kiwi //! @brief Pull changes from server if it is remote. void pull(); - //! @brief Load patcher datas from file. - bool loadFromFile(juce::File const& file); - //! @brief Save the document. //! @details Returns true if saving document succeeded false otherwise. bool saveDocument(); @@ -152,8 +156,15 @@ namespace kiwi //! @internal Write data into file. void writeDocument(); - //! @internal Reads data from file. - bool readDocument(); + //! @internal Reads from binary data file. + bool readBackEndBinary(flip::DataProviderBase& data_provider); + + //! @brief Load patcher datas from file. + //! @details Loading document from file could fail if the file can not be read. + //! This could happen if the file has not a valid extension, + //! or if the document version is incompatible. + //! @exception Throws a std::runtime exception if file loading fail. + bool loadFromFile(juce::File const& file); //! @internal flip::DocumentObserver::document_changed void document_changed(model::Patcher& patcher) override final; @@ -202,8 +213,8 @@ namespace kiwi flip::Document m_document; juce::File m_file; CarrierSocket m_socket; - bool m_need_saving_flag; - DocumentBrowser::Drive::DocumentSession* m_session; + bool m_need_saving_flag = false; + DocumentBrowser::Drive::DocumentSession* m_session = nullptr; flip::SignalConnection m_user_connected_signal_cnx; flip::SignalConnection m_user_disconnected_signal_cnx; From 126909930f80a8ce0cae9b570427dd1f12994270 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 13:05:22 +0200 Subject: [PATCH 28/41] removes "ratio", "min_width", "min_height" members from the model::Object --- Modules/KiwiModel/KiwiModel_Object.cpp | 99 +++----------------------- Modules/KiwiModel/KiwiModel_Object.h | 32 +-------- 2 files changed, 10 insertions(+), 121 deletions(-) diff --git a/Modules/KiwiModel/KiwiModel_Object.cpp b/Modules/KiwiModel/KiwiModel_Object.cpp index 3c33ce7b..0a903d87 100755 --- a/Modules/KiwiModel/KiwiModel_Object.cpp +++ b/Modules/KiwiModel/KiwiModel_Object.cpp @@ -146,10 +146,7 @@ namespace kiwi .member("pos_x") .member("pos_y") .member("width") - .member("height") - .member("min_width") - .member("min_height") - .member("ratio"); + .member("height"); } // ================================================================================ // @@ -157,27 +154,12 @@ namespace kiwi // ================================================================================ // Object::Object(flip::Default&) - { - } + {} - Object::Object() : - m_text(), - m_inlets(), - m_outlets(), - m_position_x(0.), - m_position_y(0.), - m_width(60.), - m_height(20.), - m_min_width(0.), - m_min_height(0.), - m_ratio(0.), - m_attributes(), - m_parameters(), - m_args(nullptr), - m_signals(), - m_listeners() - { - } + Object::Object() + : m_width(60.) + , m_height(20.) + {} void Object::writeAttribute(std::string const& name, tool::Parameter const& parameter) { @@ -389,79 +371,14 @@ namespace kiwi return !removed() ? m_position_y.value() : m_position_y.before(); } - void Object::setRatio(double ratio) - { - if (ratio > 0.) - { - m_ratio = ratio; - m_height = m_width * m_ratio; - m_min_height = m_min_width * m_ratio; - } - } - - double Object::getRatio() const - { - return m_ratio; - } - - void Object::setMinWidth(double min_width) - { - if (min_width >= 0.) - { - m_min_width = min_width; - - if (m_ratio > 0.) - { - m_min_height = m_min_width * m_ratio; - } - - setWidth(getWidth()); - } - } - - void Object::setMinHeight(double min_height) - { - if (min_height >= 0.) - { - m_min_height = min_height; - - if (m_ratio > 0.) - { - m_min_width = m_min_height / m_ratio; - } - - setHeight(getHeight()); - } - } - void Object::setWidth(double new_width) { - m_width = std::max(m_min_width.value(), new_width); - - if (m_ratio > 0.) - { - m_height = m_ratio * m_width; - } + m_width = std::max(0., new_width); } void Object::setHeight(double new_height) { - m_height = std::max(m_min_height.value(), new_height); - - if (m_ratio > 0.) - { - m_width = m_height / m_ratio; - } - } - - double Object::getMinWidth() const noexcept - { - return m_min_width.value(); - } - - double Object::getMinHeight() const noexcept - { - return m_min_height.value(); + m_height = std::max(0., new_height); } double Object::getWidth() const noexcept diff --git a/Modules/KiwiModel/KiwiModel_Object.h b/Modules/KiwiModel/KiwiModel_Object.h index 24af8298..9e4949a5 100755 --- a/Modules/KiwiModel/KiwiModel_Object.h +++ b/Modules/KiwiModel/KiwiModel_Object.h @@ -49,7 +49,7 @@ #include namespace kiwi -{ +{ namespace model { class Factory; @@ -289,17 +289,10 @@ namespace kiwi double getY() const noexcept; //! @brief Sets the width of the object. - //! @details Width will not be lower than minimal width. - //! If ratio was previously set proportions will be kept intact by changing height. void setWidth(double new_width); //! @brief Sets the height of the object. - //! @details Height will not be lower than minimal height. - //! If ratio was previously set proportions will be kept intact by changing width. void setHeight(double new_height); - - //! @brief Returns the aspect ratio. - double getRatio() const; //! @brief Returns the object's width. double getWidth() const noexcept; @@ -307,12 +300,6 @@ namespace kiwi //! @brief Returns the object's height. double getHeight() const noexcept; - //! @brief Returns the minimal width for this object. - double getMinWidth() const noexcept; - - //! @brief Returns the minimal height for this object; - double getMinHeight() const noexcept; - //! @brief Returns inlet or outlet description. virtual std::string getIODescription(bool is_inlet, size_t index) const; @@ -349,18 +336,6 @@ namespace kiwi //! @brief Adds an outlet at end of current outlet list. void pushOutlet(PinType type); - //! @brief Sets the ratio height/width. - //! @details If width was previously set. Height will adapt to ratio. - void setRatio(double ratio); - - //! @brief Sets the minimal width that the object can have. - //! @details Will recompute height and width if needed. - void setMinWidth(double min_width); - - //! @brief Sets the minimal height that the object can have. - //! @details Will recompute height and width if needed. - void setMinHeight(double min_height); - public: // internal methods //! @internal flip Default constructor @@ -378,12 +353,9 @@ namespace kiwi flip::Float m_position_y; flip::Float m_width; flip::Float m_height; - flip::Float m_min_width; - flip::Float m_min_height; - flip::Float m_ratio; mutable std::map m_attributes; mutable std::map m_parameters; - mutable std::unique_ptr> m_args; + mutable std::unique_ptr> m_args = nullptr; std::map> m_signals; mutable tool::Listeners m_listeners; From 164239fda3b9603fc71878f665ba1bc59f77073e Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 13:05:57 +0200 Subject: [PATCH 29/41] bump model to v4.0.1 --- Modules/KiwiModel/KiwiModel_Def.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/KiwiModel/KiwiModel_Def.h b/Modules/KiwiModel/KiwiModel_Def.h index edea2518..705830c9 100644 --- a/Modules/KiwiModel/KiwiModel_Def.h +++ b/Modules/KiwiModel/KiwiModel_Def.h @@ -21,4 +21,4 @@ #pragma once -#define KIWI_MODEL_VERSION_STRING "v4" +#define KIWI_MODEL_VERSION_STRING "v4.0.1" From 3ae393fbc887e9497675885c67f365e74dab45d2 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 13:06:46 +0200 Subject: [PATCH 30/41] Add v4 to v4.0.1 model converter --- .../KiwiModel_Converter.cpp | 2 + .../KiwiModel_Converter_v4_v401.cpp | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100755 Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v4_v401.cpp diff --git a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp index e851572e..95d4b06d 100755 --- a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp +++ b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace kiwi { namespace model { @@ -39,6 +40,7 @@ namespace kiwi { namespace model { addConverter(); addConverter(); addConverter(); + addConverter(); } Converter::~Converter() diff --git a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v4_v401.cpp b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v4_v401.cpp new file mode 100755 index 00000000..2c43b7c9 --- /dev/null +++ b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter_v4_v401.cpp @@ -0,0 +1,48 @@ +/* + ============================================================================== + + This file is part of the KIWI library. + - Copyright (c) 2014-2016, Pierre Guillot & Eliott Paris. + - Copyright (c) 2016-2017, CICM, ANR MUSICOLL, Eliott Paris, Pierre Guillot, Jean Millot. + + Permission is granted to use this software under the terms of the GPL v3 + (or any later version). Details can be found at: www.gnu.org/licenses + + KIWI 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. + + ------------------------------------------------------------------------------ + + Contact : cicm.mshparisnord@gmail.com + + ============================================================================== + */ + +#include +#include + +namespace kiwi { namespace model { + + //! @brief removes "min_width", "min_height" and "ratio" members from model::Object + struct Converter_v4_v401 : public ConverterBase + { + Converter_v4_v401() : ConverterBase("v4", "v4.0.1") {} + + bool operator () (flip::BackEndIR& backend) const override + { + flip::BackEndIR::Type& patcher = backend.root; + + auto& objects = patcher.member("objects").second.array; + + for(auto& object : objects) + { + object.second.object_remove_member("min_width"); + object.second.object_remove_member("min_height"); + object.second.object_remove_member("ratio"); + } + + return true; + } + }; +}} From 38b375a87df58db5138ecc2c6e9aed0debb6b62a Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 13:42:44 +0200 Subject: [PATCH 31/41] handle 404 errors in download and duplicate api requests --- Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp index 4fe07c87..bf9745ac 100644 --- a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp +++ b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp @@ -479,6 +479,10 @@ namespace kiwi { DocumentBrowser::handleDeniedRequest(); } + else if (res.result() == beast::http::status::not_found) + { + KiwiApp::error("error: document not found"); + } else if(res.error) { KiwiApp::error(res.error.message()); @@ -543,6 +547,10 @@ namespace kiwi { DocumentBrowser::handleDeniedRequest(); } + else if (res.result() == beast::http::status::not_found) + { + KiwiApp::error("error: document not found"); + } else if(res.error) { KiwiApp::error(res.error.message()); From 64e88048d00463a6f42f9121eb5bfcab83247a13 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 15:25:13 +0200 Subject: [PATCH 32/41] add a more descriptive commit message when an object is created and (re)enable linked_newbox feature --- .../KiwiApp_Patcher/KiwiApp_PatcherView.cpp | 90 +++++++++---------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp index 95863bef..881a09c2 100755 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp @@ -1576,52 +1576,50 @@ namespace kiwi void PatcherView::createObjectModel(std::string const& text, bool give_focus) { - if(! model::DocumentManager::isInCommitGesture(m_patcher_model)) - { - bool linked_newbox = m_local_objects_selection.size() == 1; - - std::unique_ptr new_object = model::Factory::create(tool::AtomHelper::parse(text)); - - juce::Point pos = getMouseXYRelative() - getOriginPosition(); - - auto& doc = m_patcher_model.entity().use(); - - if(linked_newbox) - { - model::Object* obj = doc.get(*m_local_objects_selection.begin()); - - if(obj) - { - pos.setX(obj->getX()); - pos.setY(obj->getY() + obj->getHeight() + m_grid_size); - - if(obj->getNumberOfInlets() >= 1) - { - m_patcher_model.addLink(*obj, 0, *new_object, 0); - } - } - } - - new_object->setPosition(pos.x, pos.y); - - m_view_model.unselectAll(); - - m_view_model.selectObject(m_patcher_model.addObject(std::move(new_object))); - - model::DocumentManager::commit(m_patcher_model, "Insert New Empty Box"); - - if(give_focus && m_local_objects_selection.size() == 1) - { - model::Object* object_m = doc.get(*m_local_objects_selection.begin()); - if(object_m) - { - const auto it = findObject(*object_m); - if(it != m_objects.cend()) - { - editObject(**it); - } - } - } + if(model::DocumentManager::isInCommitGesture(m_patcher_model)) + return; + + std::unique_ptr new_object = model::Factory::create(tool::AtomHelper::parse(text)); + + juce::Point pos = getMouseXYRelative() - getOriginPosition(); + + auto& doc = m_patcher_model.entity().use(); + + new_object->setPosition(pos.x, pos.y); + auto& obj = m_patcher_model.addObject(std::move(new_object)); + + const bool linked_newbox = m_local_objects_selection.size() == 1; + if(linked_newbox) + { + if(auto* selobj = doc.get(*m_local_objects_selection.begin())) + { + if(selobj->getNumberOfOutlets() >= 1) + { + obj.setPosition(selobj->getX(), + selobj->getY() + selobj->getHeight() + m_grid_size); + + m_patcher_model.addLink(*selobj, 0, obj, 0); + } + } + } + + m_view_model.unselectAll(); + m_view_model.selectObject(obj); + + std::string commit_message = ("Insert \"" + obj.getName() + "\""); + model::DocumentManager::commit(m_patcher_model, commit_message); + + if(give_focus && m_local_objects_selection.size() == 1) + { + model::Object* object_m = doc.get(*m_local_objects_selection.begin()); + if(object_m) + { + const auto it = findObject(*object_m); + if(it != m_objects.cend()) + { + editObject(**it); + } + } } } From b1867cc50aeb569f90fc481ca0cc8b36b66fc06a Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 15:26:23 +0200 Subject: [PATCH 33/41] fix transaction stack crashs by implementing gestures as Sessions in DocumentManager --- .../KiwiModel/KiwiModel_DocumentManager.cpp | 150 +++++++++++++----- Modules/KiwiModel/KiwiModel_DocumentManager.h | 56 ++++++- 2 files changed, 158 insertions(+), 48 deletions(-) diff --git a/Modules/KiwiModel/KiwiModel_DocumentManager.cpp b/Modules/KiwiModel/KiwiModel_DocumentManager.cpp index 5395395a..573beab6 100755 --- a/Modules/KiwiModel/KiwiModel_DocumentManager.cpp +++ b/Modules/KiwiModel/KiwiModel_DocumentManager.cpp @@ -28,18 +28,13 @@ namespace kiwi { namespace model { // DOCUMENT MANAGER // // ================================================================================ // - DocumentManager::DocumentManager(flip::DocumentBase& document) : - m_document(document), - m_history(document), - m_gesture_flag(false), - m_gesture_cnt(0) - { - ; - } + DocumentManager::DocumentManager(flip::DocumentBase& document) + : m_document(document) + , m_history(m_document) + {} DocumentManager::~DocumentManager() - { - } + {} void DocumentManager::commit(flip::Type& type, std::string action) { @@ -65,16 +60,16 @@ namespace kiwi { namespace model { patcher.entity().use().commitGesture(action); } - void DocumentManager::endCommitGesture(flip::Type & type) + void DocumentManager::endCommitGesture(flip::Type& type) { auto& patcher = type.ancestor(); patcher.entity().use().endCommitGesture(); } - bool DocumentManager::isInCommitGesture(flip::Type & type) + bool DocumentManager::isInCommitGesture(flip::Type& type) { auto & patcher = type.ancestor(); - return patcher.entity().use().m_gesture_flag; + return(patcher.entity().use().m_session != nullptr); } bool DocumentManager::canUndo() @@ -119,8 +114,6 @@ namespace kiwi { namespace model { void DocumentManager::commit(std::string action) { - assert(!m_gesture_flag); - if(!action.empty()) { m_document.set_label(action); @@ -145,49 +138,124 @@ namespace kiwi { namespace model { m_document.push(); } - void DocumentManager::startCommitGesture() { - assert(!m_gesture_flag); - m_gesture_flag = true; + if(m_session) + { + m_session->revert(); + } + else + { + m_session.reset(new Session(m_document)); + } - m_gesture_cnt = 0; + m_session->start(); } void DocumentManager::commitGesture(std::string action) { - assert(m_gesture_flag); - assert(!action.empty()); + assert(m_session && "call startCommitGesture before"); - m_document.set_label(action); - m_document.commit(); + m_session->commit(action); + } + + void DocumentManager::endCommitGesture() + { + assert(m_session && "call startCommitGesture before"); - m_document.set_label(action); - auto tx = m_document.squash(); + m_session->end(&m_history); + m_document.push(); + m_session.reset(); + } + + // ================================================================================ // + // SESSION // + // ================================================================================ // + + DocumentManager::Session::Session(flip::DocumentBase& document) + : m_document(document) + , m_history(document) + {} + + DocumentManager::Session::~Session() + {} + + void DocumentManager::Session::start() + { + if(m_tx.empty()) + return; - if(!tx.empty()) + m_document.execute_backward(m_tx); + } + + void DocumentManager::Session::commit() + { + bool first_flag = m_tx.empty(); + auto tx = m_document.commit(); + + m_document.execute_backward(tx); + m_document.execute_backward(m_tx); + + flip::Transaction tx_abs; + m_document.root().make(tx_abs); + tx_abs.invert_direction(); + + m_tx = tx_abs; + + if(tx.has_metadata(flip::Transaction::key_label)) + { + m_tx.impl_use_metadata_map()[flip::Transaction::key_label] = tx.label(); + } + + if(first_flag) { - if(m_gesture_cnt == 0) - { - m_history.add_undo_step(tx); - } - else - { - *m_history.last_undo() = tx; - } - - ++m_gesture_cnt; + m_history.add_undo_step(m_tx); } + else if(m_tx.empty()) + { + m_history.erase(m_history.last_undo()); + } + else + { + *m_history.last_undo() = m_tx; + } + + m_document.revert(); } - void DocumentManager::endCommitGesture() + void DocumentManager::Session::commit(std::string label) { - assert(m_gesture_flag); - m_gesture_flag = false; + if(!label.empty()) + { + m_document.set_label(label); + } - if(m_gesture_cnt > 0) + commit(); + } + + void DocumentManager::Session::end(flip::History* master_history) + { + if(m_tx.empty()) return; + + if(master_history != nullptr) { - m_document.push(); + // copy session transaction into the master history. + master_history->add_undo_step(m_tx); } + + m_tx.clear(); + m_history.clear(); + } + + void DocumentManager::Session::revert() + { + if(m_tx.empty()) return; + + m_document.execute_backward(m_tx); + m_tx.clear(); + + m_document.commit(); + + m_history.erase(m_history.last_undo()); } }} diff --git a/Modules/KiwiModel/KiwiModel_DocumentManager.h b/Modules/KiwiModel/KiwiModel_DocumentManager.h index a135c95f..01e35004 100755 --- a/Modules/KiwiModel/KiwiModel_DocumentManager.h +++ b/Modules/KiwiModel/KiwiModel_DocumentManager.h @@ -35,17 +35,21 @@ namespace kiwi { namespace model { public: //! @brief Constructor. - DocumentManager(flip::DocumentBase & document); + DocumentManager(flip::DocumentBase& document); //! @brief Destructor. ~DocumentManager(); //! @brief Commit and push. //! @see startCommitGesture, endCommitGesture. - static void commit(flip::Type& type, std::string action = std::string()); + static void commit(flip::Type& type, + std::string action = std::string()); //! @brief Connect the DocumentManager to a remote server - static void connect(flip::Type& type, const std::string host, uint16_t port, uint64_t session_id); + static void connect(flip::Type& type, + const std::string host, + uint16_t port, + uint64_t session_id); //! @brief Pull changes from remote server static void pull(flip::Type& type); @@ -95,7 +99,7 @@ namespace kiwi { namespace model { private: //! @brief Commmits and pushes a transaction - void commit(std::string action); + void commit(std::string label); //! @brief Pulls transactions stacked by a socket's process void pull(); @@ -107,17 +111,18 @@ namespace kiwi { namespace model { void startCommitGesture(); //! @brief Commit a gesture. - void commitGesture(std::string action); + void commitGesture(std::string label); //! @brief Ends a commit gesture. void endCommitGesture(); + class Session; + private: flip::DocumentBase& m_document; flip::History m_history; - bool m_gesture_flag; - size_t m_gesture_cnt; + std::unique_ptr m_session = nullptr; private: @@ -129,4 +134,41 @@ namespace kiwi { namespace model { bool operator ==(DocumentManager const& rhs) const = delete; bool operator !=(DocumentManager const& rhs) const = delete; }; + + // ================================================================================ // + // SESSION // + // ================================================================================ // + + //! @brief The Session is used internally by the DocumentManager to + //! handle the gesture commits. + //! @details A session manages its own transaction stack, + //! and squash each new transaction into a single one. + //! @see DocumentManager::startCommitGesture, DocumentManager::commitGesture, DocumentManager::endCommitGesture, DocumentManager::isInCommitGesture + class DocumentManager::Session + { + public: + Session(flip::DocumentBase& document); + virtual ~Session(); + + //! @brief Starts a change + void start(); + + //! @brief Commits a change without a transaction label + void commit(); + + //! @brief Commits a change with a transaction label + void commit(std::string label); + + //! @brief Clear history + void end(flip::History* master_history = nullptr); + + //! @brief Reverts all changes + void revert(); + + private: + + flip::DocumentBase& m_document; + flip::History m_history; + flip::Transaction m_tx; + }; }} From 8c7d351d3e2098806a2c1c852601cdfccf22b6af Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 22:19:55 +0200 Subject: [PATCH 34/41] update PatcherComponent toolbar --- .../KiwiApp_General/KiwiApp_LookAndFeel.h | 2 +- .../KiwiApp_PatcherComponent.cpp | 70 ++++++++++++++----- .../KiwiApp_PatcherComponent.h | 22 +++--- 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h index 11f193ef..adb19866 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h @@ -34,7 +34,7 @@ namespace kiwi LookAndFeel(); //! @brief Destructor. - ~LookAndFeel() = default; + virtual ~LookAndFeel() = default; //! @brief Return the default border size of an object. float getObjectBorderSize() const; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp index e9371244..4d437487 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp @@ -29,19 +29,39 @@ namespace kiwi { + class PatcherToolbar::PatcherToolbarLookAndFeel + : public LookAndFeel + { + public: + + PatcherToolbarLookAndFeel(PatcherToolbar& toolbar) + : m_toolbar(toolbar) + {} + + void paintToolbarBackground(juce::Graphics& g, + int width, int height, + juce::Toolbar&) override + { + m_toolbar.paintToolbarBackground(g, width, height); + } + + private: + PatcherToolbar& m_toolbar; + }; + // ================================================================================ // // PATCHER COMPONENT TOOLBAR // // ================================================================================ // - PatcherToolbar::PatcherToolbar(PatcherManager& patcher_manager) : - m_patcher_manager(patcher_manager), - m_factory(m_patcher_manager) + PatcherToolbar::PatcherToolbar(PatcherManager& patcher_manager) + : m_patcher_manager(patcher_manager) + , m_factory(m_patcher_manager) + , m_toolbar_look_and_feel(std::make_unique(*this)) { + m_toolbar.setLookAndFeel(m_toolbar_look_and_feel.get()); + m_toolbar.setVertical(false); m_toolbar.setStyle(juce::Toolbar::ToolbarItemStyle::iconsOnly); - auto& lf = KiwiApp::useLookAndFeel(); - m_toolbar.setColour(juce::Toolbar::ColourIds::backgroundColourId, lf.getCurrentColourScheme() - .getUIColour(juce::LookAndFeel_V4::ColourScheme::UIColour::windowBackground)); m_toolbar.setColour(juce::Toolbar::ColourIds::labelTextColourId, juce::Colours::black); m_toolbar.setColour(juce::Toolbar::ColourIds::buttonMouseOverBackgroundColourId, juce::Colours::whitesmoke.contrasting(0.1)); m_toolbar.setColour(juce::Toolbar::ColourIds::buttonMouseDownBackgroundColourId, juce::Colours::whitesmoke.contrasting(0.2)); @@ -53,27 +73,30 @@ namespace kiwi m_toolbar.addDefaultItems(m_factory); } + PatcherToolbar::~PatcherToolbar() + { + m_toolbar.setLookAndFeel(nullptr); + } + void PatcherToolbar::resized() { m_toolbar.setBounds(getLocalBounds()); } - void PatcherToolbar::paint(juce::Graphics& g) + void PatcherToolbar::paintToolbarBackground(juce::Graphics& g, int width, int height) { - g.fillAll(juce::Colour(0xff444444)); + g.fillAll(findColour(juce::Toolbar::ColourIds::backgroundColourId)); } void PatcherToolbar::removeUsersIcon() { - m_toolbar.removeToolbarItem(0); m_toolbar.removeToolbarItem(0); m_toolbar.repaint(); } - PatcherToolbar::Factory::Factory(PatcherManager& patcher_manager) : m_patcher_manager(patcher_manager) - { - - } + PatcherToolbar::Factory::Factory(PatcherManager& patcher_manager) + : m_patcher_manager(patcher_manager) + {} void PatcherToolbar::Factory::getAllToolbarItemIds(juce::Array& ids) { @@ -177,7 +200,8 @@ namespace kiwi void PatcherToolbar::UsersItemComponent::updateUsers() { - m_user_nb = m_patcher_manager.getNumberOfUsers(); + auto users_ids = m_patcher_manager.getConnectedUsers(); + m_user_nb = users_ids.size(); startFlashing(); @@ -210,7 +234,7 @@ namespace kiwi }); }; - KiwiApp::useApi().getUsers(m_patcher_manager.getConnectedUsers(), success, fail); + KiwiApp::useApi().getUsers(users_ids, success, fail); } void PatcherToolbar::UsersItemComponent::connectedUserChanged(PatcherManager& manager) @@ -237,13 +261,21 @@ namespace kiwi center_y - count_size / 2, count_size, count_size); - const juce::Colour badge_color(juce::Colours::grey.withAlpha(0.5f)); - g.setColour(badge_color.overlaidWith(juce::Colours::white.withAlpha(m_flash_alpha))); + const auto online_color = juce::Colour::fromRGB(72, 165, 93); + const auto badge_color = (juce::Colours::grey.withAlpha(0.5f) + .overlaidWith(online_color.withAlpha(m_flash_alpha))); + + g.setColour(badge_color); g.drawEllipse(label_bounds.expanded(2).toFloat(), 0.5f); g.fillEllipse(label_bounds.expanded(2).toFloat()); g.setColour(juce::Colours::whitesmoke); g.drawText(std::to_string(m_user_nb), label_bounds, juce::Justification::centred); + + auto flag_rect = juce::Rectangle(0, 0, 3, height).reduced(0, 2); + + g.setColour(online_color); + g.fillRect(flag_rect); } bool PatcherToolbar::UsersItemComponent::getToolbarItemSizes(int toolbarDepth, bool isVertical, @@ -264,7 +296,7 @@ namespace kiwi void PatcherToolbar::UsersItemComponent::startFlashing() { m_flash_alpha = 1.0f; - startTimerHz (25); + startTimerHz (20); } void PatcherToolbar::UsersItemComponent::stopFlashing() @@ -394,6 +426,8 @@ namespace kiwi setContentNonOwned(&m_patcher_component, true); setVisible(true); + + getLookAndFeel().setUsingNativeAlertWindows(false); } void PatcherViewWindow::removeUsersIcon() diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.h b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.h index bdf5e11d..486a2701 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.h @@ -35,18 +35,21 @@ namespace kiwi public: // methods //! @brief Constructor. - PatcherToolbar(PatcherManager& patcher_manager); + PatcherToolbar(PatcherManager& patcher_manager); + + //! @brief Destructor. + ~PatcherToolbar(); //! @brief juce::Component::resized - void resized() override; - - //! @brief juce::Component::paint - void paint(juce::Graphics& g) override; + void resized() override; //! @brief Removes users icon. void removeUsersIcon(); - private: // classes + private: // classes + + //! called by custom toolbar lookandfeel. + void paintToolbarBackground(juce::Graphics& g, int width, int height); //! @internal Toolbar item factory struct Factory : public juce::ToolbarItemFactory @@ -73,12 +76,15 @@ namespace kiwi class UsersItemComponent; - private: // variables + private: // variables PatcherManager& m_patcher_manager; juce::Toolbar m_toolbar; - Factory m_factory; + Factory m_factory; + std::unique_ptr m_toolbar_look_and_feel = nullptr; + + class PatcherToolbarLookAndFeel; }; // ================================================================================ // From 3f718284210da20582ff5ef312b0584241e92084 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sat, 16 Jun 2018 22:21:48 +0200 Subject: [PATCH 35/41] fix hexadecimal user id conversion (fixes user list display in patcherview) --- Client/Source/KiwiApp_Network/KiwiApp_Api.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp b/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp index 436f30ce..9efff7d3 100644 --- a/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp +++ b/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp @@ -22,6 +22,8 @@ #include "KiwiApp_Api.h" #include +#include + namespace kiwi { const std::string Api::Endpoint::root = "/api"; @@ -217,12 +219,14 @@ namespace kiwi json j_users; - for(uint64_t const& user_id : user_ids) + for(uint64_t user_id : user_ids) { - std::ostringstream result; - result << std::hex << std::uppercase << user_id; + std::stringstream converter; + + converter << std::setfill('0') << std::setw(16) + << std::hex << std::uppercase << user_id; - j_users.push_back(result.str()); + j_users.push_back(converter.str()); } session->setParameters({{"ids", j_users.dump()}}); From 6d2c9c2d99c4b39b57ac004de07b3bf080787242 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sun, 17 Jun 2018 20:09:33 +0200 Subject: [PATCH 36/41] better connection error handling (still need a big refactoring) --- Client/Source/KiwiApp.cpp | 141 +++++++++++---- Client/Source/KiwiApp.h | 62 ++++--- .../KiwiApp_DocumentBrowserView.h | 6 - .../KiwiApp_Application/KiwiApp_Instance.cpp | 160 ++++++++++-------- .../KiwiApp_Application/KiwiApp_Instance.h | 33 ++-- .../KiwiApp_DocumentBrowser.cpp | 14 +- .../KiwiApp_Network/KiwiApp_DocumentBrowser.h | 3 + .../KiwiApp_PatcherComponent.cpp | 4 +- .../KiwiApp_PatcherManager.cpp | 52 ++++-- .../KiwiApp_Patcher/KiwiApp_PatcherManager.h | 23 +-- .../KiwiApp_Patcher/KiwiApp_PatcherView.cpp | 2 +- 11 files changed, 322 insertions(+), 178 deletions(-) diff --git a/Client/Source/KiwiApp.cpp b/Client/Source/KiwiApp.cpp index 611b127d..97f6df53 100644 --- a/Client/Source/KiwiApp.cpp +++ b/Client/Source/KiwiApp.cpp @@ -120,11 +120,7 @@ namespace kiwi m_api.reset(new Api(*m_api_controller)); m_instance = std::make_unique(); - m_command_manager->registerAllCommandsForTarget(this); - - checkLatestRelease(); - - startTimer(10); + m_command_manager->registerAllCommandsForTarget(this); #if JUCE_WINDOWS openCommandFile(commandLine); @@ -136,7 +132,11 @@ namespace kiwi macMainMenuPopup.addSeparator(); macMainMenuPopup.addCommandItem(&getCommandManager(), CommandIDs::showAppSettingsWindow); juce::MenuBarModel::setMacMainMenu(m_menu_model.get(), &macMainMenuPopup, TRANS("Open Recent")); - #endif + #endif + + pingServer(); + startTimer(TimerIds::MainScheduler, 10); // main scheduler update + startTimer(TimerIds::ServerPing, 3000); // Server ping } void KiwiApp::anotherInstanceStarted(juce::String const& command_line) @@ -232,7 +232,8 @@ namespace kiwi juce::MenuBarModel::setMacMainMenu(nullptr); #endif - stopTimer(); + stopTimer(TimerIds::MainScheduler); + stopTimer(TimerIds::ServerPing); m_instance.reset(); m_api->cancelAll(); @@ -272,9 +273,18 @@ namespace kiwi return true; } - void KiwiApp::timerCallback() + void KiwiApp::timerCallback(int timer_id) { - m_scheduler->process(); + if(timer_id == TimerIds::MainScheduler) + { + m_instance->tick(); + m_scheduler->process(); + } + else if(timer_id == TimerIds::ServerPing) + { + // ping server + pingServer(); + } } bool KiwiApp::isMacOSX() @@ -343,55 +353,114 @@ namespace kiwi void KiwiApp::logout() { - useInstance().logout(); + useInstance().handleConnectionLost(); KiwiApp::use().m_api_controller->logout(); KiwiApp::commandStatusChanged(); } bool KiwiApp::canConnectToServer() { - return KiwiApp::use().m_same_app_and_server_version; + const auto server_version = KiwiApp::use().m_last_server_version_check; + + return (!server_version.empty() + && server_version != "null" + && KiwiApp::use().canConnectToServerVersion(server_version)); + } + + bool KiwiApp::canConnectToServerVersion(std::string const& server_version) + { + const auto& version = KiwiApp::use().getApplicationVersion(); + return (version.compare(server_version) == 0); } - void KiwiApp::checkLatestRelease() + void KiwiApp::pingSucceed(std::string const& new_server_version) { - std::string current_version = getApplicationVersion().toStdString(); + bool was_connected = canConnectToServer(); - Api::CallbackFn on_success = [this, current_version](std::string const& server_version) { - - KiwiApp::useScheduler().schedule([this, current_version, server_version]() { - - m_same_app_and_server_version = (current_version == server_version); - if (!m_same_app_and_server_version) - { - juce::String title = "Incompatible Application and Server versions."; - juce::String text = "- app: " + juce::String(current_version) + "\n"; - text += "- server: " + juce::String(server_version) + "\n"; - text += "\nPlease visit: https://github.com/Musicoll/Kiwi/releases"; - - juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, - title, text); - } - }); - }; + if((new_server_version == m_last_server_version_check) && was_connected) + return; + + if (was_connected) + { + logout(); + } + + if (canConnectToServerVersion(new_server_version)) + { + useInstance().login(); + } + else if(m_last_server_version_check != new_server_version) + { + const auto current_version = KiwiApp::use().getApplicationVersion().toStdString(); + warning("Can't connect to server! Requires Kiwi " + new_server_version + + ", please visit:"); + warning("https://github.com/Musicoll/Kiwi/releases"); + } + + m_last_server_version_check = new_server_version; + + if(!was_connected && canConnectToServer()) + { + const auto api_host = m_api_controller->getHost(); + post("Connection established to " + api_host); + } + } + + void KiwiApp::pingFailed(Api::Error error) + { + static const std::string ping_failed = "null"; - Api::ErrorCallback on_fail =[](Api::Error error) + const bool was_connected = canConnectToServer(); + if(was_connected) { + KiwiApp::error("Connection lost"); + m_instance->handleConnectionLost(); + } + else if(m_last_server_version_check != ping_failed) + { + KiwiApp::error("Connection to server failed: " + error.getMessage()); + } + + m_last_server_version_check = ping_failed; + } + + void KiwiApp::pingServer() + { + Api::CallbackFn success = [this](std::string const& server_version) { + + KiwiApp::useScheduler().schedule([this, server_version]() { + pingSucceed(server_version); + }); + }; - useApi().getRelease(on_success, on_fail); + useApi().getRelease(success, [this](Api::Error err) { + + KiwiApp::useScheduler().schedule([this, err = std::move(err)]() { + pingFailed(err); + }); + + }); } void KiwiApp::networkSettingsChanged(NetworkSettings const& settings, juce::Identifier const& id) { if (id == Ids::server_address) { - logout(); + auto& api = *m_api_controller; - m_api_controller->setHost(settings.getHost()); - m_api_controller->setPort(settings.getApiPort()); + const auto host = settings.getHost(); + const auto api_port = settings.getApiPort(); + //const auto session_port = settings.getSessionPort(); - checkLatestRelease(); + if((api.getHost() != host) || (api_port != api.getPort())) + { + // settings changed + m_api_controller->setHost(settings.getHost()); + m_api_controller->setPort(settings.getApiPort()); + + pingServer(); + } } } diff --git a/Client/Source/KiwiApp.h b/Client/Source/KiwiApp.h index 00319aa2..f23bd2b6 100644 --- a/Client/Source/KiwiApp.h +++ b/Client/Source/KiwiApp.h @@ -34,8 +34,8 @@ namespace ProjectInfo { const char* const projectName = "Kiwi"; - const char* const versionString = "v1.0.1"; - const int versionNumber = 0x101; + const char* const versionString = "v1.0.1"; + const int versionNumber = 0x101; } namespace kiwi @@ -44,9 +44,10 @@ namespace kiwi // KiWi APPLICATION // // ================================================================================ // - class KiwiApp : public juce::JUCEApplication, - public NetworkSettings::Listener, - public juce::Timer + class KiwiApp + : public juce::JUCEApplication + , public NetworkSettings::Listener + , public juce::MultiTimer { public: // methods @@ -76,7 +77,7 @@ namespace kiwi bool moreThanOneInstanceAllowed() override; //! @brief Timer call back, processes the scheduler events list. - void timerCallback() override final; + void timerCallback(int timer_id) override; //! @brief Returns true if the app is running in a Mac OSX operating system. static bool isMacOSX(); @@ -223,7 +224,20 @@ namespace kiwi KiwiApp() = default; ~KiwiApp() = default; - private: // methods + private: // methods + + //! @internal Returns true if the App is compatible with a given server version. + bool canConnectToServerVersion(std::string const& server_version); + + //! @internal Ping the server to test current connection + //! @brief Called regularly by the App + void pingServer(); + + //! @internal handle ping succeed + void pingSucceed(std::string const& server_version); + + //! @internal handle ping failed + void pingFailed(Api::Error error); //! @internal Utility to quit the app asynchronously. class AsyncQuitRetrier; @@ -234,29 +248,33 @@ namespace kiwi //! @internal Initializes gui specific objects. void declareObjectViews(); - //! @internal Checks if current Kiwi version is the latest. Show popup if version not up to date. - void checkLatestRelease(); - // @brief Handles changes of server address. void networkSettingsChanged(NetworkSettings const& settings, juce::Identifier const& ids) override final; //! @brief Parse startup command line and open file if exists. void openCommandFile(juce::String const& command_line); - private: // members - - std::unique_ptr m_api_controller; - std::unique_ptr m_api; + private: // members + + enum TimerIds : int + { + MainScheduler = 0, + ServerPing, + }; + + LookAndFeel m_looknfeel; + TooltipWindow m_tooltip_window; - std::unique_ptr m_instance; - std::unique_ptr m_command_manager; - std::unique_ptr m_menu_model; + std::unique_ptr m_api_controller = nullptr; + std::unique_ptr m_api = nullptr; - LookAndFeel m_looknfeel; - TooltipWindow m_tooltip_window; - std::unique_ptr m_settings; - std::unique_ptr> m_scheduler; + std::unique_ptr m_instance = nullptr; + std::unique_ptr m_command_manager = nullptr; + std::unique_ptr m_menu_model = nullptr; + std::unique_ptr m_settings = nullptr; + + std::unique_ptr> m_scheduler = nullptr; - bool m_same_app_and_server_version = false; + std::string m_last_server_version_check {}; }; } diff --git a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.h b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.h index 751db569..7c477979 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.h +++ b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.h @@ -107,12 +107,6 @@ namespace kiwi //! @details Called when one or more document has been changed / removed or added. void driveChanged() override; - //! @brief Disable the display of document and their modification. - void disable(); - - //! @brief Enable the display of documents and their modification. - void enable(); - //! @brief Returns the number of items in the list. int getNumRows() override; diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp b/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp index 5d417e61..4c20825a 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp @@ -41,62 +41,22 @@ namespace kiwi size_t Instance::m_untitled_patcher_index(1); - Instance::Instance() : - m_instance(std::make_unique(), KiwiApp::useScheduler()), - m_browser(KiwiApp::getCurrentUser().isLoggedIn() ? KiwiApp::getCurrentUser().getName(): "logged out", - 1000), - m_console_history(std::make_shared(m_instance)), - m_last_opened_file(juce::File::getSpecialLocation(juce::File::userHomeDirectory)) + Instance::Instance() + : m_instance(std::make_unique(), KiwiApp::useScheduler()) + , m_browser("Offline", 1000) + , m_console_history(std::make_shared(m_instance)) + , m_last_opened_file(juce::File::getSpecialLocation(juce::File::userHomeDirectory)) { - startTimer(10); - // reserve space for singleton windows. m_windows.resize(std::size_t(WindowId::count)); - - //showAppSettingsWindow(); - //showBeaconDispatcherWindow(); + showDocumentBrowserWindow(); showConsoleWindow(); } Instance::~Instance() - { - closeAllPatcherWindows(); - stopTimer(); - } - - void Instance::timerCallback() - { - for(auto manager = m_patcher_managers.begin(); manager != m_patcher_managers.end();) - { - bool keep_patcher = true; - - if ((*manager)->isRemote()) - { - (*manager)->pull(); - - if (!(*manager)->isRemote()) - { - keep_patcher - = (*manager)->getFirstWindow().showOkCancelBox(juce::AlertWindow::QuestionIcon, - "Connetion lost", - "Do you want to continue editing document \"" - + (*manager)->getDocumentName() +"\" offline", - "Ok", - "Cancel"); - } - } - - if (!keep_patcher) - { - (*manager)->forceCloseAllWindows(); - manager = m_patcher_managers.erase(manager); - } - else - { - ++manager; - } - } + { + forceCloseAllPatcherWindows(); } uint64_t Instance::getUserId() const noexcept @@ -106,42 +66,86 @@ namespace kiwi } void Instance::login() + { + if(getUserId() > flip::Ref::User::Offline) { m_browser.setDriveName(KiwiApp::getCurrentUser().getName()); - m_windows[std::size_t(WindowId::DocumentBrowser)]->getContentComponent()->setEnabled(true); } - - void Instance::logout() + else + { + showAuthWindow(AuthPanel::FormType::Login); + } + } + + void Instance::handleConnectionLost() + { + m_browser.setDriveName("Offline"); + m_windows[std::size_t(WindowId::DocumentBrowser)]->getContentComponent()->setEnabled(false); + } + + void Instance::tick() + { + static bool is_pulling = false; + if(!is_pulling) + { + is_pulling = true; + pullRemoteDocuments(); + is_pulling = false; + } + } + + bool Instance::askUserToContinueEditingDocumentOffline(PatcherManager& manager, + std::string const& reason) const { - m_browser.setDriveName("logged out"); + auto& first_window = manager.getFirstWindow(); + auto message = std::string("Do you want to continue editing document \""); + message += manager.getDocumentName() + "\" offline ?"; - for(auto manager = m_patcher_managers.begin(); manager != m_patcher_managers.end();) + return first_window.showOkCancelBox(juce::AlertWindow::QuestionIcon, + reason, message, "Yes", "No"); + } + + void Instance::pullRemoteDocuments() + { + const bool user_logged_in = KiwiApp::getCurrentUser().isLoggedIn(); + const bool is_connected_to_api = (KiwiApp::canConnectToServer() && user_logged_in); + + auto manager_it = m_patcher_managers.begin(); + while(manager_it != m_patcher_managers.end()) { - if ((*manager)->isRemote()) + auto& manager = *(*manager_it); + + if (manager.isConnected()) { - bool keep_patcher - = (*manager)->getFirstWindow().showOkCancelBox(juce::AlertWindow::QuestionIcon, - "User logged out", - "Do you want to continue editing document \"" - + (*manager)->getDocumentName() +"\" offline", - "Yes", - "No"); + manager.pull(); // This is here we pull the flip document. - if (!keep_patcher) - { - (*manager)->forceCloseAllWindows(); - manager = m_patcher_managers.erase(manager); - } - else + const bool is_still_connected = manager.isConnected(); + const bool connection_lost = !is_still_connected; + + if(connection_lost + || (is_still_connected + && (!is_connected_to_api || !user_logged_in))) { - (*manager)->disconnect(); - ++manager; + auto reason = user_logged_in ? "Connection lost" : "You are logged out"; + bool keep_edit = askUserToContinueEditingDocumentOffline(manager, reason); + + if(!keep_edit) + { + // the user wants to close the document + manager.forceCloseAllWindows(); + manager_it = m_patcher_managers.erase(manager_it); + continue; + } + else if(!is_connected_to_api) + { + manager.disconnect(); + } } } + + ++manager_it; } - - m_windows[std::size_t(WindowId::DocumentBrowser)]->getContentComponent()->setEnabled(false); } engine::Instance& Instance::useEngineInstance() @@ -326,6 +330,16 @@ namespace kiwi } return success; + } + + void Instance::forceCloseAllPatcherWindows() + { + for(auto& manager : m_patcher_managers) + { + manager->forceCloseAllWindows(); + } + + m_patcher_managers.clear(); } void Instance::openRemotePatcher(DocumentBrowser::Drive::DocumentSession& session) @@ -374,7 +388,7 @@ namespace kiwi { const auto find_it = [&file](std::unique_ptr const& manager_uptr) { - return (!manager_uptr->isRemote() && file == manager_uptr->getSelectedFile()); + return (!manager_uptr->isConnected() && file == manager_uptr->getSelectedFile()); }; return std::find_if(m_patcher_managers.begin(), m_patcher_managers.end(), find_it); @@ -384,7 +398,7 @@ namespace kiwi { const auto find_it = [session_id = session.getSessionId()](std::unique_ptr const& manager_uptr) { - return (manager_uptr->isRemote() + return (manager_uptr->isConnected() && session_id != 0 && session_id == manager_uptr->getSessionId()); diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Instance.h b/Client/Source/KiwiApp_Application/KiwiApp_Instance.h index 6f37b9ba..ce05c033 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_Instance.h +++ b/Client/Source/KiwiApp_Application/KiwiApp_Instance.h @@ -47,7 +47,7 @@ namespace kiwi // ================================================================================ // //! @brief The Application Instance - class Instance : public juce::Timer + class Instance { public: @@ -57,17 +57,14 @@ namespace kiwi //! @brief Destructor ~Instance(); - //! @brief Timer call back, processes the scheduler events list. - void timerCallback() override final; + //! @brief Tick the instance regularly to pull remote document data. + void tick(); //! @brief Get the user ID of the Instance. uint64_t getUserId() const noexcept; //! @brief Enables the document browser view. - void login(); - - //! @brief Close all remote patchers and disable document browser view. - void logout(); + void login(); //! @brief create a new patcher window. void newPatcher(); @@ -95,7 +92,10 @@ namespace kiwi //! @brief Attempt to close all document, after asking user to save them if needed. //! @return True if all document have been closed, false if the user cancel the action. - bool closeAllPatcherWindows(); + bool closeAllPatcherWindows(); + + //! @brief Force close all patcher windows. + void forceCloseAllPatcherWindows(); //! @brief Attempt to create a new patcher with document Session informations. void openRemotePatcher(DocumentBrowser::Drive::DocumentSession& session); @@ -122,9 +122,22 @@ namespace kiwi void showBeaconDispatcherWindow(); //! @brief Get Patcher clipboard data. - std::vector& getPatcherClipboardData(); + std::vector& getPatcherClipboardData(); + + //! @internal Handle connection lost. + //! @todo refactor this to handle this event by a callback instead + void handleConnectionLost(); - private: // methods + private: // methods + + //! @internal pull all documents + //! @brief currently used by the Instance::tick method + void pullRemoteDocuments(); + + //! @brief Ask the user if he wants to continue to edit document offline + //! @details This could happen if the user logged out or if the connection was lost. + bool askUserToContinueEditingDocumentOffline(PatcherManager& manager, + std::string const& reason) const; using PatcherManagers = std::vector>; diff --git a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp index bf9745ac..71ec7d6e 100644 --- a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp +++ b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp @@ -284,7 +284,19 @@ namespace kiwi void DocumentBrowser::Drive::refresh_internal() { - if(!KiwiApp::canConnectToServer()) + const bool is_connected = KiwiApp::canConnectToServer(); + const bool was_connected = m_was_connected; + + if(was_connected != is_connected) + { + // dirty connection state manager :( + // todo: need to refactor all document browser system + m_listeners.call(&Listener::driveConnectionStatusChanged, is_connected); + } + + m_was_connected = is_connected; + + if(was_connected && !is_connected) { m_drive->updateDocumentList({}); return; diff --git a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h index e625766d..626bb947 100644 --- a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h +++ b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h @@ -142,6 +142,7 @@ namespace kiwi tool::Listeners m_listeners; Comp m_sort; std::shared_ptr m_drive; + bool m_was_connected = false; friend class DocumentBrowser; }; @@ -167,6 +168,8 @@ namespace kiwi //! @brief Called when one or more documents has been added, removed or changed. virtual void driveChanged() {}; + + virtual void driveConnectionStatusChanged(bool is_online) {}; }; // ================================================================================ // diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp index 4d437487..507ea555 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp @@ -108,7 +108,7 @@ namespace kiwi ids.add(flexibleSpacerId); ids.add(ItemIds::dsp_on_off); - if(m_patcher_manager.isRemote()) + if(m_patcher_manager.isConnected()) { ids.add(ItemIds::users); } @@ -116,7 +116,7 @@ namespace kiwi void PatcherToolbar::Factory::getDefaultItemSet(juce::Array& ids) { - if(m_patcher_manager.isRemote()) + if(m_patcher_manager.isConnected()) { ids.add(ItemIds::users); ids.add(separatorBarId); diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp index 0c60e7b1..2d71f8b9 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp @@ -62,7 +62,7 @@ namespace kiwi {} PatcherManager::~PatcherManager() - { + { disconnect(); } @@ -92,10 +92,14 @@ namespace kiwi void PatcherManager::pull() { - if (isRemote()) - { - m_socket.process(); - model::DocumentManager::pull(getPatcher()); + if (isConnected()) + { + m_socket.process(); + } + + if (isConnected()) + { + model::DocumentManager::pull(getPatcher()); } } @@ -104,8 +108,11 @@ namespace kiwi { if (transition == flip::CarrierBase::Transition::Disconnected) { - m_session->useDrive().removeListener(*this); - m_session = nullptr; + if(m_session) + { + m_session->useDrive().removeListener(*this); + m_session = nullptr; + } m_connected_users.clear(); @@ -126,13 +133,12 @@ namespace kiwi void PatcherManager::disconnect() { - if (isRemote()) + if (isConnected()) { m_socket.disconnect(); } } - bool PatcherManager::connect(std::string const& host, uint16_t port, DocumentBrowser::Drive::DocumentSession& session) @@ -320,7 +326,7 @@ namespace kiwi return m_document.root(); } - bool PatcherManager::isRemote() const noexcept + bool PatcherManager::isConnected() const noexcept { return m_socket.isConnected(); } @@ -373,7 +379,7 @@ namespace kiwi bool PatcherManager::needsSaving() const noexcept { - return m_need_saving_flag && !isRemote(); + return m_need_saving_flag && !isConnected(); } void PatcherManager::writeDocument() @@ -428,13 +434,15 @@ namespace kiwi if (needsSaving()) { + const auto title = TRANS("Closing document..."); + const auto message = TRANS("Do you want to save the changes to \"") + m_name + "\"?"; + const int r = juce::AlertWindow::showYesNoCancelBox(juce::AlertWindow::QuestionIcon, - TRANS("Closing document..."), - TRANS("Do you want to save the changes to \"") - + m_name + "\"?", + title, message, TRANS("Save"), TRANS("Discard changes"), - TRANS("Cancel")); + TRANS("Cancel"), + &getFirstWindow()); if (r == 0) // cancel button { @@ -463,7 +471,7 @@ namespace kiwi it = user.removeView(*it); } - model::DocumentManager::commit(patcher); + model::DocumentManager::commit(patcher); } bool PatcherManager::askAllWindowsToClose() @@ -542,6 +550,16 @@ namespace kiwi view.entity().use().toFront(true); } } + } + + void PatcherManager::driveConnectionStatusChanged(bool is_online) + { + //! @todo drive refactoring + if(m_session && !is_online) + { + m_session->useDrive().removeListener(*this); + m_session = nullptr; + } } void PatcherManager::documentAdded(DocumentBrowser::Drive::DocumentSession& doc) @@ -628,7 +646,7 @@ namespace kiwi bool is_locked = view.getLock(); - std::string title = (isRemote() ? "[Remote] " : "") + std::string title = (isConnected() ? "[Remote] " : "") + m_name + (is_locked ? "" : " (edit)"); diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.h b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.h index d45f4292..038b7c6a 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.h @@ -95,16 +95,16 @@ namespace kiwi model::Patcher const& getPatcher() const; //! @brief Returns true if the this is a remotely connected document. - bool isRemote() const noexcept; + bool isConnected() const noexcept; //! @brief Returns the session ID of the document. //! @details This function returns 0 if the document is loaded from disk or memory. - //! @see isRemote + //! @see isConnected uint64_t getSessionId() const noexcept; //! @brief Returns the name of the document. //! @details This function returns 0 if the document is loaded from disk or memory. - //! @see isRemote + //! @see isConnected std::string getDocumentName() const; //! @brief Returns the number of users connected to the patcher document. @@ -137,19 +137,22 @@ namespace kiwi void addListener(Listener& listener); //! @brief remove a listener. - void removeListener(Listener& listener); - - //! @brief Called when a document session has been added. - void documentAdded(DocumentBrowser::Drive::DocumentSession& doc) override; - - //! @brief Called when a document session changed. - void documentChanged(DocumentBrowser::Drive::DocumentSession& doc) override; + void removeListener(Listener& listener); //! @brief Force all windows to close without asking user to save document. void forceCloseAllWindows(); private: + //! @brief Called when a document session has been added. + void documentAdded(DocumentBrowser::Drive::DocumentSession& doc) override; + + //! @brief Called when a document session changed. + void documentChanged(DocumentBrowser::Drive::DocumentSession& doc) override; + + //! @brief Called when the drive connection status changed. + void driveConnectionStatusChanged(bool is_online) override; + //! @internal Called from socket process to notify changing state. void onStateTransition(flip::CarrierBase::Transition transition, flip::CarrierBase::Error error); diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp index 881a09c2..232fc696 100755 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp @@ -1856,7 +1856,7 @@ namespace kiwi { result.setInfo(TRANS("Save"), TRANS("Save document"), CommandCategories::general, 0); result.addDefaultKeypress('s', juce::ModifierKeys::commandModifier); - result.setActive(!m_manager.isRemote()); + result.setActive(!m_manager.isConnected()); break; } case CommandIDs::newPatcherView: From cda4fc0bed3330c224c1ea8e1b842763d9d4e8ba Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sun, 17 Jun 2018 21:09:17 +0200 Subject: [PATCH 37/41] Add [sf.play~] and [sf.record~] helps --- docs/ressources/patchs/help/sf.play~.help.kiwi | Bin 0 -> 25573 bytes .../ressources/patchs/help/sf.record~.help.kiwi | Bin 0 -> 29227 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/ressources/patchs/help/sf.play~.help.kiwi create mode 100644 docs/ressources/patchs/help/sf.record~.help.kiwi diff --git a/docs/ressources/patchs/help/sf.play~.help.kiwi b/docs/ressources/patchs/help/sf.play~.help.kiwi new file mode 100644 index 0000000000000000000000000000000000000000..009e4afdc3f0927ba415982d57d334eaf60c3e19 GIT binary patch literal 25573 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 literal 0 HcmV?d00001 diff --git a/docs/ressources/patchs/help/sf.record~.help.kiwi b/docs/ressources/patchs/help/sf.record~.help.kiwi new file mode 100644 index 0000000000000000000000000000000000000000..6a02eba75fda27507091789a312e833b2d06d280 GIT binary patch literal 29227 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 Date: Sun, 17 Jun 2018 21:16:15 +0200 Subject: [PATCH 38/41] update ressources --- docs/ressources/patchs/help/help-adc~.kiwi | Bin 5832 -> 0 bytes docs/ressources/patchs/help/help-dac~.kiwi | Bin 22415 -> 0 bytes .../patchs/help/help-delaysimple~.kiwi | Bin 13074 -> 0 bytes docs/ressources/patchs/help/help.zip | Bin 33893 -> 0 bytes docs/ressources/patchs/kiwi-helps.zip | Bin 0 -> 42859 bytes .../patchs/kiwi-helps/help-adc~.kiwi | Bin 0 -> 9908 bytes .../patchs/kiwi-helps/help-dac~.kiwi | Bin 0 -> 24961 bytes .../patchs/kiwi-helps/help-delaysimple~.kiwi | Bin 0 -> 15650 bytes .../{help => kiwi-helps}/help-line~.kiwi | Bin .../{help => kiwi-helps}/help-noise~.kiwi | Bin .../{help => kiwi-helps}/help-osc~.kiwi | Bin .../{help => kiwi-helps}/help-pack.kiwi | Bin .../{help => kiwi-helps}/help-phasor~.kiwi | Bin .../{help => kiwi-helps}/help-sah~.kiwi | Bin .../{help => kiwi-helps}/help-sig~.kiwi | Bin .../{help => kiwi-helps}/help-trigger.kiwi | Bin .../{help => kiwi-helps}/sf.play~.help.kiwi | Bin .../{help => kiwi-helps}/sf.record~.help.kiwi | Bin .../tutorials.zip => kiwi-tutorials.zip} | Bin 24473 -> 23200 bytes .../flanger.kiwi | Bin .../fm-synthesis.kiwi | Bin .../overlapped-pitchshifter.kiwi | Bin .../simple-pitchshifter.kiwi | Bin docs/software/objects.md | 2 +- docs/software/tutorials.md | 2 +- 25 files changed, 2 insertions(+), 2 deletions(-) delete mode 100755 docs/ressources/patchs/help/help-adc~.kiwi delete mode 100755 docs/ressources/patchs/help/help-dac~.kiwi delete mode 100755 docs/ressources/patchs/help/help-delaysimple~.kiwi delete mode 100644 docs/ressources/patchs/help/help.zip create mode 100644 docs/ressources/patchs/kiwi-helps.zip create mode 100755 docs/ressources/patchs/kiwi-helps/help-adc~.kiwi create mode 100755 docs/ressources/patchs/kiwi-helps/help-dac~.kiwi create mode 100755 docs/ressources/patchs/kiwi-helps/help-delaysimple~.kiwi rename docs/ressources/patchs/{help => kiwi-helps}/help-line~.kiwi (100%) rename docs/ressources/patchs/{help => kiwi-helps}/help-noise~.kiwi (100%) rename docs/ressources/patchs/{help => kiwi-helps}/help-osc~.kiwi (100%) rename docs/ressources/patchs/{help => kiwi-helps}/help-pack.kiwi (100%) rename docs/ressources/patchs/{help => kiwi-helps}/help-phasor~.kiwi (100%) rename docs/ressources/patchs/{help => kiwi-helps}/help-sah~.kiwi (100%) rename docs/ressources/patchs/{help => kiwi-helps}/help-sig~.kiwi (100%) rename docs/ressources/patchs/{help => kiwi-helps}/help-trigger.kiwi (100%) rename docs/ressources/patchs/{help => kiwi-helps}/sf.play~.help.kiwi (100%) rename docs/ressources/patchs/{help => kiwi-helps}/sf.record~.help.kiwi (100%) rename docs/ressources/patchs/{tutorials/tutorials.zip => kiwi-tutorials.zip} (83%) rename docs/ressources/patchs/{tutorials => kiwi-tutorials}/flanger.kiwi (100%) rename docs/ressources/patchs/{tutorials => kiwi-tutorials}/fm-synthesis.kiwi (100%) rename docs/ressources/patchs/{tutorials => kiwi-tutorials}/overlapped-pitchshifter.kiwi (100%) rename docs/ressources/patchs/{tutorials => kiwi-tutorials}/simple-pitchshifter.kiwi (100%) diff --git a/docs/ressources/patchs/help/help-adc~.kiwi b/docs/ressources/patchs/help/help-adc~.kiwi deleted file mode 100755 index 2551dcd92cd8a9a61b82358dfd80392832451a8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5832 zcmbVQU1%It6y8ZE+2*I&#Mm^Awj@~UDru-S6qG`@jR_l#X0_28h%B4kNi)st&brzB zv}!gYq834)6hTV#!H0%Y5m68y3hk3p`cP{v2nrTdu(VJqtuOU{ckY>;y*snLdLFW~ z=YIFxbIy0ZnVlTAkMvGkWW zXkxXkyfQXlbavH=wf6E_B2`S!7zMVlPAqm-E%rY@#}Rrk;sw)KWUKW8;XT02jUUMy zY@tCcZY&>QPaQVW#r{+#z04NF0?<_jNZZ-0kwf9@1!hwfCTkRp0>U&3NE4dczpqe8 z9buj(;cevJ{gyq&9L<$Z+qRgaMK~iIIcQrJ%h9xR%)OxkKa`u#GDoX$I_C{rb_$6` zgm*oUGQyK@6TVjN8{@@4XG}A1yI9#QK3hb^$)d63<-1|VFb~bZLp(Q|7>T5r%NR?{ zO%yk9_Z;&Ouk{ui5IBg&2Fp&*8X4vx+M2RvF6o{?d^N(mXl9Bt3`!h@9qJH|C~2|z zoVlqG4`Ig-D3MT~x95^bmk8hv&_vCzFQiz{nS)y1QZUknxnLBM>}2MruD8w^;K#@$ zsXpp_o4ZoZl^AH6Gg!kY7Ij}2-6rAbMQ3t5*m&y)ejMsh4LA;8f| z4r<&c;b=Z;+u7uFDqXapx-Q`+Iz56GvJn86+3=v9D;D^q(o(|#^H45@*`+WcOjyCT zX?EbJLBJ<*CgU(ZE=lYf*7!*2?~v8^(bHR;@1b?j-fW(aIdgR(IL!q%Q~P8bf)nAo z(7cFxjPBfrRUV1gSdgfrWR^Z~x>#3^Natwrs<(1g(+6HeeJ|-G&BJs)z>* zFW>0hoG*<#-{cgciYY`DQ;5nb&}>Mhcj5LFiH(V@%J&Evhq5EFXHOH5Bq!rAtC84# zg1MrRaS%x)HsIoD#$_CCbac}JnA$Jnz_=Gjw>BAvK6r76*;u-9GL*XY;44qz8?(wy z8YhSDxWOXSI62hl2KSliFGGz}M1$&1$*#w*K6&Xl^+iggk6=g=*Zwlp`YNu411YLp?4x2H5b?ZGSqe=w0E}Y7%5)VD?>^<+;h^5#`33F2$-~G05F7y)kt9g z7==^^yc$4EqDDG5>S$m$Al&FVDdQXsOsxu7$X^3m)X~5M!2&d(Mb$K*MY#q%TA|wI z{6K|QQ~nyzqW&7V^J!@L{I$*i-s2!dK?8A+Lyf+x2I8QICdi$Vni?1-7~5G(1EWZ! zDs)W^BoLaKi|c@1BoJCb1M&Z^fpHv*@UR*wj02;P>bRl?;*JKAF`Nqv&_G;h;4}xZ zKLHwuD{3IFsDZem2I7hui2G@vxpe1L=;E;t7f($8BI>J@n9e7Je%~R4|IaaXAcc6fyBV?Sz=Ew>#Fn>1ngu~{e4v78rXH!o&Y!0sw5R(a$ z&sAenPpGk}C)C)~6Mk&^<%d2{&*)b&aE$pop;E`C!JBIVt+!CJ0`nGxNk`nBc}tOb zOObg?k$KBu{)rWqEw=#XErmLl_(BJ-A)`R>$w#iaRiyYn`#nMqqF6-%D{pCwP$ zD7oS%ikz3XWaLN;$~d`X`saiEGLLxl`>L+-yMw>Iz3b?Sze-;iAD_9}zUA8BTL0G< z-gxM-)vcB9-h3Re7lu4K18~TX;%g6_edpFoKV8||`_02cTR-?G_u|CG3IBaMVxk5Z zuQ>qcjSp-j0Ed249>CQH;BXdy%5(()_>F1rb;JmT#xKA7^3m%ZSB5`3zvqvRuipFp z^X&ou9;j}Z6G0k0hfqpX`}X^#oA37|yX_y&Xm74w*xr5LuLu42RW~dESJkipTvfvY MaOH*t04f^xFX9vz6951J diff --git a/docs/ressources/patchs/help/help-dac~.kiwi b/docs/ressources/patchs/help/help-dac~.kiwi deleted file mode 100755 index fd157f609bf907a05627b7bfe91e4d1a39764ab8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22415 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 yZ+&Y1KlU8@_|?c^^SiQ_L)VFhIP8C(@6hxB diff --git a/docs/ressources/patchs/help/help.zip b/docs/ressources/patchs/help/help.zip deleted file mode 100644 index 7f1b8b40887649eff9b1d533ed75440afaaff918..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33893 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 diff --git a/docs/ressources/patchs/kiwi-helps.zip b/docs/ressources/patchs/kiwi-helps.zip new file mode 100644 index 0000000000000000000000000000000000000000..eea84a54b38e01ac6ea51019ce5568d8e3b5f162 GIT binary patch literal 42859 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%0D4PL@7fA9+v`E=})G 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 sZrFMMzWgU-O!~he?+rqKL*5NSU%fxeO7v~Eu( z^o*s(NA<$cP9r(OG-cLk&B936ct*^+$Qr9%80(H^V#E3v8_=x5#*)F--D!^Ssu6b> z`UD#-w-ELPjAVE!rL%zwYjEYl0_MP7dMwi#jmHkML7xR!RRV~aqoaBffmbSx#G~AZ zUuk4iPp6|p@MM{VxwvGZQ9YxN!IvrvvJBN}-7+>7ono4RrS)^|)`U606w8a1rkP-h zYD>9-Be$7}1PjzKlT5v$2!C^Oe3U6_ETvfQmV_Baw6&JDl3Ur%Gf-#gs=2Ow?1pe9tQz?dG> zjs5yqA6tm&saDl#9l6)zedIe8tfnxs;2PC!Sz2ewAX`*2ua!xvmNz4)O?4{AceqVO zDj#R914TXigOu-QodQJz|H>2-Mm#gjzO1wqRnw+9+BX=DWlXeXqot-&**f0pD+^Gl zns1w#%#3js(ojW@F_esgmt@8R_KcY(@dA1(GCrw9P8_H~nWazv%zvNqL_h!5-8gC5ry*no;)L7j zh;eygz1{Z00w?cqZ@0*56ChmVS@m{{tkSJb)#2W5k<~#mr|58Rw=Aoez}f3l$sK45u4i*J1>RmDtO4;21ou61@bI7IQ)&nXD6Z=iIJi=> zUyS;ex7_;KcKMVBm@8^R1rDZ(q3qJ*m*WCu2tZG zapCGJ`+fm?5Z^@ENZ{HJ&&f?Uxpbk)r3+0?7naG_4V2tI6*$pc1E+W_?4OkioM^5e zbP%pgfs-{C?}_}2yOOC#_bPgVKV}r|S#taQF@u`YzGo@m_LhL_3UamE$pxGut?eSM z1M|Q@sub(GOl!MHE8Z-GDAL+ql2*mK_C4#HQ|5Ru7EK1zDLrNkP6cDb(PUCjq=QlR z-`LPN-hT1c01-9%s_TO-xqX+&jq}?tZTgGsTm1G*HS$_-(26~a--2mlp;q-(`VT+7 z;kt+9ZbYc_;5M!mdsYPIN=)|*PH5iq56Vs733Dzsf;(Z_zGtzop30tcZ~f)x$M1br zZoA*5?S8v$3f0#+{@3|ucHAvD9VZyvz_lVHaeGE+j&H+`RcCi^Ixjap448|I3_HD6 zG+NkYqlHDIW%}9jTojEK7LC?SFxCDQ7LB&KfKxPDxTMk84AC3;Pv5Tla^tK#Pg&&2 z#r~B=o~SpL-oLUg_phwWv}Q$GQ}fIVE{e2fU8XfF(z=7pc@$~QmZVjYM*IF%nb!4S zG?6wf>(zL)%I80Q_lgN)`3vi$7PJ%!K~J1mwMKy}D#XGmo_wFuQuaa=TIafJ=7&~4 z{=R&L48h<&uH{{+Wu#rX;PP}PI!1?K7JWYpo(%jxvP(hV98&3mzB#0|u#YTB-`s`M_hI4z zGJ|UceIJG=bep9)zD4PK1TYtzI0Do5+pL1VxeKT7QMdr}5L_$hdlVQKt{!zs-<+WD z_YX0%T;v{4iF;kYG7JKt8!}oQpT_hqh#^s}$t-XJ&3&naEh zr|6t=$)^;5(Lp8rQPijC$PYM0eXR4g8vx;=s86S;Ph#GJsp?~$x9vJaI7NL*&)XDf zwAaVKO}teF+bru;i7)4l?LBs5v)lxKiE&X0{u1N9Qz9-qCE~JEBBE3JPf_L+m58{k zL_~DTl9hoc$2yKduSYl_e6dEa9$1i5NZ;E*4(Uv|X{_kX_k zt<#&={_(1t*F5^aWTf|vUimyPY$65-)~QzD?Dm0OuD~JR()O`K< zvilx;?aE8PF~3JXuQXu_oZUVkbQ5*>>n%x`!rnr{6o8_HJ^ALYYt!$n&TaVa_vU_> zEq_sOYd#_EZo#S|hAD9NfXc#y_x$$o>C2z{=gAwj56?f;*1P(S-ST;*2~*(g_Ibh- TxRQh^a6Dn4ccTJOl(7E;#B9#k literal 0 HcmV?d00001 diff --git a/docs/ressources/patchs/kiwi-helps/help-dac~.kiwi b/docs/ressources/patchs/kiwi-helps/help-dac~.kiwi new file mode 100755 index 0000000000000000000000000000000000000000..be44878422f192bc76ceda5a62113c487e84e5e4 GIT binary patch literal 24961 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 aeGN>+O9_|)j|B|$mhXo$*bX$wuzv!$;37u= literal 0 HcmV?d00001 diff --git a/docs/ressources/patchs/kiwi-helps/help-delaysimple~.kiwi b/docs/ressources/patchs/kiwi-helps/help-delaysimple~.kiwi new file mode 100755 index 0000000000000000000000000000000000000000..f92a47cb55d83592c27cc7b229dc2fc07bd76f02 GIT binary patch literal 15650 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. The list of help patches is currently incomplete but will evolve later on. +A list of help patches describing how each object works is here. The list of help patches is currently incomplete but will evolve later on. ## List of objects diff --git a/docs/software/tutorials.md b/docs/software/tutorials.md index b17f430d..4d9ce7c2 100644 --- a/docs/software/tutorials.md +++ b/docs/software/tutorials.md @@ -1,6 +1,6 @@ # Tutorials -A set of tutorials is available here. Each tutorial aims at taking you through the creation of a specific audio processing or exploring a certain aspect of Kiwi software. +A set of tutorials is available here. Each tutorial aims at taking you through the creation of a specific audio processing or exploring a certain aspect of Kiwi software. Available tutorials: From 92461bbe2d7bade1f277d497b3e70b5037ebea21 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sun, 17 Jun 2018 21:22:21 +0200 Subject: [PATCH 39/41] bump Kiwi version to v1.0.2 --- Client/Source/KiwiApp.h | 4 ++-- Ressources/Project/Xcode/Info.plist | 4 ++-- Scripts/setup-Win32.iss | 2 +- Scripts/setup-x64.iss | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Client/Source/KiwiApp.h b/Client/Source/KiwiApp.h index f23bd2b6..d60d40ab 100644 --- a/Client/Source/KiwiApp.h +++ b/Client/Source/KiwiApp.h @@ -34,8 +34,8 @@ namespace ProjectInfo { const char* const projectName = "Kiwi"; - const char* const versionString = "v1.0.1"; - const int versionNumber = 0x101; + const char* const versionString = "v1.0.2"; + const int versionNumber = 0x102; } namespace kiwi diff --git a/Ressources/Project/Xcode/Info.plist b/Ressources/Project/Xcode/Info.plist index 9e33453b..8e95cfa8 100755 --- a/Ressources/Project/Xcode/Info.plist +++ b/Ressources/Project/Xcode/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.1 + 1.0.2 CFBundleVersion - 1.0.1 + 1.0.2 NSHighResolutionCapable NSHumanReadableCopyright diff --git a/Scripts/setup-Win32.iss b/Scripts/setup-Win32.iss index 2d8710e4..eef62299 100644 --- a/Scripts/setup-Win32.iss +++ b/Scripts/setup-Win32.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Kiwi" -#define MyAppVersion "v1.0.1" +#define MyAppVersion "v1.0.2" #define MyAppPublisher "CICM" #define MyAppURL "http://kiwi.mshparisnord.fr/" #define MyAppExeName "Kiwi.exe" diff --git a/Scripts/setup-x64.iss b/Scripts/setup-x64.iss index d51909f6..bcb32670 100644 --- a/Scripts/setup-x64.iss +++ b/Scripts/setup-x64.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Kiwi" -#define MyAppVersion "v1.0.1" +#define MyAppVersion "v1.0.2" #define MyAppPublisher "CICM" #define MyAppURL "http://kiwi.mshparisnord.fr/" #define MyAppExeName "Kiwi.exe" From 50852711623e345d59e019a992a21842fa7f138a Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sun, 17 Jun 2018 21:23:00 +0200 Subject: [PATCH 40/41] add v1.0.2 changelog --- docs/software/versions.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/software/versions.md b/docs/software/versions.md index 71cfb093..7fa247c8 100644 --- a/docs/software/versions.md +++ b/docs/software/versions.md @@ -2,6 +2,34 @@ Kiwi follows the [Semantic Versioning Specification](http://semver.org/). +## v1.0.2 + +#### New objects: +- [sf.play~]: play an audio file from disk. +- [sf.record~]: record an audio file on disk. + +#### Improvements : +- All object boxes can now be resized. +- Changed the look of the gui links and boxes. +- Enhance contrast between local and distant selections. +- Add contrast between control and signal pins. +- Add a dialog box to set the name of the patch to create. +- Drag-and-drop a file to the console to open it. +- Improved client/server version check. + +#### Bug-fix +- Fix crashes in transaction stack. +- Handle 404 errors in *download* and *duplicate* API requests. +- Fix connected user list display + +#### Dev: +- Bump Kiwi Model version to v4.0.1 +- Removes "ratio", "min_width", "min_height" members from the `model::Object`. +- Update model converter code. +- Use juce (v5.2.1). +- Add [kiwi-node-server](https://github.com/Musicoll/kiwi-node-server) as submodule. +- Add Juce dependency to the engine. + ## v1.0.1 [select] : From 955cd59270e5168529d0128d27349cb954ea6774 Mon Sep 17 00:00:00 2001 From: eliott PARIS Date: Sun, 17 Jun 2018 21:35:33 +0200 Subject: [PATCH 41/41] update kiwi-node-server --- ThirdParty/kiwi-node-server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ThirdParty/kiwi-node-server b/ThirdParty/kiwi-node-server index 5aa126ef..74c9c851 160000 --- a/ThirdParty/kiwi-node-server +++ b/ThirdParty/kiwi-node-server @@ -1 +1 @@ -Subproject commit 5aa126ef651fbad16ec43492aaa9c6e9628a4e7f +Subproject commit 74c9c851fa86357c366f59d2d8ae99e0831c4857