mirror of
https://github.com/RGBCube/serenity
synced 2025-07-23 09:27:35 +00:00
LibGUI: Implement searching/jumping as you type in views
This allows the user to start typing and highlighting and jumping to a match in ColumnsView, IconView, TableView and TreeView if the model supports it.
This commit is contained in:
parent
307f0bc778
commit
52a847a0eb
13 changed files with 244 additions and 19 deletions
|
@ -25,7 +25,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
|
#include <AK/Utf8View.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
#include <LibCore/Timer.h>
|
||||||
#include <LibGUI/AbstractView.h>
|
#include <LibGUI/AbstractView.h>
|
||||||
#include <LibGUI/DragOperation.h>
|
#include <LibGUI/DragOperation.h>
|
||||||
#include <LibGUI/Model.h>
|
#include <LibGUI/Model.h>
|
||||||
|
@ -33,6 +35,7 @@
|
||||||
#include <LibGUI/Painter.h>
|
#include <LibGUI/Painter.h>
|
||||||
#include <LibGUI/ScrollBar.h>
|
#include <LibGUI/ScrollBar.h>
|
||||||
#include <LibGUI/TextBox.h>
|
#include <LibGUI/TextBox.h>
|
||||||
|
#include <LibGfx/Palette.h>
|
||||||
|
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
|
@ -44,6 +47,8 @@ AbstractView::AbstractView()
|
||||||
|
|
||||||
AbstractView::~AbstractView()
|
AbstractView::~AbstractView()
|
||||||
{
|
{
|
||||||
|
if (m_searching_timer)
|
||||||
|
m_searching_timer->stop();
|
||||||
if (m_model)
|
if (m_model)
|
||||||
m_model->unregister_view({}, *this);
|
m_model->unregister_view({}, *this);
|
||||||
}
|
}
|
||||||
|
@ -421,9 +426,13 @@ void AbstractView::set_cursor(ModelIndex index, SelectionUpdate selection_update
|
||||||
{
|
{
|
||||||
if (!model() || !index.is_valid()) {
|
if (!model() || !index.is_valid()) {
|
||||||
m_cursor_index = {};
|
m_cursor_index = {};
|
||||||
|
cancel_searching();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m_cursor_index.is_valid() || model()->parent_index(m_cursor_index) != model()->parent_index(index))
|
||||||
|
cancel_searching();
|
||||||
|
|
||||||
if (model()->is_valid(index)) {
|
if (model()->is_valid(index)) {
|
||||||
if (selection_update == SelectionUpdate::Set)
|
if (selection_update == SelectionUpdate::Set)
|
||||||
set_selection(index);
|
set_selection(index);
|
||||||
|
@ -517,7 +526,152 @@ void AbstractView::keydown_event(KeyEvent& event)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_searchable()) {
|
||||||
|
if (event.key() == KeyCode::Key_Backspace) {
|
||||||
|
if (is_searching()) {
|
||||||
|
//if (event.modifiers() == Mod_Ctrl) {
|
||||||
|
// TODO: delete last word
|
||||||
|
//}
|
||||||
|
Utf8View view(m_searching);
|
||||||
|
size_t n_code_points = view.length();
|
||||||
|
if (n_code_points > 1) {
|
||||||
|
n_code_points--;
|
||||||
|
StringBuilder sb;
|
||||||
|
for (auto it = view.begin(); it != view.end(); ++it) {
|
||||||
|
if (n_code_points == 0)
|
||||||
|
break;
|
||||||
|
n_code_points--;
|
||||||
|
sb.append_code_point(*it);
|
||||||
|
}
|
||||||
|
do_search(sb.to_string());
|
||||||
|
start_searching_timer();
|
||||||
|
} else {
|
||||||
|
cancel_searching();
|
||||||
|
}
|
||||||
|
|
||||||
|
event.accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (event.key() == KeyCode::Key_Escape) {
|
||||||
|
if (is_searching()) {
|
||||||
|
cancel_searching();
|
||||||
|
|
||||||
|
event.accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (!event.ctrl() && !event.alt() && event.code_point() != 0) {
|
||||||
|
StringBuilder sb;
|
||||||
|
sb.append(m_searching);
|
||||||
|
sb.append_code_point(event.code_point());
|
||||||
|
do_search(sb.to_string());
|
||||||
|
start_searching_timer();
|
||||||
|
|
||||||
|
event.accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget::keydown_event(event);
|
Widget::keydown_event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AbstractView::cancel_searching()
|
||||||
|
{
|
||||||
|
m_searching = nullptr;
|
||||||
|
if (m_searching_timer)
|
||||||
|
m_searching_timer->stop();
|
||||||
|
if (m_highlighted_search_index.is_valid()) {
|
||||||
|
m_highlighted_search_index = {};
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractView::start_searching_timer()
|
||||||
|
{
|
||||||
|
if (!m_searching_timer) {
|
||||||
|
m_searching_timer = add<Core::Timer>();
|
||||||
|
m_searching_timer->set_single_shot(true);
|
||||||
|
m_searching_timer->on_timeout = [this] {
|
||||||
|
cancel_searching();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
m_searching_timer->set_interval(5 * 1000);
|
||||||
|
m_searching_timer->restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractView::do_search(String&& searching)
|
||||||
|
{
|
||||||
|
if (searching.is_empty() || !model()) {
|
||||||
|
cancel_searching();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto found_indexes = model()->matches(searching, Model::MatchesFlag::FirstMatchOnly | Model::MatchesFlag::MatchAtStart | Model::MatchesFlag::CaseInsensitive, model()->parent_index(cursor_index()));
|
||||||
|
if (!found_indexes.is_empty() && found_indexes[0].is_valid()) {
|
||||||
|
auto& index = found_indexes[0];
|
||||||
|
m_highlighted_search_index = index;
|
||||||
|
m_searching = move(searching);
|
||||||
|
set_selection(index);
|
||||||
|
scroll_into_view(index);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractView::is_searchable() const
|
||||||
|
{
|
||||||
|
if (!m_searchable || !model())
|
||||||
|
return false;
|
||||||
|
return model()->is_searchable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractView::set_searchable(bool searchable)
|
||||||
|
{
|
||||||
|
if (m_searchable == searchable)
|
||||||
|
return;
|
||||||
|
m_searchable = searchable;
|
||||||
|
if (!m_searchable)
|
||||||
|
cancel_searching();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractView::is_highlighting_searching(const ModelIndex& index) const
|
||||||
|
{
|
||||||
|
return index == m_highlighted_search_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractView::draw_item_text(Gfx::Painter& painter, const ModelIndex& index, bool is_selected, const Gfx::IntRect& text_rect, const StringView& item_text, const Gfx::Font& font, Gfx::TextAlignment alignment, Gfx::TextElision elision)
|
||||||
|
{
|
||||||
|
Color text_color;
|
||||||
|
if (is_selected)
|
||||||
|
text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
|
||||||
|
else
|
||||||
|
text_color = index.data(ModelRole::ForegroundColor).to_color(palette().color(foreground_role()));
|
||||||
|
if (is_highlighting_searching(index)) {
|
||||||
|
Utf8View searching_text(searching());
|
||||||
|
auto searching_length = searching_text.length();
|
||||||
|
|
||||||
|
// Highlight the text background first
|
||||||
|
painter.draw_text([&](const Gfx::IntRect& rect, u32) {
|
||||||
|
if (searching_length > 0) {
|
||||||
|
searching_length--;
|
||||||
|
painter.fill_rect(rect.inflated(0, 2), palette().highlight_searching());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text_rect, item_text, font, alignment, elision);
|
||||||
|
|
||||||
|
// Then draw the text
|
||||||
|
auto highlight_text_color = palette().highlight_searching_text();
|
||||||
|
searching_length = searching_text.length();
|
||||||
|
painter.draw_text([&](const Gfx::IntRect& rect, u32 code_point) {
|
||||||
|
if (searching_length > 0) {
|
||||||
|
searching_length--;
|
||||||
|
painter.draw_glyph_or_emoji(rect.location(), code_point, font, highlight_text_color);
|
||||||
|
} else {
|
||||||
|
painter.draw_glyph_or_emoji(rect.location(), code_point, font, text_color);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text_rect, item_text, font, alignment, elision);
|
||||||
|
} else {
|
||||||
|
painter.draw_text(text_rect, item_text, font, alignment, text_color, elision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <AK/Function.h>
|
#include <AK/Function.h>
|
||||||
#include <LibGUI/ModelSelection.h>
|
#include <LibGUI/ModelSelection.h>
|
||||||
#include <LibGUI/ScrollableWidget.h>
|
#include <LibGUI/ScrollableWidget.h>
|
||||||
|
#include <LibGfx/TextElision.h>
|
||||||
|
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
|
@ -68,6 +69,11 @@ public:
|
||||||
bool is_editable() const { return m_editable; }
|
bool is_editable() const { return m_editable; }
|
||||||
void set_editable(bool editable) { m_editable = editable; }
|
void set_editable(bool editable) { m_editable = editable; }
|
||||||
|
|
||||||
|
bool is_searching() const { return !m_searching.is_null(); }
|
||||||
|
|
||||||
|
bool is_searchable() const;
|
||||||
|
void set_searchable(bool);
|
||||||
|
|
||||||
enum EditTrigger {
|
enum EditTrigger {
|
||||||
None = 0,
|
None = 0,
|
||||||
DoubleClicked = 1 << 0,
|
DoubleClicked = 1 << 0,
|
||||||
|
@ -138,13 +144,22 @@ protected:
|
||||||
virtual void remove_selection(const ModelIndex&);
|
virtual void remove_selection(const ModelIndex&);
|
||||||
virtual void toggle_selection(const ModelIndex&);
|
virtual void toggle_selection(const ModelIndex&);
|
||||||
|
|
||||||
|
void draw_item_text(Gfx::Painter&, const ModelIndex&, bool, const Gfx::IntRect&, const StringView&, const Gfx::Font&, Gfx::TextAlignment, Gfx::TextElision);
|
||||||
|
|
||||||
virtual void did_scroll() override;
|
virtual void did_scroll() override;
|
||||||
void set_hovered_index(const ModelIndex&);
|
void set_hovered_index(const ModelIndex&);
|
||||||
void activate(const ModelIndex&);
|
void activate(const ModelIndex&);
|
||||||
void activate_selected();
|
void activate_selected();
|
||||||
void update_edit_widget_position();
|
void update_edit_widget_position();
|
||||||
|
|
||||||
|
StringView searching() const { return m_searching; }
|
||||||
|
void cancel_searching();
|
||||||
|
void start_searching_timer();
|
||||||
|
void do_search(String&&);
|
||||||
|
bool is_highlighting_searching(const ModelIndex&) const;
|
||||||
|
|
||||||
bool m_editable { false };
|
bool m_editable { false };
|
||||||
|
bool m_searchable { true };
|
||||||
ModelIndex m_edit_index;
|
ModelIndex m_edit_index;
|
||||||
RefPtr<Widget> m_edit_widget;
|
RefPtr<Widget> m_edit_widget;
|
||||||
Gfx::IntRect m_edit_widget_content_rect;
|
Gfx::IntRect m_edit_widget_content_rect;
|
||||||
|
@ -154,6 +169,7 @@ protected:
|
||||||
bool m_might_drag { false };
|
bool m_might_drag { false };
|
||||||
|
|
||||||
ModelIndex m_hovered_index;
|
ModelIndex m_hovered_index;
|
||||||
|
ModelIndex m_highlighted_search_index;
|
||||||
|
|
||||||
int m_key_column { -1 };
|
int m_key_column { -1 };
|
||||||
SortOrder m_sort_order;
|
SortOrder m_sort_order;
|
||||||
|
@ -161,6 +177,8 @@ protected:
|
||||||
private:
|
private:
|
||||||
RefPtr<Model> m_model;
|
RefPtr<Model> m_model;
|
||||||
ModelSelection m_selection;
|
ModelSelection m_selection;
|
||||||
|
String m_searching;
|
||||||
|
RefPtr<Core::Timer> m_searching_timer;
|
||||||
ModelIndex m_cursor_index;
|
ModelIndex m_cursor_index;
|
||||||
unsigned m_edit_triggers { EditTrigger::DoubleClicked | EditTrigger::EditKeyPressed };
|
unsigned m_edit_triggers { EditTrigger::DoubleClicked | EditTrigger::EditKeyPressed };
|
||||||
bool m_activates_on_selection { false };
|
bool m_activates_on_selection { false };
|
||||||
|
|
|
@ -141,8 +141,7 @@ void ColumnsView::paint_event(PaintEvent& event)
|
||||||
icon_rect.right() + 1 + icon_spacing(), row * item_height(),
|
icon_rect.right() + 1 + icon_spacing(), row * item_height(),
|
||||||
column.width - icon_spacing() - icon_size() - icon_spacing() - icon_spacing() - s_arrow_bitmap_width - icon_spacing(), item_height()
|
column.width - icon_spacing() - icon_size() - icon_spacing() - icon_spacing() - s_arrow_bitmap_width - icon_spacing(), item_height()
|
||||||
};
|
};
|
||||||
auto text = index.data().to_string();
|
draw_item_text(painter, index, is_selected_row, text_rect, index.data().to_string(), font_for_index(index), Gfx::TextAlignment::CenterLeft, Gfx::TextElision::None);
|
||||||
painter.draw_text(text_rect, text, Gfx::TextAlignment::CenterLeft, text_color);
|
|
||||||
|
|
||||||
bool expandable = model()->row_count(index) > 0;
|
bool expandable = model()->row_count(index) > 0;
|
||||||
if (expandable) {
|
if (expandable) {
|
||||||
|
|
|
@ -627,4 +627,21 @@ void FileSystemModel::set_data(const ModelIndex& index, const Variant& data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector<ModelIndex, 1> FileSystemModel::matches(const StringView& searching, unsigned flags, const ModelIndex& index)
|
||||||
|
{
|
||||||
|
Node& node = const_cast<Node&>(this->node(index));
|
||||||
|
node.reify_if_needed();
|
||||||
|
Vector<ModelIndex, 1> found_indexes;
|
||||||
|
for (auto& child : node.children) {
|
||||||
|
if (string_matches(child.name, searching, flags)) {
|
||||||
|
const_cast<Node&>(child).reify_if_needed();
|
||||||
|
found_indexes.append(child.index(Column::Name));
|
||||||
|
if (flags & FirstMatchOnly)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found_indexes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,7 +150,9 @@ public:
|
||||||
virtual bool accepts_drag(const ModelIndex&, const StringView& data_type) override;
|
virtual bool accepts_drag(const ModelIndex&, const StringView& data_type) override;
|
||||||
virtual bool is_column_sortable(int column_index) const override { return column_index != Column::Icon; }
|
virtual bool is_column_sortable(int column_index) const override { return column_index != Column::Icon; }
|
||||||
virtual bool is_editable(const ModelIndex&) const override;
|
virtual bool is_editable(const ModelIndex&) const override;
|
||||||
|
virtual bool is_searchable() const override { return true; }
|
||||||
virtual void set_data(const ModelIndex&, const Variant&) override;
|
virtual void set_data(const ModelIndex&, const Variant&) override;
|
||||||
|
virtual Vector<ModelIndex, 1> matches(const StringView&, unsigned = MatchesFlag::AllMatching, const ModelIndex& = ModelIndex()) override;
|
||||||
|
|
||||||
static String timestamp_string(time_t timestamp)
|
static String timestamp_string(time_t timestamp)
|
||||||
{
|
{
|
||||||
|
|
|
@ -119,4 +119,17 @@ ModelIndex FilteringProxyModel::map(const ModelIndex& index) const
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FilteringProxyModel::is_searchable() const
|
||||||
|
{
|
||||||
|
return m_model.is_searchable();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<ModelIndex, 1> FilteringProxyModel::matches(const StringView& searching, unsigned flags, const ModelIndex& index)
|
||||||
|
{
|
||||||
|
auto found_indexes = m_model.matches(searching, flags, index);
|
||||||
|
for (size_t i = 0; i < found_indexes.size(); i++)
|
||||||
|
found_indexes[i] = map(found_indexes[i]);
|
||||||
|
return found_indexes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@ public:
|
||||||
virtual Variant data(const ModelIndex&, ModelRole = ModelRole::Display) const override;
|
virtual Variant data(const ModelIndex&, ModelRole = ModelRole::Display) const override;
|
||||||
virtual void update() override;
|
virtual void update() override;
|
||||||
virtual ModelIndex index(int row, int column = 0, const ModelIndex& parent = ModelIndex()) const override;
|
virtual ModelIndex index(int row, int column = 0, const ModelIndex& parent = ModelIndex()) const override;
|
||||||
|
virtual bool is_searchable() const override;
|
||||||
|
virtual Vector<ModelIndex, 1> matches(const StringView&, unsigned = MatchesFlag::AllMatching, const ModelIndex& = ModelIndex()) override;
|
||||||
|
|
||||||
void set_filter_term(const StringView& term);
|
void set_filter_term(const StringView& term);
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
|
#include <AK/Utf8View.h>
|
||||||
#include <LibCore/Timer.h>
|
#include <LibCore/Timer.h>
|
||||||
#include <LibGUI/DragOperation.h>
|
#include <LibGUI/DragOperation.h>
|
||||||
#include <LibGUI/IconView.h>
|
#include <LibGUI/IconView.h>
|
||||||
|
@ -464,13 +465,8 @@ void IconView::paint_event(PaintEvent& event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Color text_color;
|
|
||||||
if (item_data.selected)
|
|
||||||
text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
|
|
||||||
else
|
|
||||||
text_color = item_data.index.data(ModelRole::ForegroundColor).to_color(palette().color(foreground_role()));
|
|
||||||
painter.fill_rect(item_data.text_rect, background_color);
|
painter.fill_rect(item_data.text_rect, background_color);
|
||||||
painter.draw_text(item_data.text_rect, item_text.to_string(), font_for_index(item_data.index), Gfx::TextAlignment::Center, text_color, Gfx::TextElision::Right);
|
draw_item_text(painter, item_data.index, item_data.selected, item_data.text_rect, item_text.to_string(), font_for_index(item_data.index), Gfx::TextAlignment::Center, Gfx::TextElision::Right);
|
||||||
|
|
||||||
if (item_data.index == m_drop_candidate_index) {
|
if (item_data.index == m_drop_candidate_index) {
|
||||||
// FIXME: This visualization is not great, as it's also possible to drop things on the text label..
|
// FIXME: This visualization is not great, as it's also possible to drop things on the text label..
|
||||||
|
|
|
@ -59,6 +59,13 @@ public:
|
||||||
InvalidateAllIndexes = 1 << 0,
|
InvalidateAllIndexes = 1 << 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum MatchesFlag {
|
||||||
|
AllMatching = 0,
|
||||||
|
FirstMatchOnly = 1 << 0,
|
||||||
|
CaseInsensitive = 1 << 1,
|
||||||
|
MatchAtStart = 1 << 2,
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~Model();
|
virtual ~Model();
|
||||||
|
|
||||||
virtual int row_count(const ModelIndex& = ModelIndex()) const = 0;
|
virtual int row_count(const ModelIndex& = ModelIndex()) const = 0;
|
||||||
|
@ -70,9 +77,11 @@ public:
|
||||||
virtual ModelIndex parent_index(const ModelIndex&) const { return {}; }
|
virtual ModelIndex parent_index(const ModelIndex&) const { return {}; }
|
||||||
virtual ModelIndex index(int row, int column = 0, const ModelIndex& parent = ModelIndex()) const;
|
virtual ModelIndex index(int row, int column = 0, const ModelIndex& parent = ModelIndex()) const;
|
||||||
virtual bool is_editable(const ModelIndex&) const { return false; }
|
virtual bool is_editable(const ModelIndex&) const { return false; }
|
||||||
|
virtual bool is_searchable() const { return false; }
|
||||||
virtual void set_data(const ModelIndex&, const Variant&) { }
|
virtual void set_data(const ModelIndex&, const Variant&) { }
|
||||||
virtual int tree_column() const { return 0; }
|
virtual int tree_column() const { return 0; }
|
||||||
virtual bool accepts_drag(const ModelIndex&, const StringView& data_type);
|
virtual bool accepts_drag(const ModelIndex&, const StringView& data_type);
|
||||||
|
virtual Vector<ModelIndex, 1> matches(const StringView&, unsigned = MatchesFlag::AllMatching, const ModelIndex& = ModelIndex()) { return {}; }
|
||||||
|
|
||||||
virtual bool is_column_sortable([[maybe_unused]] int column_index) const { return true; }
|
virtual bool is_column_sortable([[maybe_unused]] int column_index) const { return true; }
|
||||||
virtual void sort([[maybe_unused]] int column, SortOrder) { }
|
virtual void sort([[maybe_unused]] int column, SortOrder) { }
|
||||||
|
@ -97,6 +106,14 @@ protected:
|
||||||
void for_each_view(Function<void(AbstractView&)>);
|
void for_each_view(Function<void(AbstractView&)>);
|
||||||
void did_update(unsigned flags = UpdateFlag::InvalidateAllIndexes);
|
void did_update(unsigned flags = UpdateFlag::InvalidateAllIndexes);
|
||||||
|
|
||||||
|
static bool string_matches(const StringView& str, const StringView& needle, unsigned flags)
|
||||||
|
{
|
||||||
|
auto case_sensitivity = (flags & CaseInsensitive) ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive;
|
||||||
|
if (flags & MatchAtStart)
|
||||||
|
return str.starts_with(needle, case_sensitivity);
|
||||||
|
return str.contains(needle, case_sensitivity);
|
||||||
|
}
|
||||||
|
|
||||||
ModelIndex create_index(int row, int column, const void* data = nullptr) const;
|
ModelIndex create_index(int row, int column, const void* data = nullptr) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -295,4 +295,17 @@ void SortingProxyModel::set_data(const ModelIndex& proxy_index, const Variant& d
|
||||||
source().set_data(map_to_source(proxy_index), data);
|
source().set_data(map_to_source(proxy_index), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SortingProxyModel::is_searchable() const
|
||||||
|
{
|
||||||
|
return source().is_searchable();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<ModelIndex, 1> SortingProxyModel::matches(const StringView& searching, unsigned flags, const ModelIndex& proxy_index)
|
||||||
|
{
|
||||||
|
auto found_indexes = source().matches(searching, flags, map_to_source(proxy_index));
|
||||||
|
for (size_t i = 0; i < found_indexes.size(); i++)
|
||||||
|
found_indexes[i] = map_to_proxy(found_indexes[i]);
|
||||||
|
return found_indexes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,9 @@ public:
|
||||||
virtual ModelIndex parent_index(const ModelIndex&) const override;
|
virtual ModelIndex parent_index(const ModelIndex&) const override;
|
||||||
virtual ModelIndex index(int row, int column, const ModelIndex& parent) const override;
|
virtual ModelIndex index(int row, int column, const ModelIndex& parent) const override;
|
||||||
virtual bool is_editable(const ModelIndex&) const override;
|
virtual bool is_editable(const ModelIndex&) const override;
|
||||||
|
virtual bool is_searchable() const override;
|
||||||
virtual void set_data(const ModelIndex&, const Variant&) override;
|
virtual void set_data(const ModelIndex&, const Variant&) override;
|
||||||
|
virtual Vector<ModelIndex, 1> matches(const StringView&, unsigned = MatchesFlag::AllMatching, const ModelIndex& = ModelIndex()) override;
|
||||||
|
|
||||||
virtual bool is_column_sortable(int column_index) const override;
|
virtual bool is_column_sortable(int column_index) const override;
|
||||||
|
|
||||||
|
|
|
@ -127,11 +127,6 @@ void TableView::paint_event(PaintEvent& event)
|
||||||
painter.blit(cell_rect.location(), *bitmap, bitmap->rect());
|
painter.blit(cell_rect.location(), *bitmap, bitmap->rect());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Color text_color;
|
|
||||||
if (is_selected_row)
|
|
||||||
text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
|
|
||||||
else
|
|
||||||
text_color = cell_index.data(ModelRole::ForegroundColor).to_color(palette().color(foreground_role()));
|
|
||||||
if (!is_selected_row) {
|
if (!is_selected_row) {
|
||||||
auto cell_background_color = cell_index.data(ModelRole::BackgroundColor);
|
auto cell_background_color = cell_index.data(ModelRole::BackgroundColor);
|
||||||
if (cell_background_color.is_valid())
|
if (cell_background_color.is_valid())
|
||||||
|
@ -139,7 +134,7 @@ void TableView::paint_event(PaintEvent& event)
|
||||||
}
|
}
|
||||||
|
|
||||||
auto text_alignment = cell_index.data(ModelRole::TextAlignment).to_text_alignment(Gfx::TextAlignment::CenterLeft);
|
auto text_alignment = cell_index.data(ModelRole::TextAlignment).to_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||||
painter.draw_text(cell_rect, data.to_string(), font_for_index(cell_index), text_alignment, text_color, Gfx::TextElision::Right);
|
draw_item_text(painter, cell_index, is_selected_row, cell_rect, data.to_string(), font_for_index(cell_index), text_alignment, Gfx::TextElision::Right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -318,10 +318,8 @@ void TreeView::paint_event(PaintEvent& event)
|
||||||
if (auto bitmap = data.as_icon().bitmap_for_size(16))
|
if (auto bitmap = data.as_icon().bitmap_for_size(16))
|
||||||
painter.blit(cell_rect.location(), *bitmap, bitmap->rect());
|
painter.blit(cell_rect.location(), *bitmap, bitmap->rect());
|
||||||
} else {
|
} else {
|
||||||
if (!is_selected_row)
|
|
||||||
text_color = cell_index.data(ModelRole::ForegroundColor).to_color(palette().color(foreground_role()));
|
|
||||||
auto text_alignment = cell_index.data(ModelRole::TextAlignment).to_text_alignment(Gfx::TextAlignment::CenterLeft);
|
auto text_alignment = cell_index.data(ModelRole::TextAlignment).to_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||||
painter.draw_text(cell_rect, data.to_string(), font_for_index(cell_index), text_alignment, text_color, Gfx::TextElision::Right);
|
draw_item_text(painter, cell_index, is_selected_row, cell_rect, data.to_string(), font_for_index(cell_index), text_alignment, Gfx::TextElision::Right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -340,8 +338,7 @@ void TreeView::paint_event(PaintEvent& event)
|
||||||
icon_rect.right() + 1 + icon_spacing(), rect.y(),
|
icon_rect.right() + 1 + icon_spacing(), rect.y(),
|
||||||
rect.width() - icon_size() - icon_spacing(), rect.height()
|
rect.width() - icon_size() - icon_spacing(), rect.height()
|
||||||
};
|
};
|
||||||
auto node_text = index.data().to_string();
|
draw_item_text(painter, index, is_selected_row, text_rect, index.data().to_string(), font_for_index(index), Gfx::TextAlignment::Center, Gfx::TextElision::None);
|
||||||
painter.draw_text(text_rect, node_text, font_for_index(index), Gfx::TextAlignment::Center, text_color);
|
|
||||||
auto index_at_indent = index;
|
auto index_at_indent = index;
|
||||||
for (int i = indent_level; i > 0; --i) {
|
for (int i = indent_level; i > 0; --i) {
|
||||||
auto parent_of_index_at_indent = index_at_indent.parent();
|
auto parent_of_index_at_indent = index_at_indent.parent();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue