Skip to content

Commit

Permalink
PR #7781 from Nir: Wrap Viewer/DQT error pop up text
Browse files Browse the repository at this point in the history
  • Loading branch information
maloel committed Dec 23, 2020
2 parents b0675a0 + f407ddb commit e27697a
Show file tree
Hide file tree
Showing 8 changed files with 345 additions and 3 deletions.
10 changes: 8 additions & 2 deletions common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,18 @@ set(COMMON_SRC
"${CMAKE_CURRENT_LIST_DIR}/reflectivity/reflectivity.cpp"
)

set(UTILITIES
set(UTILITIES_FILES
"${CMAKE_CURRENT_LIST_DIR}/utilities/number/stabilized-value.h"
"${CMAKE_CURRENT_LIST_DIR}/utilities/string/trim-newlines.h"
"${CMAKE_CURRENT_LIST_DIR}/utilities/string/split.h"
"${CMAKE_CURRENT_LIST_DIR}/utilities/imgui/wrap.h"
"${CMAKE_CURRENT_LIST_DIR}/utilities/imgui/wrap.cpp"
)

set(COMMON_SRC
${COMMON_SRC}
${SW_UPDATE_FILES}
${REFLECTIVITY_FILES}
${UTILITIES})
${UTILITIES_FILES}
)

122 changes: 122 additions & 0 deletions common/utilities/imgui/wrap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2020 Intel Corporation. All Rights Reserved.

#include <string>
#include <vector>
#include <sstream>
#include "wrap.h"
#include "../common/utilities/string/split.h"
#include "../third-party/imgui/imgui.h"

namespace utilities {
namespace imgui {

void trim_leading_spaces( std::string &remaining_paragraph )
{
auto non_space_index = remaining_paragraph.find_first_not_of( ' ' );

if( non_space_index != std::string::npos )
{
// Remove trailing spaces
remaining_paragraph = remaining_paragraph.substr( non_space_index );
}
}

std::string wrap_paragraph( const std::string & paragraph, int wrap_pixels_width )
{
float space_width = ImGui::CalcTextSize( " " ).x; // Calculate space width in pixels
std::string wrapped_line; // The line that is wrapped in this iteration
std::string wrapped_paragraph; // The output wrapped paragraph
auto remaining_paragraph
= paragraph; // Holds the remaining unwrapped part of the input paragraph

// Handle a case when the paragraph starts with spaces
trim_leading_spaces(remaining_paragraph);

auto next_word = remaining_paragraph.substr(
0,
remaining_paragraph.find( ' ' ) ); // The next word to add to the current line
bool first_word = true;

while( ! next_word.empty() )
{
float next_x = 0.0f;
// If this is the first word we try to place it first in line,
// if not we concatenate it to the last word after adding a space
if( ! first_word )
{
next_x = ImGui::CalcTextSize( wrapped_line.c_str() ).x + space_width;
}

if( next_x + ImGui::CalcTextSize( next_word.c_str() ).x <= wrap_pixels_width )
{
if( ! first_word )
wrapped_line += " "; // First word should not start with " "
wrapped_line += next_word;
}
else
{ // Current line cannot feat new word so we wrap the line and
// start building the new line

wrapped_paragraph += wrapped_line; // copy line build by now
if( ! first_word )
wrapped_paragraph += '\n'; // break the previous line if exist
wrapped_line = next_word; // add next work to new line
}

first_word = false;

// If we have more characters left other then the current word, prepare inputs for next
// iteration, If not add the current line built so far to the output and finish current
// line wrap.
if( remaining_paragraph.size() > next_word.size() )
{
remaining_paragraph = remaining_paragraph.substr( next_word.size() + 1 );

// Handle a case when the paragraph starts with spaces
trim_leading_spaces(remaining_paragraph);

next_word = remaining_paragraph.substr( 0, remaining_paragraph.find( ' ' ) );

// If no more words exist, copy the current wrapped line to output and stop
if( next_word.empty() )
{
wrapped_paragraph += wrapped_line;
break;
}
}
else
{
wrapped_paragraph += wrapped_line;
break;
}
}

return wrapped_paragraph;
}

std::string wrap( const std::string & text, int wrap_pixels_width )
{
// Do not wrap if the wrap is smaller then 32 pixels (~2 characters on font 16)
if( wrap_pixels_width < 32 )
return text;

// Split text into paragraphs
auto paragraphs_vector = string::split( text, '\n' );

std::string wrapped_text;
size_t line_number = 1;
// Wrap each line according to the requested wrap width
for( auto paragraph : paragraphs_vector )
{
wrapped_text += wrap_paragraph( paragraph, wrap_pixels_width );
// Each paragraph except the last one ends with a new line
if( line_number++ != paragraphs_vector.size() )
wrapped_text += '\n';
}

return wrapped_text;
}

} // namespace imgui
} // namespace utilities
23 changes: 23 additions & 0 deletions common/utilities/imgui/wrap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2020 Intel Corporation. All Rights Reserved.

#pragma once

namespace utilities {
namespace imgui {

// Wrap text according to input width
// - Input: - text as a string
// - wrapping width
// - Output: - on success - wrapped text
// - on failure - an empty string
// Example:
// Input:
// this is the first line\nthis is the second line\nthis is the last line , wrap_width = 150 [pixels]
// Output:
// this is the\nfirst line\nthis is the\nsecond line\nthis is the last\nline
// Note: If the paragraph contain multiple spaces, it will be trimmed into a single space.
std::string wrap( const std::string & text, int wrap_pixels_width );

} // namespace imgui
} // namespace utilities
38 changes: 38 additions & 0 deletions common/utilities/string/split.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2020 Intel Corporation. All Rights Reserved.

#pragma once

#include <string>
#include <vector>
#include <sstream>

namespace utilities {
namespace string {


// Split input text to vector of strings according to input delimiter
// - Input: string to be split
// - delimiter
// - Output: a vector of strings
// Example:
// Input:
// Text: This is the first line\nThis is the second line\nThis is the last line
// Delimiter : '\n'
// Output:
// [0] this is the first line
// [1] this is the second line
// [2] this is the last line
inline std::vector< std::string > split( const std::string & str , char delimiter)
{
auto result = std::vector< std::string >{};
auto ss = std::stringstream{ str };

for( std::string line; std::getline( ss, line, delimiter); )
result.push_back( line );

return result;
}

} // namespace string
} // namespace utilities
19 changes: 18 additions & 1 deletion common/viewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

#define ARCBALL_CAMERA_IMPLEMENTATION
#include <arcball_camera.h>
#include "../common/utilities/string/trim-newlines.h"
#include "../common/utilities/imgui/wrap.h"

