Add filters
This commit is contained in:
parent
68e30aa968
commit
f08282a5ed
|
@ -26,8 +26,8 @@ set(LIBS -lpcre2-8 -lpcre2-posix SDL2 imgui)
|
||||||
set(IMGUI_LIBS SDL2 Freetype::Freetype)
|
set(IMGUI_LIBS SDL2 Freetype::Freetype)
|
||||||
|
|
||||||
set(INCLUDES imgui imgui/backends imgui/misc/cpp imgui/freetype /usr/include/SDL2)
|
set(INCLUDES imgui imgui/backends imgui/misc/cpp imgui/freetype /usr/include/SDL2)
|
||||||
set(SOURCES main.cpp event_loop.cpp logcat_thread.cpp logcat_entry.cpp log.cpp config.cpp misc.cpp pcre2_wrapper.cpp
|
set(SOURCES main.cpp event_loop.cpp logcat_thread.cpp logcat_entry.cpp log.cpp config.cpp filters.cpp misc.cpp pcre2_wrapper.cpp
|
||||||
windows/logs.cpp windows/settings.cpp windows/main.cpp)
|
group_panel.cpp windows/logs.cpp windows/settings.cpp windows/filters.cpp windows/main.cpp)
|
||||||
set(IMGUI_SOURCES imgui/imgui.cpp imgui/imgui_draw.cpp imgui/imgui_widgets.cpp imgui/imgui_tables.cpp
|
set(IMGUI_SOURCES imgui/imgui.cpp imgui/imgui_draw.cpp imgui/imgui_widgets.cpp imgui/imgui_tables.cpp
|
||||||
imgui/misc/cpp/imgui_stdlib.cpp imgui/misc/freetype/imgui_freetype.cpp
|
imgui/misc/cpp/imgui_stdlib.cpp imgui/misc/freetype/imgui_freetype.cpp
|
||||||
imgui/backends/imgui_impl_sdl.cpp imgui/backends/imgui_impl_opengl3.cpp)
|
imgui/backends/imgui_impl_sdl.cpp imgui/backends/imgui_impl_opengl3.cpp)
|
||||||
|
@ -42,7 +42,8 @@ endif()
|
||||||
|
|
||||||
set(FLAGS "${FLAGS} -DIMGUI_USER_CONFIG='\"../myimconfig.h\"'")
|
set(FLAGS "${FLAGS} -DIMGUI_USER_CONFIG='\"../myimconfig.h\"'")
|
||||||
# https://t.me/NightShadowsHangout/670691
|
# https://t.me/NightShadowsHangout/670691
|
||||||
set(FLAGS "${FLAGS} -Werror -Wall -Wextra -Wshadow -Wpedantic -Wno-gnu-anonymous-struct -fno-rtti -fPIC -Wconversion -Wno-unused-parameter -Wimplicit-fallthrough")
|
# https://t.me/NightShadowsHangout/688372
|
||||||
|
set(FLAGS "${FLAGS} -Werror -Wall -Wextra -Wshadow -Wpedantic -Wno-gnu-anonymous-struct -fPIC -Wconversion -Wno-unused-parameter -Wimplicit-fallthrough")
|
||||||
# https://sourceforge.net/p/valgrind/mailman/valgrind-users/thread/Ygze8PzaQAYWlKDj%40wildebeest.org/
|
# https://sourceforge.net/p/valgrind/mailman/valgrind-users/thread/Ygze8PzaQAYWlKDj%40wildebeest.org/
|
||||||
set(FLAGS "${FLAGS} -gdwarf-4")
|
set(FLAGS "${FLAGS} -gdwarf-4")
|
||||||
|
|
||||||
|
|
6
config.h
6
config.h
|
@ -12,9 +12,3 @@ std::string get_config_folder();
|
||||||
void create_config_folders_if_necessary();
|
void create_config_folders_if_necessary();
|
||||||
Config load_config();
|
Config load_config();
|
||||||
void write_config(const Config& config);
|
void write_config(const Config& config);
|
||||||
|
|
||||||
constexpr bool operator!=(const Config& lhs, const Config& rhs) {
|
|
||||||
return lhs.logcat_command != rhs.logcat_command
|
|
||||||
|| lhs.normal_font_size != rhs.normal_font_size
|
|
||||||
|| lhs.monospace_font_size != rhs.monospace_font_size;
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,23 +2,29 @@
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "filters.h"
|
||||||
#include "logcat_thread.h"
|
#include "logcat_thread.h"
|
||||||
|
|
||||||
#include "windows/logs.h"
|
#include "windows/logs.h"
|
||||||
|
#include "windows/filters.h"
|
||||||
#include "windows/settings.h"
|
#include "windows/settings.h"
|
||||||
#include "windows/main.h"
|
#include "windows/main.h"
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#include "windows/debug.h"
|
#include "windows/debug.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline void check_for_logcat_items(LogcatThread& logcat_thread, std::vector<LogcatEntry>& filtered_logcat_entries) {
|
static inline void check_for_logcat_items(LogcatThread& logcat_thread, const Filters& filters,
|
||||||
|
std::vector<LogcatEntry>& logcat_entries, std::vector<size_t>& filtered_logcat_entry_offsets) {
|
||||||
LogcatThreadItem* logcat_thread_item;
|
LogcatThreadItem* logcat_thread_item;
|
||||||
|
|
||||||
while ((logcat_thread_item = logcat_thread.atomic_ring_buffer.get())) {
|
while ((logcat_thread_item = logcat_thread.atomic_ring_buffer.get())) {
|
||||||
if (std::holds_alternative<std::string>(*logcat_thread_item)) {
|
if (std::holds_alternative<std::string>(*logcat_thread_item)) {
|
||||||
log_raw(std::move(std::get<std::string>(*logcat_thread_item)), false);
|
log_raw(std::move(std::get<std::string>(*logcat_thread_item)), false);
|
||||||
} else if (std::holds_alternative<LogcatEntry>(*logcat_thread_item)) {
|
} else if (std::holds_alternative<LogcatEntry>(*logcat_thread_item)) {
|
||||||
filtered_logcat_entries.push_back(std::move(std::get<LogcatEntry>(*logcat_thread_item)));
|
logcat_entries.push_back(std::move(std::get<LogcatEntry>(*logcat_thread_item)));
|
||||||
|
if (matches(filters, logcat_entries.back())) {
|
||||||
|
filtered_logcat_entry_offsets.push_back(logcat_entries.size() - 1);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw std::runtime_error("Cannot handle all possible logcat thread item variants");
|
throw std::runtime_error("Cannot handle all possible logcat thread item variants");
|
||||||
}
|
}
|
||||||
|
@ -27,14 +33,17 @@ static inline void check_for_logcat_items(LogcatThread& logcat_thread, std::vect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void event_loop(ImFont* monospace_font, Config& active_config, LogcatThread& logcat_thread, bool* run_event_loop) {
|
void event_loop(ImFont* monospace_font, Config& active_config, Filters& active_filters, LogcatThread& logcat_thread, bool* run_event_loop) {
|
||||||
static Config inactive_config;
|
static Config inactive_config;
|
||||||
|
static Filters inactive_filters;
|
||||||
static bool show_settings_window = false;
|
static bool show_settings_window = false;
|
||||||
|
static bool show_filters_window = false;
|
||||||
static bool show_logs_window = false;
|
static bool show_logs_window = false;
|
||||||
static size_t log_entries_read = 0;
|
static size_t log_entries_read = 0;
|
||||||
static std::vector<LogcatEntry> filtered_logcat_entries;
|
static std::vector<LogcatEntry> logcat_entries;
|
||||||
|
static std::vector<size_t> filtered_logcat_entry_offsets;
|
||||||
|
|
||||||
check_for_logcat_items(logcat_thread, filtered_logcat_entries);
|
check_for_logcat_items(logcat_thread, active_filters, logcat_entries, filtered_logcat_entry_offsets);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
debug_window(logcat_thread);
|
debug_window(logcat_thread);
|
||||||
|
@ -44,6 +53,10 @@ void event_loop(ImFont* monospace_font, Config& active_config, LogcatThread& log
|
||||||
settings_window(active_config, inactive_config, &show_settings_window);
|
settings_window(active_config, inactive_config, &show_settings_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (show_filters_window) {
|
||||||
|
filters_window(active_filters, inactive_filters, logcat_entries, filtered_logcat_entry_offsets, &show_filters_window);
|
||||||
|
}
|
||||||
|
|
||||||
if (show_logs_window) {
|
if (show_logs_window) {
|
||||||
bool autoscrolling = false;
|
bool autoscrolling = false;
|
||||||
logs_window(monospace_font, &autoscrolling, &show_logs_window);
|
logs_window(monospace_font, &autoscrolling, &show_logs_window);
|
||||||
|
@ -53,8 +66,9 @@ void event_loop(ImFont* monospace_font, Config& active_config, LogcatThread& log
|
||||||
}
|
}
|
||||||
|
|
||||||
// log_entries must not be mutated until the show logs button
|
// log_entries must not be mutated until the show logs button
|
||||||
main_window(log_entries_read == log_entries.size(),
|
main_window(log_entries_read == log_entries.size(), monospace_font,
|
||||||
monospace_font, filtered_logcat_entries,
|
logcat_entries, filtered_logcat_entry_offsets,
|
||||||
active_config, inactive_config, &show_settings_window,
|
active_config, inactive_config,
|
||||||
&show_logs_window, run_event_loop);
|
active_filters, inactive_filters,
|
||||||
|
&show_settings_window, &show_filters_window, &show_logs_window, run_event_loop);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "filters.h"
|
||||||
#include "logcat_thread.h"
|
#include "logcat_thread.h"
|
||||||
|
|
||||||
void event_loop(ImFont* monospace_font, Config& active_config, LogcatThread& logcat_thread, bool* run_event_loop);
|
void event_loop(ImFont* monospace_font, Config& active_config, Filters& active_filters, LogcatThread& logcat_thread, bool* run_event_loop);
|
||||||
|
|
|
@ -0,0 +1,275 @@
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
#include "filters.h"
|
||||||
|
|
||||||
|
Filter::~Filter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IntegerFilter::IntegerFilter(FilterKey key_, size_t other_, bool inverted_, bool disabled)
|
||||||
|
: key(key_), other(other_), inverted(inverted_), _disabled(disabled) {
|
||||||
|
if (key_ != FilterKey::PID && key_ != FilterKey::TID) {
|
||||||
|
throw std::runtime_error("IntegerFilter is passed a non-integer key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegerFilter::updated() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IntegerFilter::disabled() const {
|
||||||
|
return this->_disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegerFilter::disabled(bool disabled) {
|
||||||
|
this->_disabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Filter> IntegerFilter::clone() const {
|
||||||
|
return std::make_unique<IntegerFilter>(this->key, this->other, this->inverted, this->_disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string* IntegerFilter::error() const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IntegerFilter::match(const LogcatEntry& entry) const {
|
||||||
|
bool matched;
|
||||||
|
switch (this->key) {
|
||||||
|
case FilterKey::PID: matched = entry.pid == this->other; break;
|
||||||
|
case FilterKey::TID: matched = entry.tid == this->other; break;
|
||||||
|
default: throw std::runtime_error("IntegerFilter::match received unhandled key");
|
||||||
|
};
|
||||||
|
return !this->inverted ? matched : !matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StringFilter::StringFilter(FilterKey key_, std::string other_, bool inverted_, bool disabled)
|
||||||
|
: key(key_), other(std::move(other_)), inverted(inverted_), _disabled(disabled) {
|
||||||
|
if (key_ != FilterKey::User && key_ != FilterKey::Tag && key_ != FilterKey::Message) {
|
||||||
|
throw std::runtime_error("StringFilter is passed a non-string key");
|
||||||
|
}
|
||||||
|
this->updated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StringFilter::updated() {
|
||||||
|
this->_regex.reset();
|
||||||
|
this->_error.clear();
|
||||||
|
|
||||||
|
if (this->other.empty()) {
|
||||||
|
this->_error = "String must not be empty";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->other.starts_with("regex:")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->other.size() == 6) {
|
||||||
|
this->_error = "Regex must not be empty";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this->_regex.emplace(&this->other.c_str()[6]);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
this->_error = e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StringFilter::disabled() const {
|
||||||
|
return this->_disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StringFilter::disabled(bool disabled) {
|
||||||
|
this->_disabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Filter> StringFilter::clone() const {
|
||||||
|
return std::make_unique<StringFilter>(this->key, this->other, this->inverted, this->_disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string* StringFilter::error() const {
|
||||||
|
return !this->_error.empty() ? &this->_error : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StringFilter::match(const LogcatEntry& entry) const {
|
||||||
|
bool matched;
|
||||||
|
|
||||||
|
if (this->error()) {
|
||||||
|
log(std::string("StringFilter::match called despite there being an error: ") + *this->error());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!this->other.starts_with("regex:")) {
|
||||||
|
switch (this->key) {
|
||||||
|
case FilterKey::User: matched = entry.user.value_or("") == this->other; break;
|
||||||
|
case FilterKey::Tag: matched = entry.tag == this->other; break;
|
||||||
|
case FilterKey::Message: matched = entry.message == this->other; break;
|
||||||
|
default: throw std::runtime_error("StringFilter::match received unhandled key");
|
||||||
|
}
|
||||||
|
return !this->inverted ? matched : !matched;
|
||||||
|
}
|
||||||
|
if (!this->_regex) {
|
||||||
|
log("StringFilter::match called with a regex despite there being no regex");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string str;
|
||||||
|
regmatch_t pmatch[1];
|
||||||
|
switch (this->key) {
|
||||||
|
case FilterKey::User: str = entry.user.value_or(""); break;
|
||||||
|
case FilterKey::Tag: str = entry.tag; break;
|
||||||
|
case FilterKey::Message: str = entry.message; break;
|
||||||
|
default: throw std::runtime_error("StringFilter::match received unhandled key");
|
||||||
|
}
|
||||||
|
pmatch[0].rm_so = 0;
|
||||||
|
pmatch[0].rm_eo = static_cast<regoff_t>(str.size());
|
||||||
|
|
||||||
|
matched = this->_regex->match(str.data(), 1, pmatch, REG_STARTEND)
|
||||||
|
&& pmatch[0].rm_so == 0 && static_cast<size_t>(pmatch[0].rm_eo) == str.size();
|
||||||
|
return !this->inverted ? matched : !matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BufferFilter::BufferFilter(unsigned int wanted_, bool disabled) : wanted(wanted_), _disabled(disabled) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferFilter::updated() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BufferFilter::disabled() const {
|
||||||
|
return this->_disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferFilter::disabled(bool disabled) {
|
||||||
|
this->_disabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Filter> BufferFilter::clone() const {
|
||||||
|
return std::make_unique<BufferFilter>(this->wanted, this->_disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string* BufferFilter::error() const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BufferFilter::match(const LogcatEntry& entry) const {
|
||||||
|
return this->wanted & static_cast<unsigned int>(entry.buffer) || this->wanted == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PriorityFilter::PriorityFilter(unsigned int wanted_, bool disabled) : wanted(wanted_), _disabled(disabled) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PriorityFilter::updated() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PriorityFilter::disabled() const {
|
||||||
|
return this->_disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PriorityFilter::disabled(bool disabled) {
|
||||||
|
this->_disabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Filter> PriorityFilter::clone() const {
|
||||||
|
return std::make_unique<PriorityFilter>(this->wanted, this->_disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string* PriorityFilter::error() const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PriorityFilter::match(const LogcatEntry& entry) const {
|
||||||
|
return this->wanted & static_cast<unsigned int>(entry.priority) || this->wanted == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GroupFilter::GroupFilter(std::vector<std::unique_ptr<Filter>> filters_, GroupFilter::Type type_, bool disabled)
|
||||||
|
: filters(std::move(filters_)), type(type_), _disabled(disabled) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupFilter::updated() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupFilter::disabled() const {
|
||||||
|
return this->_disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupFilter::disabled(bool disabled) {
|
||||||
|
this->_disabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Filter> GroupFilter::clone() const {
|
||||||
|
std::vector<std::unique_ptr<Filter>> new_filters;
|
||||||
|
new_filters.reserve(this->filters.size());
|
||||||
|
|
||||||
|
for (const std::unique_ptr<Filter>& filter : this->filters) {
|
||||||
|
new_filters.push_back(filter->clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<GroupFilter>(std::move(new_filters), this->type, this->_disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string* GroupFilter::error() const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupFilter::match(const LogcatEntry& entry) const {
|
||||||
|
bool had_matched = false;
|
||||||
|
|
||||||
|
for (const std::unique_ptr<Filter>& filter : this->filters) {
|
||||||
|
if (filter->disabled() || filter->error()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matched = filter->match(entry);
|
||||||
|
if (this->type == GroupFilter::Type::Any && matched) {
|
||||||
|
return true;
|
||||||
|
} else if (this->type == GroupFilter::Type::None && matched) {
|
||||||
|
return false;
|
||||||
|
} else if (this->type == GroupFilter::Type::All && !matched) {
|
||||||
|
return false;
|
||||||
|
} else if (this->type == GroupFilter::Type::One) {
|
||||||
|
if (had_matched && matched) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (matched) {
|
||||||
|
had_matched = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->type == GroupFilter::Type::Any) {
|
||||||
|
return false;
|
||||||
|
} else if (this->type == GroupFilter::Type::One && !had_matched) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void copy_filters(Filters& filters, const Filters& other) {
|
||||||
|
filters.clear();
|
||||||
|
filters.reserve(other.size());
|
||||||
|
|
||||||
|
for (const auto &[title, filter] : other) {
|
||||||
|
filters.push_back(std::make_pair(title, filter->clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matches(const Filters& filters, const LogcatEntry& entry) {
|
||||||
|
bool ok_filter_exists = false;
|
||||||
|
|
||||||
|
for (const auto &[title, filter] : filters) {
|
||||||
|
if (filter->disabled() || filter->error()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ok_filter_exists = true;
|
||||||
|
if (filter->match(entry)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !ok_filter_exists;
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "logcat_entry.h"
|
||||||
|
#include "pcre2_wrapper.h"
|
||||||
|
|
||||||
|
enum class FilterKey {
|
||||||
|
Buffer,
|
||||||
|
User,
|
||||||
|
PID,
|
||||||
|
TID,
|
||||||
|
Priority,
|
||||||
|
Tag,
|
||||||
|
Message,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Filter {
|
||||||
|
public:
|
||||||
|
// https://stackoverflow.com/a/10024812
|
||||||
|
virtual ~Filter() = 0;
|
||||||
|
|
||||||
|
virtual void updated() = 0;
|
||||||
|
virtual bool disabled() const = 0;
|
||||||
|
virtual void disabled(bool disabled) = 0;
|
||||||
|
// https://stackoverflow.com/a/65344294
|
||||||
|
virtual std::unique_ptr<Filter> clone() const = 0;
|
||||||
|
virtual const std::string* error() const = 0;
|
||||||
|
virtual bool match(const LogcatEntry& entry) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IntegerFilter : public Filter {
|
||||||
|
public:
|
||||||
|
IntegerFilter(FilterKey key_, size_t other_, bool inverted_ = false, bool disabled = false);
|
||||||
|
|
||||||
|
void updated() override;
|
||||||
|
bool disabled() const override;
|
||||||
|
void disabled(bool disabled) override;
|
||||||
|
std::unique_ptr<Filter> clone() const override;
|
||||||
|
const std::string* error() const override;
|
||||||
|
bool match(const LogcatEntry& entry) const override;
|
||||||
|
|
||||||
|
FilterKey key;
|
||||||
|
size_t other;
|
||||||
|
bool inverted;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _disabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringFilter : public Filter {
|
||||||
|
public:
|
||||||
|
// https://stackoverflow.com/a/2173764
|
||||||
|
StringFilter(const StringFilter&) = delete;
|
||||||
|
StringFilter& operator=(const StringFilter&) = delete;
|
||||||
|
|
||||||
|
StringFilter(FilterKey key_, std::string other_, bool inverted_ = false, bool disabled = false);
|
||||||
|
|
||||||
|
void updated() override;
|
||||||
|
bool disabled() const override;
|
||||||
|
void disabled(bool disabled) override;
|
||||||
|
std::unique_ptr<Filter> clone() const override;
|
||||||
|
const std::string* error() const override;
|
||||||
|
bool match(const LogcatEntry& entry) const override;
|
||||||
|
|
||||||
|
FilterKey key;
|
||||||
|
std::string other;
|
||||||
|
bool inverted;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string _error;
|
||||||
|
std::optional<Pcre2Regex> _regex;
|
||||||
|
bool _disabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BufferFilter : public Filter {
|
||||||
|
public:
|
||||||
|
BufferFilter(unsigned int wanted_, bool disabled = false);
|
||||||
|
|
||||||
|
void updated() override;
|
||||||
|
bool disabled() const override;
|
||||||
|
void disabled(bool disabled) override;
|
||||||
|
std::unique_ptr<Filter> clone() const override;
|
||||||
|
const std::string* error() const override;
|
||||||
|
bool match(const LogcatEntry& entry) const override;
|
||||||
|
|
||||||
|
unsigned int wanted;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _disabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PriorityFilter : public Filter {
|
||||||
|
public:
|
||||||
|
PriorityFilter(unsigned int wanted_, bool disabled = false);
|
||||||
|
|
||||||
|
void updated() override;
|
||||||
|
bool disabled() const override;
|
||||||
|
void disabled(bool disabled) override;
|
||||||
|
std::unique_ptr<Filter> clone() const override;
|
||||||
|
const std::string* error() const override;
|
||||||
|
bool match(const LogcatEntry& entry) const override;
|
||||||
|
|
||||||
|
unsigned int wanted;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _disabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupFilter : public Filter {
|
||||||
|
public:
|
||||||
|
enum class Type {
|
||||||
|
All = 0,
|
||||||
|
Any = 1,
|
||||||
|
One = 2,
|
||||||
|
None = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
GroupFilter(std::vector<std::unique_ptr<Filter>> filters_, Type type_, bool disabled = false);
|
||||||
|
|
||||||
|
void updated() override;
|
||||||
|
bool disabled() const override;
|
||||||
|
void disabled(bool disabled) override;
|
||||||
|
std::unique_ptr<Filter> clone() const override;
|
||||||
|
const std::string* error() const override;
|
||||||
|
bool match(const LogcatEntry& entry) const override;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Filter>> filters;
|
||||||
|
Type type;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _disabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<std::pair<std::string, std::unique_ptr<Filter>>> Filters;
|
||||||
|
void copy_filters(Filters& filters, const Filters& other);
|
||||||
|
bool matches(const Filters& filters, const LogcatEntry& entry);
|
|
@ -0,0 +1,97 @@
|
||||||
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
#include <imgui_internal.h>
|
||||||
|
#include "group_panel.h"
|
||||||
|
|
||||||
|
// The following code is slightly modified public domain code from https://github.com/thedmd
|
||||||
|
// All modifications have an inline comment with "[CUSTOM]"
|
||||||
|
// Public domain claim from https://github.com/ocornut/imgui/issues/1496#issuecomment-1287772456
|
||||||
|
// Code from https://github.com/ocornut/imgui/issues/1496#issuecomment-569892444 and
|
||||||
|
// some snippets from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
|
||||||
|
|
||||||
|
void ImGui::BeginGroupPanel(const ImVec2& size) // [CUSTOM]
|
||||||
|
{
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
|
//auto cursorPos = ImGui::GetCursorScreenPos(); // [CUSTOM]
|
||||||
|
auto itemSpacing = ImGui::GetStyle().ItemSpacing;
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
|
||||||
|
|
||||||
|
auto frameHeight = ImGui::GetFrameHeight();
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
|
ImVec2 effectiveSize = size;
|
||||||
|
if (size.x < 0.0f)
|
||||||
|
effectiveSize.x = ImGui::GetContentRegionAvail().x; // [CUSTOM]
|
||||||
|
else
|
||||||
|
effectiveSize.x = size.x;
|
||||||
|
ImGui::Dummy(ImVec2(effectiveSize.x, 0.0f));
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(frameHeight, 0.0f)); // [CUSTOM]
|
||||||
|
ImGui::SameLine(0.0f, 0.0f);
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
ImGui::Dummy(ImVec2(frameHeight, 0.0f)); // [CUSTOM]
|
||||||
|
ImGui::SameLine(0.0f, 0.0f);
|
||||||
|
//ImGui::TextUnformatted(name); // [CUSTOM]
|
||||||
|
ImGui::SameLine(0.0f, 0.0f);
|
||||||
|
ImGui::Dummy(ImVec2(0.0, frameHeight + itemSpacing.y));
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
|
||||||
|
//ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x -= frameHeight * 0.5f; // [CUSTOM]
|
||||||
|
ImGui::GetCurrentWindow()->ContentRegionRect.Max.x -= frameHeight * 0.5f;
|
||||||
|
ImGui::GetCurrentWindow()->WorkRect.Max.x -= frameHeight * 0.5f;
|
||||||
|
ImGui::GetCurrentWindow()->InnerRect.Max.x -= frameHeight * 0.5f;
|
||||||
|
ImGui::GetCurrentWindow()->Size.x -= frameHeight;
|
||||||
|
|
||||||
|
auto itemWidth = ImGui::CalcItemWidth();
|
||||||
|
ImGui::PushItemWidth(ImMax(0.0f, itemWidth - frameHeight));
|
||||||
|
//ImGui::PushItemWidth(effectiveSize.x - frameHeight); // [CUSTOM]
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGui::EndGroupPanel()
|
||||||
|
{
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
|
auto itemSpacing = ImGui::GetStyle().ItemSpacing;
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
|
||||||
|
|
||||||
|
auto frameHeight = ImGui::GetFrameHeight();
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
//ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(0, 255, 0, 64), 4.0f);
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
ImGui::SameLine(0.0f, 0.0f);
|
||||||
|
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
|
||||||
|
ImGui::Dummy(ImVec2(0.0, frameHeight - itemSpacing.y)); // [CUSTOM]
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
auto itemMin = ImGui::GetItemRectMin();
|
||||||
|
auto itemMax = ImGui::GetItemRectMax();
|
||||||
|
//ImGui::GetWindowDrawList()->AddRectFilled(itemMin, itemMax, IM_COL32(255, 0, 0, 64), 4.0f);
|
||||||
|
|
||||||
|
ImVec2 halfFrame = ImVec2(frameHeight * 0.25f, frameHeight) * 0.5f;
|
||||||
|
ImGui::GetWindowDrawList()->AddRect(
|
||||||
|
itemMin + halfFrame, itemMax - ImVec2(halfFrame.x, 0.0f),
|
||||||
|
ImColor(ImGui::GetStyleColorVec4(ImGuiCol_Border)),
|
||||||
|
halfFrame.x);
|
||||||
|
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
|
||||||
|
//ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x += frameHeight * 0.5f; // [CUSTOM]
|
||||||
|
ImGui::GetCurrentWindow()->ContentRegionRect.Max.x += frameHeight * 0.5f;
|
||||||
|
ImGui::GetCurrentWindow()->WorkRect.Max.x += frameHeight * 0.5f;
|
||||||
|
ImGui::GetCurrentWindow()->InnerRect.Max.x += frameHeight * 0.5f;
|
||||||
|
ImGui::GetCurrentWindow()->Size.x += frameHeight;
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0.0f, 0.0f));
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
// The following code is slightly modified public domain code from https://github.com/thedmd
|
||||||
|
// All modifications have an inline comment with "[CUSTOM]"
|
||||||
|
// Public domain claim from https://github.com/ocornut/imgui/issues/1496#issuecomment-1287772456
|
||||||
|
// Code from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
|
||||||
|
|
||||||
|
namespace ImGui { // [CUSTOM]
|
||||||
|
void BeginGroupPanel(const ImVec2& size = ImVec2(-1.0f, -1.0f)); // [CUSTOM]
|
||||||
|
void EndGroupPanel();
|
||||||
|
}; // namespace ImGui // [CUSTOM]
|
5
main.cpp
5
main.cpp
|
@ -16,12 +16,15 @@
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "fonts.h"
|
#include "fonts.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "filters.h"
|
||||||
#include "event_loop.h"
|
#include "event_loop.h"
|
||||||
#include "logcat_thread.h"
|
#include "logcat_thread.h"
|
||||||
|
|
||||||
int main(int, char**) {
|
int main(int, char**) {
|
||||||
setlocale(LC_TIME, "");
|
setlocale(LC_TIME, "");
|
||||||
|
|
||||||
|
// TODO add saving
|
||||||
|
Filters filters;
|
||||||
Config config;
|
Config config;
|
||||||
try {
|
try {
|
||||||
config = load_config();
|
config = load_config();
|
||||||
|
@ -161,7 +164,7 @@ int main(int, char**) {
|
||||||
ImGui_ImplSDL2_NewFrame();
|
ImGui_ImplSDL2_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
event_loop(monospace_font, config, logcat_thread, &run_event_loop);
|
event_loop(monospace_font, config, filters, logcat_thread, &run_event_loop);
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
|
|
|
@ -0,0 +1,257 @@
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_stdlib.h>
|
||||||
|
|
||||||
|
#include "../group_panel.h"
|
||||||
|
#include "../filters.h"
|
||||||
|
#include "filters.h"
|
||||||
|
|
||||||
|
static inline void render_integer_filter(IntegerFilter* filter);
|
||||||
|
static inline void render_string_filter(StringFilter* filter);
|
||||||
|
static inline void render_buffer_filter(BufferFilter* filter);
|
||||||
|
static inline void render_priority_filter(PriorityFilter* filter);
|
||||||
|
static inline void render_group_filter(GroupFilter* filter);
|
||||||
|
static inline std::unique_ptr<Filter> render_add_filter_popup();
|
||||||
|
static void update_logcat_entries(const Filters& filters,
|
||||||
|
const std::vector<LogcatEntry>& logcat_entries, std::vector<size_t>& filtered_logcat_entry_offsets);
|
||||||
|
|
||||||
|
static void render_filter(Filter* filter, std::string* title, bool* request_removal) {
|
||||||
|
ImGui::PushID(filter);
|
||||||
|
ImGui::BeginGroupPanel();
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("Title:%s%s", title->empty() ? "" : " ", title->c_str());
|
||||||
|
ImGui::SameLine();
|
||||||
|
std::string change_title_id = std::string("changefiltertitle_") + std::to_string(reinterpret_cast<size_t>(filter));
|
||||||
|
if (ImGui::Button("Edit")) {
|
||||||
|
ImGui::OpenPopup(change_title_id.c_str());
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup(change_title_id.c_str())) {
|
||||||
|
ImGui::InputText("##title", title);
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("Remove")) {
|
||||||
|
*request_removal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ImGui::SameLine();
|
||||||
|
bool disabled = filter->disabled();
|
||||||
|
if (ImGui::Checkbox("Disabled", &disabled)) {
|
||||||
|
filter->disabled(disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
StringFilter* sfilter = dynamic_cast<StringFilter*>(filter);
|
||||||
|
IntegerFilter* ifilter = !sfilter ? dynamic_cast<IntegerFilter*>(filter) : nullptr;
|
||||||
|
if (sfilter || ifilter) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Inverted", sfilter ? &sfilter->inverted : &ifilter->inverted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter->error()) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text("(%s)", filter->error()->c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IntegerFilter* ifilter = dynamic_cast<IntegerFilter*>(filter)) {
|
||||||
|
render_integer_filter(ifilter);
|
||||||
|
} else if (StringFilter* sfilter = dynamic_cast<StringFilter*>(filter)) {
|
||||||
|
render_string_filter(sfilter);
|
||||||
|
} else if (BufferFilter* bfilter = dynamic_cast<BufferFilter*>(filter)) {
|
||||||
|
render_buffer_filter(bfilter);
|
||||||
|
} else if (PriorityFilter* pfilter = dynamic_cast<PriorityFilter*>(filter)) {
|
||||||
|
render_priority_filter(pfilter);
|
||||||
|
} else if (GroupFilter* gfilter = dynamic_cast<GroupFilter*>(filter)) {
|
||||||
|
render_group_filter(gfilter);
|
||||||
|
} else {
|
||||||
|
ImGui::TextUnformatted("An unknown filter, this probably shouldn't be mutated");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndGroupPanel();
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void render_integer_filter(IntegerFilter* filter) {
|
||||||
|
const char* head;
|
||||||
|
switch (filter->key) {
|
||||||
|
case FilterKey::PID: head = "PID"; break;
|
||||||
|
case FilterKey::TID: head = "TID"; break;
|
||||||
|
default: head = "Something";
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("%s is%s", head, filter->inverted ? " not" : "");
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::InputScalar("##int", ImGuiDataType_U64, &filter->other)) {
|
||||||
|
filter->updated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void render_string_filter(StringFilter* filter) {
|
||||||
|
const char* head;
|
||||||
|
switch (filter->key) {
|
||||||
|
case FilterKey::User: head = "User"; break;
|
||||||
|
case FilterKey::Tag: head = "Tag"; break;
|
||||||
|
case FilterKey::Message: head = "Message"; break;
|
||||||
|
default: head = "Something";
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("%s is%s", head, filter->inverted ? " not" : "");
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::InputText("##str", &filter->other)) {
|
||||||
|
filter->updated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void render_buffer_filter(BufferFilter* filter) {
|
||||||
|
auto update_if_needed = [&](bool updated) {
|
||||||
|
if (updated) {
|
||||||
|
filter->updated();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextUnformatted("Buffer is:"); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Unknown", &filter->wanted, static_cast<unsigned int>(Buffer::Unknown))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Main", &filter->wanted, static_cast<unsigned int>(Buffer::Main))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("System", &filter->wanted, static_cast<unsigned int>(Buffer::System))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Radio", &filter->wanted, static_cast<unsigned int>(Buffer::Radio))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Events", &filter->wanted, static_cast<unsigned int>(Buffer::Events))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Crash", &filter->wanted, static_cast<unsigned int>(Buffer::Crash)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void render_priority_filter(PriorityFilter* filter) {
|
||||||
|
auto update_if_needed = [&](bool updated) {
|
||||||
|
if (updated) {
|
||||||
|
filter->updated();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextUnformatted("Priority is:"); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Unknown", &filter->wanted, static_cast<unsigned int>(Priority::Unknown))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Verbose", &filter->wanted, static_cast<unsigned int>(Priority::Verbose))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Debug", &filter->wanted, static_cast<unsigned int>(Priority::Debug))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Info", &filter->wanted, static_cast<unsigned int>(Priority::Info))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Warning", &filter->wanted, static_cast<unsigned int>(Priority::Warn))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Error", &filter->wanted, static_cast<unsigned int>(Priority::Error))); ImGui::SameLine();
|
||||||
|
update_if_needed(ImGui::CheckboxFlags("Fatal", &filter->wanted, static_cast<unsigned int>(Priority::Fatal)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void render_group_filter(GroupFilter* filter) {
|
||||||
|
int selected_type = static_cast<int>(filter->type);
|
||||||
|
if (ImGui::Combo("of these are true", &selected_type, "All\0Any\0One\0None\0")) {
|
||||||
|
filter->type = static_cast<GroupFilter::Type>(selected_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::vector<std::unique_ptr<Filter>>::iterator it = filter->filters.begin(); it != filter->filters.end();) {
|
||||||
|
bool removal_requested = false;
|
||||||
|
render_filter(it->get(), nullptr, &removal_requested);
|
||||||
|
if (removal_requested) {
|
||||||
|
filter->filters.erase(it);
|
||||||
|
filter->updated();
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string add_filter_id = std::string("addfilter_") + std::to_string(reinterpret_cast<size_t>(filter));
|
||||||
|
if (ImGui::Button("+ add filter")) {
|
||||||
|
ImGui::OpenPopup(add_filter_id.c_str());
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup(add_filter_id.c_str())) {
|
||||||
|
std::unique_ptr<Filter> added_filter = render_add_filter_popup();
|
||||||
|
if (added_filter) {
|
||||||
|
filter->filters.push_back(std::move(added_filter));
|
||||||
|
filter->updated();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline std::unique_ptr<Filter> render_add_filter_popup() {
|
||||||
|
if (ImGui::Selectable("Buffer")) {
|
||||||
|
return std::make_unique<BufferFilter>(0);
|
||||||
|
} else if (ImGui::Selectable("User")) {
|
||||||
|
return std::make_unique<StringFilter>(FilterKey::User, "");
|
||||||
|
} else if (ImGui::Selectable("PID")) {
|
||||||
|
return std::make_unique<IntegerFilter>(FilterKey::PID, 0);
|
||||||
|
} else if (ImGui::Selectable("TID")) {
|
||||||
|
return std::make_unique<IntegerFilter>(FilterKey::TID, 0);
|
||||||
|
} else if (ImGui::Selectable("Priority")) {
|
||||||
|
return std::make_unique<PriorityFilter>(0);
|
||||||
|
} else if (ImGui::Selectable("Tag")) {
|
||||||
|
return std::make_unique<StringFilter>(FilterKey::Tag, "");
|
||||||
|
} else if (ImGui::Selectable("Message")) {
|
||||||
|
return std::make_unique<StringFilter>(FilterKey::Message, "");
|
||||||
|
} else if (ImGui::Selectable("Group of filters")) {
|
||||||
|
return std::make_unique<GroupFilter>(std::vector<std::unique_ptr<Filter>>(), GroupFilter::Type::All);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::unique_ptr<Filter>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_logcat_entries(const Filters& filters,
|
||||||
|
const std::vector<LogcatEntry>& logcat_entries, std::vector<size_t>& filtered_logcat_entry_offsets) {
|
||||||
|
filtered_logcat_entry_offsets.clear();
|
||||||
|
for (size_t i=0; i < logcat_entries.size(); i++) {
|
||||||
|
if (matches(filters, logcat_entries[i])) {
|
||||||
|
filtered_logcat_entry_offsets.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void filters_window(Filters& active_filters, Filters& inactive_filters,
|
||||||
|
const std::vector<LogcatEntry>& logcat_entries, std::vector<size_t>& filtered_logcat_entry_offsets,
|
||||||
|
bool* p_open) {
|
||||||
|
if (!ImGui::Begin("Filters", p_open)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextUnformatted("You can use regex for strings by prepending \"regex:\"");
|
||||||
|
|
||||||
|
for (Filters::iterator it = inactive_filters.begin(); it != inactive_filters.end();) {
|
||||||
|
bool removal_requested = false;
|
||||||
|
render_filter(it->second.get(), &it->first, &removal_requested);
|
||||||
|
if (removal_requested) {
|
||||||
|
inactive_filters.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("+ add filter")) {
|
||||||
|
ImGui::OpenPopup("addfilter_root");
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup("addfilter_root")) {
|
||||||
|
std::unique_ptr<Filter> added_filter = render_add_filter_popup();
|
||||||
|
if (added_filter) {
|
||||||
|
inactive_filters.push_back(std::make_pair("", std::move(added_filter)));
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImVec2 button_size(4 * ImGui::GetFontSize(), 0);
|
||||||
|
if (ImGui::Button("OK", button_size)) {
|
||||||
|
active_filters = std::move(inactive_filters);
|
||||||
|
update_logcat_entries(active_filters, logcat_entries, filtered_logcat_entry_offsets);
|
||||||
|
*p_open = false;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Cancel", button_size)) {
|
||||||
|
*p_open = false;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Apply", button_size)) {
|
||||||
|
copy_filters(active_filters, inactive_filters);
|
||||||
|
update_logcat_entries(active_filters, logcat_entries, filtered_logcat_entry_offsets);
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../logcat_entry.h"
|
||||||
|
#include "../filters.h"
|
||||||
|
|
||||||
|
void filters_window(Filters& active_filters, Filters& inactive_filters,
|
||||||
|
const std::vector<LogcatEntry>& logcat_entries, std::vector<size_t>& filtered_logcat_entry_offsets,
|
||||||
|
bool* p_open);
|
|
@ -13,15 +13,15 @@ static std::string leftpad(std::string str, size_t characters) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void main_scrolling_region(ImFont* monospace_font, std::vector<LogcatEntry>& filtered_logcat_entries) {
|
static inline void main_scrolling_region(ImFont* monospace_font, std::vector<LogcatEntry>& logcat_entries, std::vector<size_t>& filtered_logcat_entry_offsets) {
|
||||||
ImGui::PushFont(monospace_font);
|
ImGui::PushFont(monospace_font);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||||
|
|
||||||
ImGuiListClipper clipper;
|
ImGuiListClipper clipper;
|
||||||
clipper.Begin(static_cast<int>(filtered_logcat_entries.size()));
|
clipper.Begin(static_cast<int>(filtered_logcat_entry_offsets.size()));
|
||||||
while (clipper.Step()) {
|
while (clipper.Step()) {
|
||||||
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
|
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
|
||||||
LogcatEntry* logcat_entry = &filtered_logcat_entries[static_cast<size_t>(i)];
|
LogcatEntry* logcat_entry = &logcat_entries[filtered_logcat_entry_offsets[static_cast<size_t>(i)]];
|
||||||
char time_as_str[128] = {0};
|
char time_as_str[128] = {0};
|
||||||
strftime(time_as_str, 127 * sizeof(char), "%c", localtime(&logcat_entry->time));
|
strftime(time_as_str, 127 * sizeof(char), "%c", localtime(&logcat_entry->time));
|
||||||
|
|
||||||
|
@ -49,8 +49,11 @@ static inline void main_scrolling_region(ImFont* monospace_font, std::vector<Log
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void main_window(bool latest_log_entries_read, ImFont* monospace_font, std::vector<LogcatEntry>& filtered_logcat_entries,
|
void main_window(bool latest_log_entries_read, ImFont* monospace_font,
|
||||||
const Config& active_config, Config& inactive_config, bool* show_settings_window, bool* show_logs_window, bool* run_event_loop) {
|
std::vector<LogcatEntry>& logcat_entries, std::vector<size_t>& filtered_logcat_entry_offsets,
|
||||||
|
const Config& active_config, Config& inactive_config,
|
||||||
|
const Filters& active_filters, Filters& inactive_filters,
|
||||||
|
bool* show_settings_window, bool* show_filters_window, bool* show_logs_window, bool* run_event_loop) {
|
||||||
if (!ImGui::Begin("LogMeow", run_event_loop)) {
|
if (!ImGui::Begin("LogMeow", run_event_loop)) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
return;
|
return;
|
||||||
|
@ -61,6 +64,12 @@ void main_window(bool latest_log_entries_read, ImFont* monospace_font, std::vect
|
||||||
*show_settings_window = true;
|
*show_settings_window = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Filters") && !*show_filters_window) {
|
||||||
|
copy_filters(inactive_filters, active_filters);
|
||||||
|
*show_filters_window = true;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (!latest_log_entries_read) {
|
if (!latest_log_entries_read) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(0.0f, 0.6f, 0.6f));
|
ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(0.0f, 0.6f, 0.6f));
|
||||||
|
@ -79,7 +88,7 @@ void main_window(bool latest_log_entries_read, ImFont* monospace_font, std::vect
|
||||||
// and [SECTION] Example App: Long Text / ShowExampleAppLongText()
|
// and [SECTION] Example App: Long Text / ShowExampleAppLongText()
|
||||||
// and [SECTION] Example App: Debug Log / ShowExampleAppLog()
|
// and [SECTION] Example App: Debug Log / ShowExampleAppLog()
|
||||||
if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar)) {
|
if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||||
main_scrolling_region(monospace_font, filtered_logcat_entries);
|
main_scrolling_region(monospace_font, logcat_entries, filtered_logcat_entry_offsets);
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../config.h"
|
||||||
|
#include "../filters.h"
|
||||||
#include "../logcat_entry.h"
|
#include "../logcat_entry.h"
|
||||||
|
|
||||||
void main_window(bool latest_log_entries_read, ImFont* monospace_font, std::vector<LogcatEntry>& filtered_logcat_entries,
|
void main_window(bool latest_log_entries_read, ImFont* monospace_font,
|
||||||
const Config& active_config, Config& inactive_config, bool* show_settings_window, bool* show_logs_window, bool* run_event_loop);
|
std::vector<LogcatEntry>& logcat_entries, std::vector<size_t>& filtered_logcat_entry_offsets,
|
||||||
|
const Config& active_config, Config& inactive_config,
|
||||||
|
const Filters& active_filters, Filters& inactive_filters,
|
||||||
|
bool* show_settings_window, bool* show_filters_window, bool* show_logs_window, bool* run_event_loop);
|
||||||
|
|
|
@ -14,15 +14,12 @@ static void write_config_and_update_structures(const Config& config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void settings_window(Config& active_config, Config& inactive_config, bool* p_open) {
|
void settings_window(Config& active_config, Config& inactive_config, bool* p_open) {
|
||||||
ImGuiWindowFlags flags = active_config != inactive_config
|
if (!ImGui::Begin("Settings", p_open)) {
|
||||||
? ImGuiWindowFlags_UnsavedDocument
|
|
||||||
: 0;
|
|
||||||
if (!ImGui::Begin("Settings", p_open, flags)) {
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO actually have process control
|
// TODO actually have process control
|
||||||
ImGui::Text("Logcat command only takes effect when logcat is not running");
|
ImGui::TextUnformatted("Logcat command only takes effect when logcat is not running");
|
||||||
ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec uid'", &inactive_config.logcat_command);
|
ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec uid'", &inactive_config.logcat_command);
|
||||||
|
|
||||||
ImGui::Text("Font sizes only take effect when LogMeow is restarted");
|
ImGui::Text("Font sizes only take effect when LogMeow is restarted");
|
||||||
|
@ -34,7 +31,7 @@ void settings_window(Config& active_config, Config& inactive_config, bool* p_ope
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImVec2 button_size(4 * ImGui::GetFontSize(), 0);
|
ImVec2 button_size(4 * ImGui::GetFontSize(), 0);
|
||||||
if (ImGui::Button("OK", button_size)) {
|
if (ImGui::Button("OK", button_size)) {
|
||||||
active_config = inactive_config;
|
active_config = std::move(inactive_config);
|
||||||
write_config_and_update_structures(active_config);
|
write_config_and_update_structures(active_config);
|
||||||
*p_open = false;
|
*p_open = false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue