diff --git a/.gitmodules b/.gitmodules index ba43d85c..efbaad35 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,12 @@ [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 [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/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) diff --git a/Client/Source/KiwiApp.cpp b/Client/Source/KiwiApp.cpp index 0cb4b512..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) @@ -209,7 +209,9 @@ namespace kiwi engine::SwitchTilde::declare(); engine::Float::declare(); engine::ClipTilde::declare(); - engine::Clip::declare(); + engine::Clip::declare(); + engine::SfPlayTilde::declare(); + engine::SfRecordTilde::declare(); } void KiwiApp::declareObjectViews() @@ -230,7 +232,8 @@ namespace kiwi juce::MenuBarModel::setMacMainMenu(nullptr); #endif - stopTimer(); + stopTimer(TimerIds::MainScheduler); + stopTimer(TimerIds::ServerPing); m_instance.reset(); m_api->cancelAll(); @@ -270,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() @@ -331,7 +343,6 @@ namespace kiwi void KiwiApp::setAuthUser(Api::AuthUser const& auth_user) { (*KiwiApp::use().m_api_controller).setAuthUser(auth_user); - KiwiApp::useInstance().login(); } @@ -342,45 +353,114 @@ namespace kiwi void KiwiApp::logout() { - useInstance().logout(); + useInstance().handleConnectionLost(); KiwiApp::use().m_api_controller->logout(); KiwiApp::commandStatusChanged(); } - void KiwiApp::checkLatestRelease() + bool KiwiApp::canConnectToServer() { - std::string current_version = getApplicationVersion().toStdString(); + const auto server_version = KiwiApp::use().m_last_server_version_check; - Api::CallbackFn on_success = [current_version](std::string const& latest_version) + 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::pingSucceed(std::string const& new_server_version) + { + bool was_connected = canConnectToServer(); + + if((new_server_version == m_last_server_version_check) && was_connected) + return; + + if (was_connected) { - KiwiApp::useScheduler().schedule([current_version, latest_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"); - } - }); - }; + logout(); + } - Api::ErrorCallback on_fail =[](Api::Error error) + 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"; + + 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 ac7cbb2f..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 @@ -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(); @@ -111,7 +112,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(); @@ -220,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; @@ -231,27 +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; + + std::string m_last_server_version_check {}; }; } diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp b/Client/Source/KiwiApp_Application/KiwiApp_Console.cpp index 5cae1fe5..ba25a607 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"); } } @@ -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; }; diff --git a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp index ea356479..42b54bb4 100644 --- a/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp +++ b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.cpp @@ -33,14 +33,16 @@ #include "../KiwiApp_Ressources/KiwiApp_BinaryData.h" #include "../KiwiApp.h" +#include + 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 +95,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 +141,45 @@ 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,10 +199,10 @@ namespace kiwi { juce::PopupMenu m; - if (!m_drive_view.getTrashMode()) + 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"); } @@ -354,56 +368,49 @@ 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); - 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.getTrashMode(); - 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.getTrashMode() ? 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.getTrashMode()) - 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() @@ -584,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) @@ -617,7 +636,7 @@ namespace kiwi }); } - bool DocumentBrowserView::DriveView::getTrashMode() const + bool DocumentBrowserView::DriveView::isShowingTrashedDocuments() const { return m_trash_mode; } @@ -632,14 +651,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_Application/KiwiApp_DocumentBrowserView.h b/Client/Source/KiwiApp_Application/KiwiApp_DocumentBrowserView.h index 3d4d496e..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; @@ -182,7 +176,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 +296,7 @@ namespace kiwi const juce::Image m_kiwi_filetype_img; - int m_row; + int m_row = -1; bool m_selected; bool m_mouseover = false; }; diff --git a/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp b/Client/Source/KiwiApp_Application/KiwiApp_Instance.cpp index be5edcad..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 @@ -107,41 +67,85 @@ namespace kiwi void Instance::login() { - m_browser.setDriveName(KiwiApp::getCurrentUser().getName()); + if(getUserId() > flip::Ref::User::Offline) + { + m_browser.setDriveName(KiwiApp::getCurrentUser().getName()); + m_windows[std::size_t(WindowId::DocumentBrowser)]->getContentComponent()->setEnabled(true); + } + 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 + { + auto& first_window = manager.getFirstWindow(); + auto message = std::string("Do you want to continue editing document \""); + message += manager.getDocumentName() + "\" offline ?"; - m_windows[std::size_t(WindowId::DocumentBrowser)]->getContentComponent()->setEnabled(true); + return first_window.showOkCancelBox(juce::AlertWindow::QuestionIcon, + reason, message, "Yes", "No"); } - - void Instance::logout() + + void Instance::pullRemoteDocuments() { - m_browser.setDriveName("logged out"); + const bool user_logged_in = KiwiApp::getCurrentUser().isLoggedIn(); + const bool is_connected_to_api = (KiwiApp::canConnectToServer() && user_logged_in); - for(auto manager = m_patcher_managers.begin(); manager != m_patcher_managers.end();) + 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", - "Ok", - "Cancel"); + 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() @@ -171,47 +175,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() @@ -316,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) @@ -364,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); @@ -374,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()); @@ -415,10 +439,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_Application/KiwiApp_Instance.h b/Client/Source/KiwiApp_Application/KiwiApp_Instance.h index e97319b9..ce05c033 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" @@ -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_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(); 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_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_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_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..a8659d6f 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.cpp @@ -1,235 +1,284 @@ -/* - ============================================================================== - - 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 - - ============================================================================== - */ +/* + ============================================================================== + + 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 +{ + LookAndFeel::LookAndFeel() + : juce::LookAndFeel_V4(getGreyColourScheme()) + { + setUsingNativeAlertWindows(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) + { + // 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; + 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_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, link_control_color); + setColour(LinkView::ColourIds::SignalBackground, link_signal_color); + } +} -#include "../KiwiApp_General/KiwiApp_LookAndFeel.h" -#include "../KiwiApp_Ressources/KiwiApp_BinaryData.h" - -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; - } - - LookAndFeel::LookAndFeel() : juce::LookAndFeel_V4(getGreyColourScheme()) - { - setColour(juce::ScrollBar::ColourIds::thumbColourId, juce::Colours::grey.withAlpha(0.7f)); - setUsingNativeAlertWindows(true); - } - - 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::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) - { - /* - if (isMouseDown) - g.fillAll(component.findColour (Toolbar::buttonMouseDownBackgroundColourId, true)); - else if (isMouseOver) - g.fillAll(component.findColour (Toolbar::buttonMouseOverBackgroundColourId, true)); - */ - } -} diff --git a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h index 610f19e2..adb19866 100644 --- a/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h +++ b/Client/Source/KiwiApp_General/KiwiApp_LookAndFeel.h @@ -1,81 +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 - - ============================================================================== - */ +/* + ============================================================================== + + 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. + virtual ~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; + + private: // deleted methods + + //! @brief Set the default Application components colors + 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; + }; +} -#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. - void drawTableHeaderColumn(juce::Graphics& g, 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; - - private: // deleted methods - - 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_Network/KiwiApp_Api.cpp b/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp index 1904c302..9efff7d3 100644 --- a/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp +++ b/Client/Source/KiwiApp_Network/KiwiApp_Api.cpp @@ -20,6 +20,9 @@ */ #include "KiwiApp_Api.h" +#include + +#include namespace kiwi { @@ -216,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()}}); @@ -524,13 +529,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) diff --git a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.cpp index 8d099aa8..71ec7d6e 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]() { @@ -284,6 +284,24 @@ namespace kiwi void DocumentBrowser::Drive::refresh_internal() { + 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; + } + std::weak_ptr drive(m_drive); KiwiApp::useApi().getDocuments([drive](Api::Response res, Api::Documents docs) @@ -473,6 +491,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()); @@ -537,6 +559,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()); diff --git a/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h b/Client/Source/KiwiApp_Network/KiwiApp_DocumentBrowser.h index f098e9f4..626bb947 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); @@ -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_LinkView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_LinkView.cpp index dcdaf98a..863665e4 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.5f); + + 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_Objects/KiwiApp_BangView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp index 8e67e40d..c122da4d 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_BangView.cpp @@ -48,32 +48,32 @@ 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) { 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 bfa6e72c..c9d6da3a 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 @@ -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, bg_colour.contrasting(0.4)); label.setColour(juce::Label::backgroundColourId, bg_colour); label.setColour(juce::Label::backgroundWhenEditingColourId, bg_colour); @@ -52,43 +55,32 @@ namespace kiwi } ClassicView::~ClassicView() - { - } + {} void ClassicView::paintOverChildren (juce::Graphics& g) - { - g.setColour (findColour (ObjectView::ColourIds::Outline)); - + { drawOutline(g); } - void ClassicView::resized() - { - getLabel().setBounds(getLocalBounds()); - } - 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 +90,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..badab6df 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ClassicView.h @@ -49,25 +49,22 @@ namespace kiwi //! @brief Destructor. ~ClassicView(); - private: // methods + private: // methods //! @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..f01d781e 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_CommentView.cpp @@ -40,76 +40,83 @@ 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.f); + float const dashed_length[2] {2.f, 2.f}; + path_stroke.createDashedStroke(path, path, dashed_length, 2); + + g.setColour(findColour (ObjectView::ColourIds::Outline)); + g.strokePath(path, path_stroke); + } } 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); + checkComponentBounds(this); } } 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 +127,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 +146,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..1c556bf9 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,10 @@ namespace kiwi { //! @brief Destructor. ~CommentView(); - private: // methods - - //! @brief Called when the object is resized. - void resized() override final; + private: // methods + + //! @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 +73,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..24b879fb 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_EditableObjectView.cpp @@ -27,16 +27,56 @@ 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); + + 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) @@ -50,13 +90,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; + + arr.addJustifiedText (getFont(), 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 +189,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 +226,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..7e05ca80 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,89 @@ 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 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; + + //! @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 +160,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 +171,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..f029cd5c 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MessageView.cpp @@ -40,105 +40,121 @@ 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::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) + { + 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(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 (ObjectView::ColourIds::Outline)); + { + if(getLabel().isBeingEdited()) + return; // abort - drawOutline(g); + 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; - - juce::Rectangle bounds = getLocalBounds(); - - if (m_active) - { - g.setColour(findColour(ObjectView::ColourIds::Active)); - } - - 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); } - 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()); + checkComponentBounds(this); } } 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 +165,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..3f443e8d 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 @@ -54,31 +55,30 @@ namespace kiwi { private: // methods //! @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_MeterTildeView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_MeterTildeView.cpp index f08342af..00c5fb60 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,59 @@ 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](float 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::resized() { - juce::AffineTransform transform; + const auto bounds = getLocalBounds().toFloat(); - 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())); - } + const int min = 10; + const int max = 30; + const bool vertical = (getWidth() <= getHeight()); + setMinimumSize(vertical ? min : max, vertical ? max : min); - juce::Rectangle bounds = getLocalBounds().transformedBy(transform); + const float num_leds = m_leds.size(); + const float padding = m_padding; + const float sep = m_led_distance; - float led_width = (bounds.getWidth() - (m_leds.size() - 1.) * m_padding - 2. * m_border) / m_leds.size(); + auto space = bounds.reduced(padding, padding); - int led_height = bounds.getHeight() - 2 * m_border; - - int led_y = bounds.getY() + (bounds.getHeight() / 2.) - (led_height / 2.); - - 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 +122,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 +132,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 +141,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..2bbae9c9 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 @@ -68,13 +68,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 08c03281..4c9cc696 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_NumberViewBase.cpp @@ -48,11 +48,11 @@ namespace kiwi { label.setInterceptsMouseClicks(false, false); addAndMakeVisible(label); + setMinimumSize(20., getMinHeight()); } NumberViewBase::~NumberViewBase() - { - } + {} void NumberViewBase::paint(juce::Graphics & g) { @@ -61,8 +61,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_ObjectFrame.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp index 09663d7e..8f2fc338 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.cpp @@ -32,31 +32,41 @@ 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_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); + + updateBoundsFromModel(false); + addChildComponent(m_outline); - + updateOutline(); + addAndMakeVisible(m_object_view.get()); - - updateBounds(false); - - updateOutline(); - - setInterceptsMouseClicks(isLocked(), isLocked()); } 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) @@ -73,7 +83,7 @@ namespace kiwi m_outline.setBounds(getLocalBounds()); } - void ObjectFrame::updateBounds(bool animate) + void ObjectFrame::updateBoundsFromModel(bool animate) { model::Object const& model = getModel(); @@ -81,10 +91,13 @@ namespace kiwi { const juce::Point origin = m_patcher_view.getOriginPosition(); - const juce::Rectangle object_bounds(model.getX() + origin.getX(), - model.getY() + origin.getY(), - model.getWidth(), - model.getHeight()); + 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()); @@ -102,33 +115,81 @@ namespace kiwi } void ObjectFrame::paintOverChildren (juce::Graphics& g) - { - if(!isLocked()) + { + const bool locked = isLocked(); + if(!locked) { - g.setColour(findColour(ObjectFrame::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()); + } + + juce::ComponentBoundsConstrainer* ObjectFrame::getBoundsConstrainer() const + { + return m_object_view.get(); + } + + int ObjectFrame::getResizingFlags() const + { + return m_object_view->getResizingFlags(); + } void ObjectFrame::mouseDown(juce::MouseEvent const& e) { @@ -163,11 +224,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)) { @@ -179,9 +243,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)) { @@ -197,12 +261,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; @@ -216,14 +278,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); @@ -237,19 +291,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(); } @@ -259,7 +331,7 @@ namespace kiwi const bool animate = (ctrl == flip::Controller::UNDO || ctrl == flip::Controller::EXTERNAL); - updateBounds(animate); + updateBoundsFromModel(animate); repaint(); } @@ -274,9 +346,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,63 +368,16 @@ 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(); } } 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.setVisible(true); - - m_outline.setResizeColour(findColour(ObjectFrame::ColourIds::Selection)); - - if (distant_selected) - { - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::SelectionDistant)); - } - else if (other_view_selected) - { - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::SelectionOtherView)); - } - else - { - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::Selection)); - } - } - else if(distant_selected) - { - m_outline.setVisible(true); - m_outline.setResizeColour(findColour(ObjectFrame::ColourIds::SelectionDistant)); - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::SelectionDistant)); - } - else if (other_view_selected) - { - m_outline.setVisible(true); - m_outline.setResizeColour(findColour(ObjectFrame::ColourIds::SelectionOtherView)); - m_outline.setInnerColour(findColour(ObjectFrame::ColourIds::SelectionOtherView)); - } - else - { - m_outline.setVisible(false); - } + { + m_outline.setVisible(isSelected()); } void ObjectFrame::selectionChanged() @@ -364,14 +387,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 @@ -401,25 +426,39 @@ namespace kiwi juce::Rectangle ObjectFrame::getInletLocalBounds(const size_t index) const { - juce::Rectangle inlet_bounds = m_object_view->getBounds(); - - 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) - { - if(m_inlets == 1 && index == 0) + 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()); + rect.setBounds(inlet_bounds.getX(), + inlet_bounds.getY(), + pin_width, getPinHeight()); } - - if(m_inlets > 1) + else if(ninlets > 1) { - const double ratio = (inlet_bounds.getWidth() - getPinWidth()) / (double)(m_inlets - 1); - rect.setBounds(inlet_bounds.getX() + ratio * index, inlet_bounds.getY(), - getPinWidth(), getPinHeight()); + const double ratio = (inlet_bounds.getWidth() - pin_width) / (double)(ninlets - 1); + rect.setBounds(inlet_bounds.getX() + ratio * index, + inlet_bounds.getY(), + pin_width, getPinHeight()); } } @@ -427,29 +466,40 @@ 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; - - if(m_outlets > 0 && index < m_outlets) - { - if(m_outlets == 1 && index == 0) + { + juce::Rectangle rect; + 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(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()); + pin_width, getPinHeight()); } } @@ -471,23 +521,13 @@ 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), - m_resize_thickness(resize_thickness), - m_inner_thickness(inner_thickness), - m_corners(), - m_borders(), - m_resize_colour(), - m_inner_colour() - { - } + ObjectFrame::Outline::Outline(int resize_length, int resize_thickness) + : m_resize_length(resize_length) + , m_resize_thickness(resize_thickness) + {} ObjectFrame::Outline::~Outline() - { - } + {} int ObjectFrame::Outline::getBorderThickness() const { @@ -501,9 +541,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(), @@ -542,39 +582,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) @@ -620,39 +635,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); - - drawCorner(g, Border::Left | Border::Top); - drawCorner(g, Border::Top | Border::Right); - drawCorner(g, Border::Right | 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; + g.drawRect(outline_bounds.reduced(thickness*0.75), thickness*0.25); + + /* // 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 fd34e2b7..ceaf9348 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectFrame.h @@ -1,261 +1,258 @@ -/* - ============================================================================== - - 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 - - ============================================================================== - */ +/* + ============================================================================== + + 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. + void mouseDrag(juce::MouseEvent const& e) override final; + + //! @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 + + struct Outline : public juce::Component + { + public: // classes + + enum Border : int + { + Top = 1 << 0, + Bottom = 1 << 1, + Left = 1 << 2, + Right = 1 << 3 + }; + + 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; + }; +} -#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: // 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. - 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; - - 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 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 updateBounds(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; - }; -} diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp index acf2f4c0..11cbc51b 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.cpp @@ -33,19 +33,14 @@ 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_master(this, [](ObjectView*){}) + , m_resizing_flags(HitTester::Border::None) { - 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::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::Active, juce::Colour(0xff21ba90)); + object_model.addListener(*this); + canGrowHorizontally(true); + canGrowVertically(true); } ObjectView::~ObjectView() @@ -96,8 +91,60 @@ 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) + { + // 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 d5962419..fdaa3be4 100644 --- a/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h +++ b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ObjectView.h @@ -32,25 +32,31 @@ 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 juce::ComponentBoundsConstrainer + , public model::Object::Listener { public: // classes enum ColourIds - { - 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 @@ -68,9 +74,28 @@ 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 Called every time a patcher is locked or unlocked. + 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; @@ -84,7 +109,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); @@ -107,9 +132,9 @@ namespace kiwi private: // members - model::Object& m_model; - int m_border_size; - std::shared_ptr m_master; + model::Object& m_model; + 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 4f3078cd..ba6ce920 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)); @@ -106,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_ToggleView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_Objects/KiwiApp_ToggleView.cpp index 7ceac3bf..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() @@ -67,33 +69,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_PatcherComponent.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherComponent.cpp index 8450e715..507ea555 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) { @@ -85,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); } @@ -93,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); @@ -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() @@ -383,9 +415,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); @@ -393,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; }; // ================================================================================ // diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherManager.cpp index 9b571f98..2d71f8b9 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) @@ -82,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()); } } @@ -94,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(); @@ -116,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) @@ -216,70 +232,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() @@ -292,7 +326,7 @@ namespace kiwi return m_document.root(); } - bool PatcherManager::isRemote() const noexcept + bool PatcherManager::isConnected() const noexcept { return m_socket.isConnected(); } @@ -345,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() @@ -400,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 { @@ -435,7 +471,7 @@ namespace kiwi it = user.removeView(*it); } - model::DocumentManager::commit(patcher); + model::DocumentManager::commit(patcher); } bool PatcherManager::askAllWindowsToClose() @@ -514,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) @@ -600,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 a2ce396c..038b7c6a 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(); @@ -91,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. @@ -133,27 +137,37 @@ 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); //! @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 +216,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; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherView.cpp index 4c9d4901..232fc696 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); @@ -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; @@ -135,7 +136,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); } } } @@ -646,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)) @@ -728,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 @@ -990,7 +991,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()) @@ -1000,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; @@ -1065,8 +1070,6 @@ namespace kiwi } } - updateParameters(patcher); - if(view.removed()) {} } @@ -1366,10 +1369,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 +1531,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 +1549,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; @@ -1568,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); + } + } } } @@ -1850,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: @@ -1863,7 +1869,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 +1880,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/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>; diff --git a/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp b/Client/Source/KiwiApp_Patcher/KiwiApp_PatcherViewMouseHandler.cpp index 94abd1d3..f90d094a 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(); @@ -104,7 +106,7 @@ namespace kiwi break; } - case Action::MoveObject: + case Action::MoveObjects: { KiwiApp::commandStatusChanged(); @@ -189,7 +191,7 @@ namespace kiwi } break; } - case Action::ResizeObject: + case Action::ResizeObjects: { m_direction = getResizeDirection(hit_tester); @@ -216,63 +218,6 @@ namespace kiwi } } - void MouseHandler::applyNewBounds(model::Object & object_model, juce::Rectangle new_bounds, double ratio) const - { - juce::ComponentBoundsConstrainer bounds_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()); - } - - 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()); - } - - if (object_model.getRatio() != 0) - { - bounds_constrainer.setFixedAspectRatio(1 / object_model.getRatio()); - } - else if (ratio != 0) - { - bounds_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 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()); - } - void MouseHandler::continueAction(juce::MouseEvent const& e) { HitTester& hit_tester = m_patcher_view.m_hittester; @@ -286,7 +231,7 @@ namespace kiwi m_patcher_view.copySelectionToClipboard(); m_patcher_view.pasteFromClipboard({0, 0}); - startAction(Action::MoveObject, e); + startAction(Action::MoveObjects, e); } break; } @@ -342,7 +287,7 @@ namespace kiwi break; } - case Action::MoveObject: + case Action::MoveObjects: { if (m_patcher_view.isAnyObjectSelected()) { @@ -362,7 +307,7 @@ namespace kiwi { if (hit_tester.objectTouched() && e.getMouseDownPosition() != e.getPosition()) { - startAction(Action::MoveObject, e); + startAction(Action::MoveObjects, e); } break; } @@ -370,7 +315,7 @@ namespace kiwi { if (hit_tester.objectTouched() && e.getMouseDownPosition() != e.getPosition()) { - startAction(Action::MoveObject, e); + startAction(Action::MoveObjects, e); } break; } @@ -378,51 +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; - - 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) - { - 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())); - } + auto& document = patcher_model.entity().use(); + const auto object_ref = bounds_it.first; - if (m_direction & Direction::Down) + if(auto model = document.get(object_ref)) { - new_bounds.setBottom(std::max(new_bounds.getBottom() + delta.getY(), new_bounds.getY())); + if(auto* box = m_patcher_view.getObject(*model)) + { + resizeModelObjectBounds(*model, *box, + bounds_it.second, + delta, e.mods.isShiftDown()); + } } - - auto& document = m_patcher_view.m_patcher_model.entity().use(); - - applyNewBounds(*document.get(bounds_it.first), new_bounds, ratio); } - 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; } @@ -487,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: @@ -505,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: @@ -584,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()) @@ -675,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); + + const bool grow_y = ((resize_flags & HitTester::Border::Top) + || resize_flags & HitTester::Border::Bottom); - if ((border & HitTester::Border::Bottom) && object_model.hasFlag(model::ObjectClass::Flag::ResizeHeight)) + 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; @@ -706,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) : @@ -795,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/KiwiEngine/KiwiEngine_Objects/KiwiEngine_Objects.h b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_Objects.h index cb719e42..75aef02b 100644 --- a/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_Objects.h +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_Objects.h @@ -85,3 +85,5 @@ #include #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..514cd584 --- /dev/null +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfPlayTilde.cpp @@ -0,0 +1,432 @@ +/* + ============================================================================== + + 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 // + // ================================================================================ // + + //! @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) + { + 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(end_ms * 0.001 * sf_samplerate, sf_frames); + } + + int64_t length = end_sample - start_sample; + 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()); + + 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 : 0; + } + + 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("sf.play~", &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()) + { + 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() == "play") + { + if(args.size() > 1 && args[1].isNumber()) + { + 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") + { + if(args.size() == 2 && args[1].isNumber()) + { + m_player.setLoop(args[1].getInt()); + } + else + { + warning("sf.play~: 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("sf.play~: is not an absolute path"); + return false; + } + + if(file.isDirectory()) + { + warning("sf.play~: invalid file path"); + return false; + } + + if(!file.exists()) + { + warning("sf.play~: 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/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.cpp b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.cpp new file mode 100644 index 00000000..c914523b --- /dev/null +++ b/Modules/KiwiEngine/KiwiEngine_Objects/KiwiEngine_SfRecordTilde.cpp @@ -0,0 +1,369 @@ +/* + ============================================================================== + + 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 RECORDER // + // ================================================================================ // + + //! @note the SoundFileRecorder class is based on the juce Demo App + SoundFileRecorder::SoundFileRecorder() + : m_background_thread("SoundFile Recorder Thread") + { + m_background_thread.startThread(); + } + + SoundFileRecorder::~SoundFileRecorder() + { + stop(); + } + + bool SoundFileRecorder::start(const juce::File& file) + { + stop(); + + if (m_sample_rate <= 0) + return false; // abort + + // Create an OutputStream to write to our destination file... + file.deleteFile(); + std::unique_ptr 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; + + 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 + + // (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.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); + 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("sf.record~", &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[1].isString()) + { + openFile(juce::File(args[1].getString())); + } + else + { + error("sf.record~: bad argument for open"); + } + } + else + { + openFileDialog(); + } + } + else if (args[0].isNumber()) + { + 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") + { + 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("sf.record~: is not an absolute path"); + return false; + } + + if(file.isDirectory()) + { + warning("sf.record~: invalid file path"); + return false; + } + + // is a file + + if(!file.hasWriteAccess()) + { + warning("sf.record~: 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("sf.record~: can't create file \"" + path.toStdString() + "\""); + return false; + } + } + else + { + warning("sf.record~: 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_Converters/KiwiModel_Converter.cpp b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp index 1b1c4457..95d4b06d 100755 --- a/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp +++ b/Modules/KiwiModel/KiwiModel_Converters/KiwiModel_Converter.cpp @@ -24,7 +24,10 @@ #include #include -#include +#include +#include +#include +#include namespace kiwi { namespace model { @@ -32,152 +35,76 @@ 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(); + 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; + } + }; + +}} 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; + } + }; +}} diff --git a/Modules/KiwiModel/KiwiModel_DataModel.cpp b/Modules/KiwiModel/KiwiModel_DataModel.cpp index 9e82d92b..04275466 100644 --- a/Modules/KiwiModel/KiwiModel_DataModel.cpp +++ b/Modules/KiwiModel/KiwiModel_DataModel.cpp @@ -96,7 +96,9 @@ namespace kiwi model::SwitchTilde::declare(); model::Float::declare(); model::ClipTilde::declare(); - model::Clip::declare(); + model::Clip::declare(); + model::SfPlayTilde::declare(); + model::SfRecordTilde::declare(); } void DataModel::init(std::function declare_object) 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" 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; + }; }} 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; 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 8ca1f580..67c17294 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Comment.cpp @@ -55,24 +55,21 @@ 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) { 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_Objects.h b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h index 4de18ba2..293d8350 100755 --- a/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h +++ b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_Objects.h @@ -84,4 +84,7 @@ #include #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..9a285e52 --- /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("sf.play~", &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, 0 to stop, 1 to start"; + } + } + 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/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.cpp b/Modules/KiwiModel/KiwiModel_Objects/KiwiModel_SfRecordTilde.cpp new file mode 100755 index 00000000..55f7ab7d --- /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("sf.record~", &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 "Audio Channel 1, open a file and start/stop recording"; + } + 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/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); diff --git a/Modules/KiwiServer/KiwiServer_Server.cpp b/Modules/KiwiServer/KiwiServer_Server.cpp index 54d0e0e3..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,8 +472,8 @@ namespace kiwi m_backend_file.create(); } - m_logger.log("saving session : " + hexadecimal_convert(m_identifier) - + " in file : " + m_backend_file.getFileName()); + 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; 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" 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; } 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 diff --git a/ThirdParty/kiwi-node-server b/ThirdParty/kiwi-node-server new file mode 160000 index 00000000..74c9c851 --- /dev/null +++ b/ThirdParty/kiwi-node-server @@ -0,0 +1 @@ +Subproject commit 74c9c851fa86357c366f59d2d8ae99e0831c4857 diff --git a/docs/ressources/pathces/first-patch.kiwi b/docs/ressources/patchs/first-patch.kiwi similarity index 100% rename from docs/ressources/pathces/first-patch.kiwi rename to docs/ressources/patchs/first-patch.kiwi diff --git a/docs/ressources/patchs/kiwi-helps.zip b/docs/ressources/patchs/kiwi-helps.zip new file mode 100644 index 00000000..eea84a54 Binary files /dev/null and b/docs/ressources/patchs/kiwi-helps.zip differ diff --git a/docs/ressources/patchs/kiwi-helps/help-adc~.kiwi b/docs/ressources/patchs/kiwi-helps/help-adc~.kiwi new file mode 100755 index 00000000..f049b168 Binary files /dev/null and b/docs/ressources/patchs/kiwi-helps/help-adc~.kiwi differ 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 00000000..be448784 Binary files /dev/null and b/docs/ressources/patchs/kiwi-helps/help-dac~.kiwi differ 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 00000000..f92a47cb Binary files /dev/null and b/docs/ressources/patchs/kiwi-helps/help-delaysimple~.kiwi differ diff --git a/docs/ressources/pathces/help/help-line~.kiwi b/docs/ressources/patchs/kiwi-helps/help-line~.kiwi similarity index 100% rename from docs/ressources/pathces/help/help-line~.kiwi rename to docs/ressources/patchs/kiwi-helps/help-line~.kiwi diff --git a/docs/ressources/pathces/help/help-noise~.kiwi b/docs/ressources/patchs/kiwi-helps/help-noise~.kiwi similarity index 100% rename from docs/ressources/pathces/help/help-noise~.kiwi rename to docs/ressources/patchs/kiwi-helps/help-noise~.kiwi diff --git a/docs/ressources/pathces/help/help-osc~.kiwi b/docs/ressources/patchs/kiwi-helps/help-osc~.kiwi similarity index 100% rename from docs/ressources/pathces/help/help-osc~.kiwi rename to docs/ressources/patchs/kiwi-helps/help-osc~.kiwi diff --git a/docs/ressources/pathces/help/help-pack.kiwi b/docs/ressources/patchs/kiwi-helps/help-pack.kiwi similarity index 100% rename from docs/ressources/pathces/help/help-pack.kiwi rename to docs/ressources/patchs/kiwi-helps/help-pack.kiwi diff --git a/docs/ressources/pathces/help/help-phasor~.kiwi b/docs/ressources/patchs/kiwi-helps/help-phasor~.kiwi similarity index 100% rename from docs/ressources/pathces/help/help-phasor~.kiwi rename to docs/ressources/patchs/kiwi-helps/help-phasor~.kiwi diff --git a/docs/ressources/pathces/help/help-sah~.kiwi b/docs/ressources/patchs/kiwi-helps/help-sah~.kiwi similarity index 100% rename from docs/ressources/pathces/help/help-sah~.kiwi rename to docs/ressources/patchs/kiwi-helps/help-sah~.kiwi diff --git a/docs/ressources/pathces/help/help-sig~.kiwi b/docs/ressources/patchs/kiwi-helps/help-sig~.kiwi similarity index 100% rename from docs/ressources/pathces/help/help-sig~.kiwi rename to docs/ressources/patchs/kiwi-helps/help-sig~.kiwi diff --git a/docs/ressources/pathces/help/help-trigger.kiwi b/docs/ressources/patchs/kiwi-helps/help-trigger.kiwi similarity index 100% rename from docs/ressources/pathces/help/help-trigger.kiwi rename to docs/ressources/patchs/kiwi-helps/help-trigger.kiwi diff --git a/docs/ressources/patchs/kiwi-helps/sf.play~.help.kiwi b/docs/ressources/patchs/kiwi-helps/sf.play~.help.kiwi new file mode 100644 index 00000000..009e4afd Binary files /dev/null and b/docs/ressources/patchs/kiwi-helps/sf.play~.help.kiwi differ diff --git a/docs/ressources/patchs/kiwi-helps/sf.record~.help.kiwi b/docs/ressources/patchs/kiwi-helps/sf.record~.help.kiwi new file mode 100644 index 00000000..6a02eba7 Binary files /dev/null and b/docs/ressources/patchs/kiwi-helps/sf.record~.help.kiwi differ diff --git a/docs/ressources/pathces/tutorials/tutorials.zip b/docs/ressources/patchs/kiwi-tutorials.zip similarity index 83% rename from docs/ressources/pathces/tutorials/tutorials.zip rename to docs/ressources/patchs/kiwi-tutorials.zip index 9ccd9193..8023cfa6 100644 Binary files a/docs/ressources/pathces/tutorials/tutorials.zip and b/docs/ressources/patchs/kiwi-tutorials.zip differ diff --git a/docs/ressources/pathces/tutorials/flanger.kiwi b/docs/ressources/patchs/kiwi-tutorials/flanger.kiwi similarity index 100% rename from docs/ressources/pathces/tutorials/flanger.kiwi rename to docs/ressources/patchs/kiwi-tutorials/flanger.kiwi diff --git a/docs/ressources/pathces/tutorials/fm-synthesis.kiwi b/docs/ressources/patchs/kiwi-tutorials/fm-synthesis.kiwi similarity index 100% rename from docs/ressources/pathces/tutorials/fm-synthesis.kiwi rename to docs/ressources/patchs/kiwi-tutorials/fm-synthesis.kiwi diff --git a/docs/ressources/pathces/tutorials/overlapped-pitchshifter.kiwi b/docs/ressources/patchs/kiwi-tutorials/overlapped-pitchshifter.kiwi similarity index 100% rename from docs/ressources/pathces/tutorials/overlapped-pitchshifter.kiwi rename to docs/ressources/patchs/kiwi-tutorials/overlapped-pitchshifter.kiwi diff --git a/docs/ressources/pathces/tutorials/simple-pitchshifter.kiwi b/docs/ressources/patchs/kiwi-tutorials/simple-pitchshifter.kiwi similarity index 100% rename from docs/ressources/pathces/tutorials/simple-pitchshifter.kiwi rename to docs/ressources/patchs/kiwi-tutorials/simple-pitchshifter.kiwi diff --git a/docs/ressources/pathces/help/help-adc~.kiwi b/docs/ressources/pathces/help/help-adc~.kiwi deleted file mode 100755 index 2551dcd9..00000000 Binary files a/docs/ressources/pathces/help/help-adc~.kiwi and /dev/null differ diff --git a/docs/ressources/pathces/help/help-dac~.kiwi b/docs/ressources/pathces/help/help-dac~.kiwi deleted file mode 100755 index fd157f60..00000000 Binary files a/docs/ressources/pathces/help/help-dac~.kiwi and /dev/null differ diff --git a/docs/ressources/pathces/help/help-delaysimple~.kiwi b/docs/ressources/pathces/help/help-delaysimple~.kiwi deleted file mode 100755 index dc0dea7b..00000000 Binary files a/docs/ressources/pathces/help/help-delaysimple~.kiwi and /dev/null differ diff --git a/docs/ressources/pathces/help/help.zip b/docs/ressources/pathces/help/help.zip deleted file mode 100644 index 7f1b8b40..00000000 Binary files a/docs/ressources/pathces/help/help.zip and /dev/null differ diff --git a/docs/software/getting-started.md b/docs/software/getting-started.md index 31e029b8..3571aec9 100644 --- a/docs/software/getting-started.md +++ b/docs/software/getting-started.md @@ -46,7 +46,7 @@ To connect to an existing user account, click on login in the menu 'Account'. A At this point, you can either join a shared patch using the Document Browser window or open a local patch. In this section we will demonstrate how to open and manipulate a local patch. Instructions on how to collaborate will be given later. -Pease click on this 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 f6f464ec..e2600093 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 @@ -72,3 +72,5 @@ A list of help patches describing how each object works is 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: 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] :