namespace rs2
{
Expand Down Expand Up @@ -1068,9 +1070,24 @@ namespace rs2
auto custom_command = [&]()
{
auto msg = _active_popups.front().message;

// Wrap the text to feet the error pop-up window
std::string wrapped_msg;
try
{
auto trimmed_msg = utilities::string::trim_newlines(msg);
wrapped_msg = utilities::imgui::wrap(trimmed_msg, 500);
}
catch (...)
{
wrapped_msg = msg; // Revert to original text on wrapping failure
not_model->output.add_log(RS2_LOG_SEVERITY_WARN, __FILE__, __LINE__,
to_string() << "Wrapping of error message text failed!");
}

ImGui::Text("RealSense error calling:");
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, regular_blue);
ImGui::InputTextMultiline("##error", const_cast<char*>(msg.c_str()),
ImGui::InputTextMultiline("##error", const_cast<char*>(wrapped_msg.c_str()),
msg.size() + 1, { 500,95 }, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_ReadOnly);
ImGui::PopStyleColor();

Expand Down
15 changes: 15 additions & 0 deletions unit-tests/utilities/imgui/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2020 Intel Corporation. All Rights Reserved.

#define CATCH_CONFIG_MAIN

#include "../../catch.h"

#include <easylogging++.h>
#ifdef BUILD_SHARED_LIBS
// With static linkage, ELPP is initialized by librealsense, so doing it here will
// create errors. When we're using the shared .so/.dll, the two are separate and we have
// to initialize ours if we want to use the APIs!
INITIALIZE_EASYLOGGINGPP
#endif

90 changes: 90 additions & 0 deletions unit-tests/utilities/imgui/test-wrap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2020 Intel Corporation. All Rights Reserved.

//#cmake:add-file ../../../common/utilities/imgui/wrap.h
//#cmake:add-file ../../../common/utilities/imgui/wrap.cpp
//#cmake:add-file ../../../third-party/imgui/imgui.h

#include "common.h"
#include "../../../common/utilities/imgui/wrap.h"
#include "../../../third-party/imgui/imgui.h"

namespace ImGui {
// Mock ImGui function for test
// all characters are at length of 10 pixels except 'i' and 'j' which is 5 pixels
ImVec2 CalcTextSize( const char * text,
const char * text_end,
bool hide_text_after_double_hash,
float wrap_width )
{
if (!text) return ImVec2(0.0f, 0.0f);

float total_size = 0.0f;
while( *text )
{
if( *text == 'i' || *text == 'j' )
total_size += 5.0f;
else
total_size += 10.0f;
text++;
}
return ImVec2( total_size, 0.0f );
}
} // namespace ImGui

using namespace utilities::imgui;

TEST_CASE( "wrap-text", "[string]" )
{
// Verify illegal inputs return empty string
CHECK( wrap( "", 0 ) == "" );
CHECK( wrap( "", 10 ) == "" );
CHECK( wrap( "abc", 0 ) == "abc" );
CHECK( wrap( "abc", 10 ) == "abc" );
CHECK( wrap( "abc\nabc", 0 ) == "abc\nabc" );
CHECK( wrap( "abc abc", 5 ) == "abc abc" );
CHECK( wrap( "", 10 ) == "" );

// Verify no wrap if not needed
CHECK( wrap( "abc", 100 ) == "abc" );
CHECK( wrap( "abc\nabc", 100 ) == "abc\nabc" );
CHECK( wrap( "abc abc a", 100 ) == "abc abc a" );

// No wrapping possible, copy line until first space and continue wrapping
CHECK( wrap( "abcdefgh", 40 ) == "abcdefgh" );
CHECK( wrap( "aabbccddff das ds fr", 50 ) == "aabbccddff\ndas\nds fr" );
CHECK( wrap( "das aabbccddff ds fr", 50 ) == "das\naabbccddff\nds fr" );

// Exact wrap position test
CHECK( wrap( "abcde abcde", 50 ) == "abcde\nabcde" );

// Short letters test
CHECK(wrap("aaaa bbbb cc", 100) == "aaaa bbbb\ncc");
// i and j are only 5 pixels so we get more characters inside the wrap
CHECK(wrap("aaaa iijj cc", 100) == "aaaa iijj cc");


// Check wrapping of 3 paragraphs
CHECK( wrap( "this is the first line\nthis is the second line\nthis is the last line", 150 )
== "this is the\nfirst line\nthis is the\nsecond line\nthis is the last\nline" );

CHECK( wrap( "this is the first line\nthis is the second line\nthis is the last line", 60 )
== "this is\nthe\nfirst\nline\nthis is\nthe\nsecond\nline\nthis is\nthe\nlast\nline" );

// Spaces checks
CHECK(wrap("ab cd ", 32) == "ab\ncd"); // Ending spaces
CHECK(wrap("ab cd", 32) == "ab\ncd"); // Middle spaces
CHECK(wrap(" ab cd ", 32) == "ab\ncd"); // Mixed multiple spaces
CHECK(wrap(" ab cd ", 32) == "ab\ncd"); // Mixed multiple spaces
CHECK(wrap(" ab ", 33) == "ab"); // Mixed multiple spaces
CHECK(wrap("ab ", 33) == "ab"); // Ending multiple spaces
CHECK(wrap(" ab", 33) == "ab");

// Only spaces
CHECK(wrap(" ", 33) == "");
CHECK(wrap(" ", 33) == "");

CHECK(wrap("ab cd ", 100) == "ab cd");
CHECK(wrap("ab cd", 100) == "ab cd"); // Known corner case - we trim multiple spaces

}
31 changes: 31 additions & 0 deletions unit-tests/utilities/string/test-split.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2020 Intel Corporation. All Rights Reserved.

//#cmake:add-file ../../../common/utilities/string/split.h

#include "common.h"
#include "../../../common/utilities/string/split.h"

using namespace utilities::string;

TEST_CASE("split_string_by_newline", "[string]")
{
// split size check
CHECK(split("" , '\n').size() == 0);
CHECK(split("abc", '\n').size() == 1);
CHECK(split("abc\nabc", '\n').size() == 2);
CHECK(split("a\nbc\nabc", '\n').size() == 3);
CHECK(split("a\nbc\nabc\n", '\n').size() == 3);
CHECK(split("1-12-123-1234", '-').size() == 4);

CHECK(split("a\nbc\nabc", '\n')[0] == "a");
CHECK(split("a\nbc\nabc", '\n')[1] == "bc");
CHECK(split("a\nbc\nabc", '\n')[2] == "abc");
CHECK(split("a\nbc\nabc\n", '\n')[2] == "abc");


CHECK(split("1-12-123-1234", '-')[0] == "1");
CHECK(split("1-12-123-1234", '-')[1] == "12");
CHECK(split("1-12-123-1234", '-')[2] == "123");
CHECK(split("1-12-123-1234", '-')[3] == "1234");
}

0 comments on commit e27697a

Please sign in to comment.