Commit 53ecad68 authored by Davis King's avatar Davis King

Added the text_box gui widget.

--HG--
extra : convert_revision : svn%3Afdd8eb12-d10e-0410-9acb-85c331704f74/trunk%402795
parent 84bb80cc
......@@ -435,26 +435,26 @@ namespace dlib
}
// find the start of the given line
typename string::size_type last_pos = pos;
for (typename string::size_type i = first; i <= last && line != 0; ++i)
{
if (str[i] == '\n')
{
--line;
pos += (i - last_pos);
last_pos = pos;
pos = i + 1;
}
}
// now str[pos] == the first character of the start of the line
// that contains the cursor.
const typename string::size_type start_of_line = pos;
long cur_x = f.left_overflow();
// set the current cursor position to where the mouse clicked
while (pos <= last)
{
if (x <= cur_x)
if (x <= cur_x || str[pos] == '\n')
break;
if (is_combining_char(str[pos]) == false &&
......@@ -467,8 +467,10 @@ namespace dlib
if (x <= cur_x)
{
if (pos != first)
if (pos != start_of_line)
{
// we might actually be closer to the previous character
// so check for that and if so then jump us back one.
const long width = f[str[pos-1]].width();
if (x < cur_x - width/2)
--pos;
......
......@@ -903,6 +903,93 @@ namespace dlib
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// text_box styles
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
void text_box_style_default::
draw_text_box (
const canvas& c,
const rectangle& display_rect,
const rectangle& text_rect,
const bool enabled,
const font& mfont,
const ustring& text,
const rectangle& cursor_rect,
const rgb_pixel& text_color,
const rgb_pixel& bg_color,
const bool has_focus,
const bool cursor_visible,
const long highlight_start,
const long highlight_end
) const
{
rectangle area = display_rect.intersect(c);
if (enabled)
{
// first fill our area with the bg_color
fill_rect(c, area,bg_color);
}
else
{
// first fill our area with gray
fill_rect(c, area,rgb_pixel(212,208,200));
}
if (enabled)
mfont.draw_string(c,text_rect,text,text_color, 0, ustring::npos, area);
else
mfont.draw_string(c,text_rect,text,rgb_pixel(128,128,128), 0, ustring::npos, area);
// now draw the highlight if there is any
if (highlight_start <= highlight_end && enabled)
{
const rectangle first_pos = mfont.compute_cursor_rect(text_rect, text, highlight_start);
const rectangle last_pos = mfont.compute_cursor_rect(text_rect, text, highlight_end+1);
const rgb_alpha_pixel color(10, 30, 106, 90);
// if the highlighted text is all on one line
if (first_pos.top() == last_pos.top())
{
fill_rect(c, (first_pos + last_pos).intersect(display_rect), color);
}
else
{
const rectangle min_boundary(display_rect.left()+4, display_rect.top()+4,
display_rect.right()-4, display_rect.bottom()-4);
const rectangle boundary( display_rect.intersect(text_rect) + min_boundary);
rectangle first_row, last_row, middle_rows;
first_row += first_pos;
first_row += point(boundary.right(), first_pos.top());
last_row += last_pos;
last_row += point(boundary.left(), last_pos.bottom());
middle_rows.left() = boundary.left();
middle_rows.right() = boundary.right();
middle_rows.top() = first_row.bottom()+1;
middle_rows.bottom() = last_row.top()-1;
fill_rect(c, first_row.intersect(display_rect), color);
fill_rect(c, middle_rows, color);
fill_rect(c, last_row.intersect(display_rect), color);
}
}
// now draw the cursor if we need to
if (cursor_visible && has_focus && enabled)
{
draw_line(c, point(cursor_rect.left(), cursor_rect.top()),point(cursor_rect.left(), cursor_rect.bottom()), 0, area);
}
}
// ----------------------------------------------------------------------------------------
}
......
......@@ -661,6 +661,82 @@ namespace dlib
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// text_box styles
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
class text_box_style
{
public:
text_box_style()
{
}
virtual ~text_box_style()
{}
virtual unsigned long get_padding (
const font& mfont
) const = 0;
virtual void draw_text_box (
const canvas& c,
const rectangle& display_rect,
const rectangle& text_rect,
const bool enabled,
const font& mfont,
const ustring& text,
const rectangle& cursor_rect,
const rgb_pixel& text_color,
const rgb_pixel& bg_color,
const bool has_focus,
const bool cursor_visible,
const long highlight_start,
const long highlight_end
) const = 0;
};
// ----------------------------------------------------------------------------------------
class text_box_style_default : public text_box_style
{
public:
text_box_style_default()
{
}
scrollable_region_style_default get_scrollable_region_style (
) const { return scrollable_region_style_default(); }
virtual ~text_box_style_default()
{}
virtual unsigned long get_padding (
const font& mfont
) const { return 1; }
virtual void draw_text_box (
const canvas& c,
const rectangle& display_rect,
const rectangle& text_rect,
const bool enabled,
const font& mfont,
const ustring& text,
const rectangle& cursor_rect,
const rgb_pixel& text_color,
const rgb_pixel& bg_color,
const bool has_focus,
const bool cursor_visible,
const long highlight_start,
const long highlight_end
) const;
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// text_field styles
......
......@@ -51,7 +51,7 @@ namespace dlib
- rect == the get_rect() that defines where the button is
ensures
- returns a rectangle that should be invalidated whenever a button
needs to redraw itself. (If you wanted your button style to
needs to redraw itself. (e.g. If you wanted your button style to
draw outside the button then you could return a larger rectangle)
!*/
......@@ -489,6 +489,97 @@ namespace dlib
!*/
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// text_box styles
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
class text_box_style
{
/*!
WHAT THIS OBJECT REPRESENTS
This is an abstract class that defines the interface a
text_box style object must implement.
Note that derived classes must be copyable via
their copy constructors.
!*/
public:
virtual ~text_field_style() {}
scrollable_region_style_type get_scrollable_region_style (
) const;
/*!
ensures
- returns the style of scrollable_region to use for the
text_box.
!*/
virtual unsigned long get_padding (
const font& mfont
) const = 0;
/*!
requires
- the mutex drawable::m is locked
ensures
- returns the number of pixels that separate the text in the text_box
from the edge of the text_box widget itself.
!*/
virtual void draw_text_box (
const canvas& c,
const rectangle& display_rect,
const rectangle& text_rect,
const bool enabled,
const font& mfont,
const ustring& text,
const rectangle& cursor_rect,
const rgb_pixel& text_color,
const rgb_pixel& bg_color,
const bool has_focus,
const bool cursor_visible,
const long highlight_start,
const long highlight_end
) const = 0;
/*!
requires
- the mutex drawable::m is locked
- c == the canvas to draw on
- enabled and mfont are the variables defined in the protected section
- text_rect == the rectangle in which we should draw the given text
of the drawable class.
- display_rect == the rectangle returned by scrollable_region::display_rect()
- text == the current text in the text_box
- cursor_rect == A rectangle of width 1 that represents the current
position of the cursor on the screen.
- text_color == the color of the text to be drawn
- bg_color == the background color of the text field
- has_focus == true if this text field has keyboard input focus
- cursor_visible == true if the cursor should be drawn
- if (highlight_start <= highlight_end) then
- text[highlight_start] though text[highlight_end] should be
highlighted
ensures
- draws the text_box on the canvas c at the location given by text_rect.
(Note that the scroll bars and borders are drawn by the scrollable_region
and therefore the style returned by get_scrollable_region_style()
controls how those appear)
- doesn't draw anything outside display_rect
!*/
};
// ----------------------------------------------------------------------------------------
class text_box_style_default : public text_box_style
{
public:
/*!
This is the default style for text_box objects.
!*/
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// list_box styles
......@@ -604,7 +695,7 @@ namespace dlib
/*!
WHAT THIS OBJECT REPRESENTS
This is an abstract class that defines the interface a
list_box style object must implement.
text_field style object must implement.
Note that derived classes must be copyable via
their copy constructors.
......@@ -647,7 +738,7 @@ namespace dlib
- rect, enabled, and mfont are the variables defined in the protected section
of the drawable class.
- text == the current text in the text_field
- text_rect == the rectanle in which we should draw the given text
- text_rect == the rectangle in which we should draw the given text
- cursor_x == the x coordinate of the cursor relative to the left side
of rect. i.e. the number of pixels that separate the cursor from the
left side of the text_field.
......@@ -661,7 +752,7 @@ namespace dlib
- text[highlight_start] though text[highlight_end] should be
highlighted
ensures
- draws the button on the canvas c at the location given by rect.
- draws the text_field on the canvas c at the location given by rect.
!*/
};
......
......@@ -4675,6 +4675,958 @@ namespace dlib
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// text_field object methods
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
rectangle text_box::
get_text_rect (
) const
{
const unsigned long padding = style->get_padding(*mfont);
rectangle text_rect;
text_rect.set_left(total_rect().left()+padding);
text_rect.set_top(total_rect().top()+padding);
text_rect.set_right(total_rect().right()-padding);
text_rect.set_bottom(total_rect().bottom()-padding);
return text_rect;
}
// ----------------------------------------------------------------------------------------
void text_box::
enable (
)
{
scrollable_region::enable();
right_click_menu.enable();
}
// ----------------------------------------------------------------------------------------
void text_box::
on_cut (
)
{
on_copy();
on_delete_selected();
}
// ----------------------------------------------------------------------------------------
void text_box::
on_copy (
)
{
if (highlight_start <= highlight_end)
{
put_on_clipboard(text_.substr(highlight_start, highlight_end-highlight_start+1));
}
}
// ----------------------------------------------------------------------------------------
void text_box::
on_paste (
)
{
ustring temp_str;
get_from_clipboard(temp_str);
if (highlight_start <= highlight_end)
{
text_ = text_.substr(0,highlight_start) + temp_str +
text_.substr(highlight_end+1,text_.size()-highlight_end-1);
move_cursor(highlight_start+temp_str.size());
highlight_start = 0;
highlight_end = -1;
parent.invalidate_rectangle(rect);
on_no_text_selected();
// send out the text modified event
if (text_modified_handler.is_set())
text_modified_handler();
}
else
{
text_ = text_.substr(0,cursor_pos) + temp_str +
text_.substr(cursor_pos,text_.size()-cursor_pos);
move_cursor(cursor_pos+temp_str.size());
// send out the text modified event
if (temp_str.size() != 0 && text_modified_handler.is_set())
text_modified_handler();
}
adjust_total_rect();
}
// ----------------------------------------------------------------------------------------
void text_box::
on_select_all (
)
{
move_cursor(static_cast<long>(text_.size()));
highlight_start = 0;
highlight_end = static_cast<long>(text_.size()-1);
if (highlight_start <= highlight_end)
on_text_is_selected();
parent.invalidate_rectangle(rect);
}
// ----------------------------------------------------------------------------------------
void text_box::
on_delete_selected (
)
{
if (highlight_start <= highlight_end)
{
text_ = text_.erase(highlight_start,highlight_end-highlight_start+1);
move_cursor(highlight_start);
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
// send out the text modified event
if (text_modified_handler.is_set())
text_modified_handler();
adjust_total_rect();
parent.invalidate_rectangle(rect);
}
}
// ----------------------------------------------------------------------------------------
void text_box::
on_text_is_selected (
)
{
right_click_menu.menu().enable_menu_item(0);
right_click_menu.menu().enable_menu_item(1);
right_click_menu.menu().enable_menu_item(3);
}
// ----------------------------------------------------------------------------------------
void text_box::
on_no_text_selected (
)
{
right_click_menu.menu().disable_menu_item(0);
right_click_menu.menu().disable_menu_item(1);
right_click_menu.menu().disable_menu_item(3);
}
// ----------------------------------------------------------------------------------------
void text_box::
show (
)
{
scrollable_region::show();
right_click_menu.show();
}
// ----------------------------------------------------------------------------------------
void text_box::
disable (
)
{
auto_mutex M(m);
scrollable_region::disable();
t.stop();
has_focus = false;
cursor_visible = false;
right_click_menu.disable();
}
// ----------------------------------------------------------------------------------------
void text_box::
hide (
)
{
auto_mutex M(m);
scrollable_region::hide();
t.stop();
has_focus = false;
cursor_visible = false;
}
// ----------------------------------------------------------------------------------------
void text_box::
adjust_total_rect (
)
{
const unsigned long padding = style->get_padding(*mfont);
unsigned long text_width;
unsigned long text_height;
mfont->compute_size(text_, text_width, text_height);
set_total_rect_size(text_width + padding*2, text_height + padding*2);
}
// ----------------------------------------------------------------------------------------
void text_box::
set_main_font (
const shared_ptr_thread_safe<font>& f
)
{
auto_mutex M(m);
mfont = f;
adjust_total_rect();
right_click_menu.set_rect(display_rect());
}
// ----------------------------------------------------------------------------------------
void text_box::
draw (
const canvas& c
) const
{
scrollable_region::draw(c);
rectangle area = rect.intersect(c);
if (area.is_empty())
return;
const point origin(total_rect().left(), total_rect().top());
style->draw_text_box(c,display_rect(),get_text_rect(), enabled, *mfont, text_,
translate_rect(cursor_rect, origin),
text_color_, bg_color_, has_focus, cursor_visible, highlight_start,
highlight_end);
}
// ----------------------------------------------------------------------------------------
void text_box::
set_text (
const std::string& text
)
{
set_text(convert_mbstring_to_wstring(text));
}
void text_box::
set_text (
const std::wstring& text
)
{
set_text(convert_wstring_to_utf32(text));
}
void text_box::
set_text (
const dlib::ustring& text
)
{
auto_mutex M(m);
// do this to get rid of any reference counting that may be present in
// the std::string implementation.
text_ = text.c_str();
adjust_total_rect();
move_cursor(0);
highlight_start = 0;
highlight_end = -1;
}
// ----------------------------------------------------------------------------------------
const std::string text_box::
text (
) const
{
std::string temp = convert_wstring_to_mbstring(wtext());
return temp;
}
const std::wstring text_box::
wtext (
) const
{
std::wstring temp = convert_utf32_to_wstring(utext());
return temp;
}
const dlib::ustring text_box::
utext (
) const
{
auto_mutex M(m);
// do this to get rid of any reference counting that may be present in
// the dlib::ustring implementation.
dlib::ustring temp = text_.c_str();
return temp;
}
// ----------------------------------------------------------------------------------------
void text_box::
set_size (
unsigned long width,
unsigned long height
)
{
auto_mutex M(m);
scrollable_region::set_size(width,height);
right_click_menu.set_rect(display_rect());
}
// ----------------------------------------------------------------------------------------
void text_box::
set_pos (
long x,
long y
)
{
scrollable_region::set_pos(x,y);
right_click_menu.set_rect(get_text_rect());
}
// ----------------------------------------------------------------------------------------
void text_box::
set_background_color (
const rgb_pixel color
)
{
auto_mutex M(m);
bg_color_ = color;
parent.invalidate_rectangle(rect);
}
// ----------------------------------------------------------------------------------------
const rgb_pixel text_box::
background_color (
) const
{
auto_mutex M(m);
return bg_color_;
}
// ----------------------------------------------------------------------------------------
void text_box::
set_text_color (
const rgb_pixel color
)
{
auto_mutex M(m);
text_color_ = color;
parent.invalidate_rectangle(rect);
}
// ----------------------------------------------------------------------------------------
const rgb_pixel text_box::
text_color (
) const
{
auto_mutex M(m);
return text_color_;
}
// ----------------------------------------------------------------------------------------
void text_box::
on_mouse_move (
unsigned long state,
long x,
long y
)
{
if (!enabled || hidden || !has_focus)
{
return;
}
if (state & base_window::LEFT)
{
if (highlight_start <= highlight_end)
{
if (highlight_start == cursor_pos)
shift_pos = highlight_end + 1;
else
shift_pos = highlight_start;
}
unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y);
if (static_cast<long>(new_pos) != cursor_pos)
{
move_cursor(new_pos);
parent.invalidate_rectangle(rect);
}
}
else if (shift_pos != -1)
{
shift_pos = -1;
}
}
// ----------------------------------------------------------------------------------------
void text_box::
on_mouse_up (
unsigned long btn,
unsigned long,
long ,
long
)
{
if (!enabled || hidden)
return;
if (btn == base_window::LEFT)
shift_pos = -1;
}
// ----------------------------------------------------------------------------------------
void text_box::
on_mouse_down (
unsigned long btn,
unsigned long state,
long x,
long y,
bool double_clicked
)
{
using namespace std;
if (!enabled || hidden || btn != (unsigned long)base_window::LEFT)
return;
if (display_rect().contains(x,y))
{
has_focus = true;
cursor_visible = true;
parent.invalidate_rectangle(rect);
t.start();
if (double_clicked)
{
// highlight the double clicked word
string::size_type first, last;
const ustring ustr = convert_utf8_to_utf32(std::string(" \t\n"));
first = text_.substr(0,cursor_pos).find_last_of(ustr.c_str());
last = text_.find_first_of(ustr.c_str(),cursor_pos);
long f = static_cast<long>(first);
long l = static_cast<long>(last);
if (first == string::npos)
f = -1;
if (last == string::npos)
l = static_cast<long>(text_.size());
++f;
--l;
move_cursor(l+1);
highlight_start = f;
highlight_end = l;
on_text_is_selected();
}
else
{
if (state & base_window::SHIFT)
{
if (highlight_start <= highlight_end)
{
if (highlight_start == cursor_pos)
shift_pos = highlight_end + 1;
else
shift_pos = highlight_start;
}
else
{
shift_pos = cursor_pos;
}
}
bool at_end = false;
if (cursor_pos == 0 || cursor_pos == static_cast<long>(text_.size()))
at_end = true;
const long old_pos = cursor_pos;
unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y);
move_cursor(new_pos);
shift_pos = cursor_pos;
if (at_end && cursor_pos == old_pos)
{
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
}
}
}
else if (has_focus && rect.contains(x,y) == false)
{
t.stop();
has_focus = false;
cursor_visible = false;
shift_pos = -1;
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
if (focus_lost_handler.is_set())
focus_lost_handler();
parent.invalidate_rectangle(rect);
}
else
{
has_focus = false;
}
}
// ----------------------------------------------------------------------------------------
void text_box::
on_keydown (
unsigned long key,
bool is_printable,
unsigned long state
)
{
// If the right click menu is up then we don't want to do anything with
// the keyboard ourselves. Let the popup menu use the keyboard for now.
if (right_click_menu.popup_menu_visible())
return;
if (has_focus && enabled && !hidden)
{
const ustring space_str = convert_utf8_to_utf32(std::string(" \t\n"));
const bool shift = (state&base_window::KBD_MOD_SHIFT) != 0;
const bool ctrl = (state&base_window::KBD_MOD_CONTROL) != 0;
if (shift && is_printable == false)
{
if (shift_pos == -1)
{
if (highlight_start <= highlight_end)
{
if (highlight_start == cursor_pos)
shift_pos = highlight_end + 1;
else
shift_pos = highlight_start;
}
else
{
shift_pos = cursor_pos;
}
}
}
else
{
shift_pos = -1;
}
if (key == base_window::KEY_LEFT)
{
if (cursor_pos != 0)
{
unsigned long new_pos;
if (ctrl)
{
// find the first non-whitespace to our left
std::string::size_type pos = text_.find_last_not_of(space_str.c_str(),cursor_pos);
if (pos != std::string::npos)
{
pos = text_.find_last_of(space_str.c_str(),pos);
if (pos != std::string::npos)
new_pos = static_cast<unsigned long>(pos);
else
new_pos = 0;
}
else
{
new_pos = 0;
}
}
else
{
new_pos = cursor_pos-1;
}
move_cursor(new_pos);
}
else if (shift_pos == -1)
{
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
parent.invalidate_rectangle(rect);
}
}
else if (key == base_window::KEY_RIGHT)
{
if (cursor_pos != static_cast<long>(text_.size()))
{
unsigned long new_pos;
if (ctrl)
{
// find the first non-whitespace to our left
std::string::size_type pos = text_.find_first_not_of(space_str.c_str(),cursor_pos);
if (pos != std::string::npos)
{
pos = text_.find_first_of(space_str.c_str(),pos);
if (pos != std::string::npos)
new_pos = static_cast<unsigned long>(pos+1);
else
new_pos = static_cast<unsigned long>(text_.size());
}
else
{
new_pos = static_cast<unsigned long>(text_.size());
}
}
else
{
new_pos = cursor_pos+1;
}
move_cursor(new_pos);
}
else if (shift_pos == -1)
{
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
parent.invalidate_rectangle(rect);
}
}
else if (key == base_window::KEY_UP)
{
if (ctrl)
{
move_cursor(0);
}
else
{
const point origin(total_rect().left(), total_rect().top());
// move the cursor so the position that is just a few pixels above
// the current cursor_rect
move_cursor(mfont->compute_cursor_pos(
get_text_rect(), text_, cursor_rect.left()+origin.x(),
cursor_rect.top()+origin.y()-mfont->height()/2));
}
if (shift_pos == -1)
{
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
parent.invalidate_rectangle(rect);
}
}
else if (key == base_window::KEY_DOWN)
{
if (ctrl)
{
move_cursor(static_cast<unsigned long>(text_.size()));
}
else
{
const point origin(total_rect().left(), total_rect().top());
// move the cursor so the position that is just a few pixels above
// the current cursor_rect
move_cursor(mfont->compute_cursor_pos(
get_text_rect(), text_, cursor_rect.left()+origin.x(),
cursor_rect.bottom()+origin.y()+mfont->height()/2));
}
if (shift_pos == -1)
{
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
parent.invalidate_rectangle(rect);
}
}
else if (is_printable)
{
if (ctrl)
{
if (key == 'a')
{
on_select_all();
}
else if (key == 'c')
{
on_copy();
}
else if (key == 'v')
{
on_paste();
}
else if (key == 'x')
{
on_cut();
}
}
else
{
if (highlight_start <= highlight_end)
{
text_ = text_.substr(0,highlight_start) + static_cast<unichar>(key) +
text_.substr(highlight_end+1,text_.size()-highlight_end-1);
adjust_total_rect();
move_cursor(highlight_start+1);
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
}
else
{
text_ = text_.substr(0,cursor_pos) + static_cast<unichar>(key) +
text_.substr(cursor_pos,text_.size()-cursor_pos);
adjust_total_rect();
move_cursor(cursor_pos+1);
}
// send out the text modified event
if (text_modified_handler.is_set())
text_modified_handler();
}
if (key == '\n')
{
if (enter_key_handler.is_set())
enter_key_handler();
}
}
else if (key == base_window::KEY_BACKSPACE)
{
// if something is highlighted then delete that
if (highlight_start <= highlight_end)
{
on_delete_selected();
}
else if (cursor_pos != 0)
{
text_ = text_.erase(cursor_pos-1,1);
adjust_total_rect();
move_cursor(cursor_pos-1);
// send out the text modified event
if (text_modified_handler.is_set())
text_modified_handler();
}
else
{
// do this just so it repaints itself right
move_cursor(cursor_pos);
}
}
else if (key == base_window::KEY_DELETE)
{
// if something is highlighted then delete that
if (highlight_start <= highlight_end)
{
on_delete_selected();
}
else if (cursor_pos != static_cast<long>(text_.size()))
{
text_ = text_.erase(cursor_pos,1);
adjust_total_rect();
// send out the text modified event
if (text_modified_handler.is_set())
text_modified_handler();
}
else
{
// do this just so it repaints itself right
move_cursor(cursor_pos);
}
}
else if (key == base_window::KEY_HOME)
{
if (ctrl)
{
move_cursor(0);
}
else if (cursor_pos != 0)
{
// find the start of the current line
ustring::size_type pos = text_.find_last_of('\n',cursor_pos-1);
if (pos == ustring::npos)
pos = 0;
else
pos += 1;
move_cursor(static_cast<unsigned long>(pos));
}
if (shift_pos == -1)
{
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
parent.invalidate_rectangle(rect);
}
}
else if (key == base_window::KEY_END)
{
if (ctrl)
{
move_cursor(static_cast<unsigned long>(text_.size()));
}
{
ustring::size_type pos = text_.find_first_of('\n',cursor_pos);
if (pos == ustring::npos)
pos = text_.size();
move_cursor(static_cast<unsigned long>(pos));
}
if (shift_pos == -1)
{
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
parent.invalidate_rectangle(rect);
}
}
else if (key == base_window::KEY_PAGE_DOWN || key == base_window::KEY_PAGE_UP)
{
long jump_size = display_rect().height() -
std::min(mfont->height()*3, display_rect().height()/5);
// if we are supposed to page up then just jump in the other direction
if (key == base_window::KEY_PAGE_UP)
jump_size = -jump_size;
scroll_to_rect(translate_rect(display_rect(), point(0, jump_size )));
}
cursor_visible = true;
recent_movement = true;
}
}
// ----------------------------------------------------------------------------------------
void text_box::
on_string_put(
const std::wstring &str
)
{
if (has_focus && enabled && !hidden)
{
ustring ustr = convert_wstring_to_utf32(str);
if (highlight_start <= highlight_end)
{
text_ = text_.substr(0,highlight_start) + ustr +
text_.substr(highlight_end+1,text_.size()-highlight_end-1);
adjust_total_rect();
move_cursor(highlight_start+ustr.size());
highlight_start = 0;
highlight_end = -1;
on_no_text_selected();
}
else
{
text_ = text_.substr(0,cursor_pos) + ustr +
text_.substr(cursor_pos,text_.size()-cursor_pos);
adjust_total_rect();
move_cursor(cursor_pos+ustr.size());
}
// send out the text modified event
if (text_modified_handler.is_set())
text_modified_handler();
}
}
// ----------------------------------------------------------------------------------------
void text_box::
move_cursor (
unsigned long pos
)
{
using namespace std;
const long old_cursor_pos = cursor_pos;
// figure out where the cursor is supposed to be
cursor_rect = mfont->compute_cursor_rect(get_text_rect(), text_, pos);
const point origin(total_rect().left(), total_rect().top());
cursor_pos = pos;
const unsigned long padding = style->get_padding(*mfont);
// find the delta between the cursor rect and the corner of the total rect
point delta = point(cursor_rect.left(), cursor_rect.top()) - point(total_rect().left(), total_rect().top());
// now scroll us so that we can see the current cursor
scroll_to_rect(centered_rect(cursor_rect, cursor_rect.width() + padding + 6, cursor_rect.height() + 1));
// adjust the cursor_rect so that it is relative to the total_rect
cursor_rect = translate_rect(cursor_rect, -origin);
parent.set_im_pos(cursor_rect.left(), cursor_rect.top());
if (old_cursor_pos != cursor_pos)
{
if (shift_pos != -1)
{
highlight_start = std::min(shift_pos,cursor_pos);
highlight_end = std::max(shift_pos,cursor_pos)-1;
}
if (highlight_start > highlight_end)
on_no_text_selected();
else
on_text_is_selected();
recent_movement = true;
cursor_visible = true;
parent.invalidate_rectangle(display_rect());
}
if (shift_pos == -1)
{
highlight_start = 0;
highlight_end = -1;
}
}
// ----------------------------------------------------------------------------------------
}
......
......@@ -649,6 +649,379 @@ namespace dlib
);
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// class text_box
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
class text_box : public scrollable_region
{
/*!
INITIAL VALUE
text_color_ == rgb_pixel(0,0,0)
bg_color_ == rgb_pixel(255,255,255)
cursor_pos == 0
text_ == ""
has_focus == false
cursor_visible == false
recent_movement == false
highlight_start == 0
highlight_end == -1
shift_pos == -1
CONVENTION
- cursor_pos == the position of the cursor in the string text_. The
cursor appears before the letter text_[cursor_pos]
- cursor_rect == The rectangle that should be drawn for the cursor.
The position is relative to total_rect().
- has_focus == true if this text field has keyboard input focus
- cursor_visible == true if the cursor should be painted
- text_ == text()
- if (has_focus && the user has recently moved the cursor) then
- recent_movement == true
- else
- recent_movement == false
- if (highlight_start <= highlight_end) then
- text[highlight_start] though text[highlight_end] should be
highlighted
- if (shift_pos != -1) then
- has_focus == true
- the shift key is being held down or the left mouse button is
being held down.
- shift_pos == the position of the cursor when the shift or mouse key
was first pressed.
- text_color() == text_color_
- background_color() == bg_color_
!*/
public:
text_box(
drawable_window& w
) :
scrollable_region(w,MOUSE_CLICK | KEYBOARD_EVENTS | MOUSE_MOVE | STRING_PUT),
text_color_(0,0,0),
bg_color_(255,255,255),
recent_movement(false),
has_focus(false),
cursor_visible(false),
cursor_pos(0),
highlight_start(0),
highlight_end(-1),
shift_pos(-1),
t(*this,&text_box::timer_action),
right_click_menu(w)
{
style.reset(new text_box_style_default());
const long padding = static_cast<long>(style->get_padding(*mfont));
cursor_rect = mfont->compute_cursor_rect(rectangle(padding,padding,1000000,1000000), text_, 0);
adjust_total_rect();
set_vertical_mouse_wheel_scroll_increment(mfont->height());
set_horizontal_mouse_wheel_scroll_increment(mfont->height());
right_click_menu.menu().add_menu_item(menu_item_text("Cut",*this,&text_box::on_cut,'t'));
right_click_menu.menu().add_menu_item(menu_item_text("Copy",*this,&text_box::on_copy,'C'));
right_click_menu.menu().add_menu_item(menu_item_text("Paste",*this,&text_box::on_paste,'P'));
right_click_menu.menu().add_menu_item(menu_item_text("Delete",*this,&text_box::on_delete_selected,'D'));
right_click_menu.menu().add_menu_item(menu_item_separator());
right_click_menu.menu().add_menu_item(menu_item_text("Select All",*this,&text_box::on_select_all,'A'));
right_click_menu.set_rect(get_text_rect());
set_size(100,100);
enable_events();
t.set_delay_time(500);
}
~text_box (
)
{
disable_events();
parent.invalidate_rectangle(rect);
t.stop_and_wait();
}
template <
typename style_type
>
void set_style (
const style_type& style_
)
{
auto_mutex M(m);
style.reset(new style_type(style_));
scrollable_region::set_style(style_.get_scrollable_region_style());
// call this just so that this widget redraws itself with the new style
set_main_font(mfont);
}
void set_text (
const std::string& text_
);
void set_text (
const std::wstring& text_
);
void set_text (
const dlib::ustring& text_
);
const std::string text (
) const;
const std::wstring wtext (
) const;
const dlib::ustring utext (
) const;
void set_text_color (
const rgb_pixel color
);
const rgb_pixel text_color (
) const;
void set_background_color (
const rgb_pixel color
);
const rgb_pixel background_color (
) const;
void set_size (
unsigned long width,
unsigned long height
);
void set_pos (
long x,
long y
);
void set_main_font (
const shared_ptr_thread_safe<font>& f
);
int next_free_user_event_number (
) const
{
return scrollable_region::next_free_user_event_number()+1;
}
void disable (
);
void enable (
);
void hide (
);
void show (
);
template <
typename T
>
void set_text_modified_handler (
T& object,
void (T::*event_handler)()
)
{
auto_mutex M(m);
text_modified_handler.set(object,event_handler);
}
template <
typename T
>
void set_enter_key_handler (
T& object,
void (T::*event_handler)()
)
{
auto_mutex M(m);
enter_key_handler.set(object,event_handler);
}
template <
typename T
>
void set_focus_lost_handler (
T& object,
void (T::*event_handler)()
)
{
auto_mutex M(m);
focus_lost_handler.set(object,event_handler);
}
private:
void on_cut (
);
void on_copy (
);
void on_paste (
);
void on_select_all (
);
void on_delete_selected (
);
void on_text_is_selected (
);
void on_no_text_selected (
);
void on_user_event (
int num
)
{
// ignore this user event if it isn't for us
if (num != scrollable_region::next_free_user_event_number())
return;
if (recent_movement == false)
{
cursor_visible = !cursor_visible;
parent.invalidate_rectangle(rect);
}
else
{
if (cursor_visible == false)
{
cursor_visible = true;
parent.invalidate_rectangle(rect);
}
recent_movement = false;
}
}
// The reason for using user actions here rather than just having the timer just call
// what it needs directly is to avoid a potential deadlock during destruction of this widget.
void timer_action (
) { parent.trigger_user_event(this,scrollable_region::next_free_user_event_number()); }
/*!
ensures
- flips the state of cursor_visible
!*/
void move_cursor (
unsigned long pos
);
/*!
requires
- pos <= text_.size()
ensures
- moves the cursor to the position given by pos and moves the text
in the text box if necessary
- if the position changes then the parent window will be updated
!*/
rectangle get_text_rect (
) const;
/*!
ensures
- returns the rectangle that should contain the text in this widget
!*/
void adjust_total_rect (
);
/*!
ensures
- adjusts total_rect() so that it is big enough to contain the text
currently in this object.
!*/
dlib::ustring text_;
rgb_pixel text_color_;
rgb_pixel bg_color_;
bool recent_movement;
bool has_focus;
bool cursor_visible;
long cursor_pos;
rectangle cursor_rect;
// this tells you what part of the text is highlighted
long highlight_start;
long highlight_end;
long shift_pos;
member_function_pointer<>::kernel_1a_c text_modified_handler;
member_function_pointer<>::kernel_1a_c enter_key_handler;
member_function_pointer<>::kernel_1a_c focus_lost_handler;
scoped_ptr<text_box_style> style;
timer<text_box>::kernel_2a t;
popup_menu_region right_click_menu;
// restricted functions
text_box(text_box&); // copy constructor
text_box& operator=(text_box&); // assignment operator
protected:
void draw (
const canvas& c
) const;
void on_mouse_down (
unsigned long btn,
unsigned long state,
long x,
long y,
bool is_double_click
);
void on_mouse_up (
unsigned long btn,
unsigned long state,
long x,
long y
);
void on_mouse_move (
unsigned long state,
long x,
long y
);
void on_keydown (
unsigned long key,
bool is_printable,
unsigned long state
);
void on_string_put (
const std::wstring &str
);
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// class check_box
......
......@@ -623,6 +623,195 @@ namespace dlib
text_field& operator=(text_field&); // assignment operator
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// class text_box
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
class text_box : public drawable
{
/*!
INITIAL VALUE
- text() == ""
- The text color will be black.
- width() == 100
- height() == 100
WHAT THIS OBJECT REPRESENTS
This object represents a simple multi-line text input box.
!*/
public:
text_box(
drawable_window& w
);
/*!
ensures
- #*this is properly initialized
- #*this has been added to window w
- #parent_window() == w
throws
- std::bad_alloc
- dlib::thread_error
!*/
virtual ~text_box(
);
/*!
ensures
- all resources associated with *this have been released
!*/
template <
typename style_type
>
void set_style (
const style_type& style
);
/*!
requires
- style_type == a type that inherits from text_box_style
ensures
- this text_box object will draw itself using the given
text box style
!*/
void set_text (const std::wstring& text);
void set_text (const dlib::ustring& text);
void set_text (
const std::string& text
);
/*!
ensures
- #text() == text
throws
- std::bad_alloc
!*/
const std::wstring wtext () const;
const dlib::ustring utext () const;
const std::string text (
) const;
/*!
ensures
- returns the text of this text_box
throws
- std::bad_alloc
!*/
void set_size (
unsigned long width,
unsigned long height
);
/*!
ensures
- #width() == width_
- #height() == height_
- #top() == top()
- #left() == left()
- i.e. The location of the upper left corner of this widget stays the
same but its width and height are modified
!*/
void set_text_color (
const rgb_pixel color
);
/*!
ensures
- #text_color() == color
!*/
const rgb_pixel text_color (
) const;
/*!
ensures
- returns the color used to draw the text in this widget
!*/
void set_background_color (
const rgb_pixel color
);
/*!
ensures
- #background_color() == color
!*/
const rgb_pixel background_color (
) const;
/*!
ensures
- returns the color used to fill in the background of this widget
!*/
template <
typename T
>
void set_text_modified_handler (
T& object,
void (T::*event_handler)()
);
/*!
requires
- event_handler is a valid pointer to a member function in T
ensures
- the event_handler function is called on object when the text
in this text_box is modified by the user.
- any previous calls to this function are overridden by this new call.
(i.e. you can only have one event handler associated with this
event at a time)
throws
- std::bad_alloc
!*/
template <
typename T
>
void set_enter_key_handler (
T& object,
void (T::*event_handler)()
);
/*!
requires
- event_handler is a valid pointer to a member function in T
ensures
- the event_handler function is called on object when this text box
has input focus and the user hits the enter key on their keyboard.
- any previous calls to this function are overridden by this new call.
(i.e. you can only have one event handler associated with this
event at a time)
throws
- std::bad_alloc
!*/
template <
typename T
>
void set_focus_lost_handler (
T& object,
void (T::*event_handler)()
);
/*!
requires
- event_handler is a valid pointer to a member function in T
ensures
- the event_handler function is called on object when this object
loses input focus due to the user clicking outside the text box
- any previous calls to this function are overridden by this new call.
(i.e. you can only have one event handler associated with this
event at a time)
throws
- std::bad_alloc
!*/
private:
// restricted functions
text_box(text_box&); // copy constructor
text_box& operator=(text_box&); // assignment operator
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// class check_box
......
......@@ -236,6 +236,11 @@ class win : public drawable_window
else
tf.disable();
if (sb_enabled.is_checked())
tb.enable();
else
tb.disable();
}
void cb_sb_shown (
......@@ -428,6 +433,7 @@ public:
tab_label(*this),
tabs(*this),
tf(*this),
tb(*this),
mbar(*this)
{
bool use_bdf_fonts = false;
......@@ -635,6 +641,13 @@ public:
tf.set_enter_key_handler(*this,&win::on_enter_key);
tf.set_focus_lost_handler(*this,&win::on_tf_focus_lost);
tb.set_pos(250,400);
tb.set_text("initial test\nstring");
tb.set_size(300,300);
tb.set_text_color(rgb_pixel(255,0,0));
tb.set_enter_key_handler(*this,&win::on_enter_key);
tb.set_focus_lost_handler(*this,&win::on_tf_focus_lost);
button_count = 0;
count = 0;
......@@ -646,7 +659,7 @@ public:
b.set_main_font(f);
set_size(800,500);
set_size(815,730);
nrect.wrap_around(
cbox.get_rect() +
......@@ -660,7 +673,10 @@ public:
save_file_box(*this,&win::on_open_file);
if (use_bdf_fonts)
{
tf.set_main_font(f);
tb.set_main_font(f);
}
if (use_bdf_fonts)
tabs.set_main_font(f);
......@@ -676,7 +692,7 @@ private:
void on_tf_focus_lost()
{
cout << "text field lost focus" << endl;
cout << "text field/box lost focus" << endl;
}
......@@ -766,6 +782,7 @@ private:
label tab_label;
tabbed_display tabs;
text_field tf;
text_box tb;
menu_bar mbar;
popup_menu submenu;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment