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