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(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 | ||||
|     windows/logs.cpp windows/settings.cpp windows/main.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 | ||||
|     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 | ||||
|     imgui/misc/cpp/imgui_stdlib.cpp imgui/misc/freetype/imgui_freetype.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\"'") | ||||
| # 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/ | ||||
| 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(); | ||||
| Config load_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 "config.h" | ||||
| #include "filters.h" | ||||
| #include "logcat_thread.h" | ||||
| 
 | ||||
| #include "windows/logs.h" | ||||
| #include "windows/filters.h" | ||||
| #include "windows/settings.h" | ||||
| #include "windows/main.h" | ||||
| #ifndef NDEBUG | ||||
|     #include "windows/debug.h" | ||||
| #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; | ||||
| 
 | ||||
|     while ((logcat_thread_item = logcat_thread.atomic_ring_buffer.get())) { | ||||
|         if (std::holds_alternative<std::string>(*logcat_thread_item)) { | ||||
|             log_raw(std::move(std::get<std::string>(*logcat_thread_item)), false); | ||||
|         } 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 { | ||||
|             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 Filters inactive_filters; | ||||
|     static bool show_settings_window = false; | ||||
|     static bool show_filters_window = false; | ||||
|     static bool show_logs_window = false; | ||||
|     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 | ||||
|     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); | ||||
|     } | ||||
| 
 | ||||
|     if (show_filters_window) { | ||||
|         filters_window(active_filters, inactive_filters, logcat_entries, filtered_logcat_entry_offsets, &show_filters_window); | ||||
|     } | ||||
| 
 | ||||
|     if (show_logs_window) { | ||||
|         bool autoscrolling = false; | ||||
|         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
 | ||||
|     main_window(log_entries_read == log_entries.size(), | ||||
|         monospace_font, filtered_logcat_entries, | ||||
|         active_config, inactive_config, &show_settings_window, | ||||
|         &show_logs_window, run_event_loop); | ||||
|     main_window(log_entries_read == log_entries.size(), monospace_font, | ||||
|         logcat_entries, filtered_logcat_entry_offsets, | ||||
|         active_config, inactive_config, | ||||
|         active_filters, inactive_filters, | ||||
|         &show_settings_window, &show_filters_window, &show_logs_window, run_event_loop); | ||||
| } | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| #include <imgui.h> | ||||
| 
 | ||||
| #include "config.h" | ||||
| #include "filters.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 "fonts.h" | ||||
| #include "config.h" | ||||
| #include "filters.h" | ||||
| #include "event_loop.h" | ||||
| #include "logcat_thread.h" | ||||
| 
 | ||||
| int main(int, char**) { | ||||
|     setlocale(LC_TIME, ""); | ||||
| 
 | ||||
|     // TODO add saving
 | ||||
|     Filters filters; | ||||
|     Config config; | ||||
|     try { | ||||
|         config = load_config(); | ||||
|  | @ -161,7 +164,7 @@ int main(int, char**) { | |||
|         ImGui_ImplSDL2_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
 | ||||
|         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; | ||||
| } | ||||
| 
 | ||||
| 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::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); | ||||
| 
 | ||||
|     ImGuiListClipper clipper; | ||||
|     clipper.Begin(static_cast<int>(filtered_logcat_entries.size())); | ||||
|     clipper.Begin(static_cast<int>(filtered_logcat_entry_offsets.size())); | ||||
|     while (clipper.Step()) { | ||||
|         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}; | ||||
|             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, | ||||
|         const Config& active_config, Config& inactive_config, bool* show_settings_window, bool* show_logs_window, bool* run_event_loop) { | ||||
| void main_window(bool latest_log_entries_read, ImFont* monospace_font, | ||||
|         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)) { | ||||
|         ImGui::End(); | ||||
|         return; | ||||
|  | @ -61,6 +64,12 @@ void main_window(bool latest_log_entries_read, ImFont* monospace_font, std::vect | |||
|         *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(); | ||||
|     if (!latest_log_entries_read) { | ||||
|         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: Debug Log / ShowExampleAppLog()
 | ||||
|     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::End(); | ||||
|  |  | |||
|  | @ -4,7 +4,11 @@ | |||
| #include <vector> | ||||
| 
 | ||||
| #include "../config.h" | ||||
| #include "../filters.h" | ||||
| #include "../logcat_entry.h" | ||||
| 
 | ||||
| void main_window(bool latest_log_entries_read, ImFont* monospace_font, std::vector<LogcatEntry>& filtered_logcat_entries, | ||||
|         const Config& active_config, Config& inactive_config, bool* show_settings_window, bool* show_logs_window, bool* run_event_loop); | ||||
| void main_window(bool latest_log_entries_read, ImFont* monospace_font, | ||||
|         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) { | ||||
|     ImGuiWindowFlags flags = active_config != inactive_config | ||||
|         ? ImGuiWindowFlags_UnsavedDocument | ||||
|         : 0; | ||||
|     if (!ImGui::Begin("Settings", p_open, flags)) { | ||||
|     if (!ImGui::Begin("Settings", p_open)) { | ||||
|         ImGui::End(); | ||||
|         return; | ||||
|     } | ||||
|     // 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::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(); | ||||
|     ImVec2 button_size(4 * ImGui::GetFontSize(), 0); | ||||
|     if (ImGui::Button("OK", button_size)) { | ||||
|         active_config = inactive_config; | ||||
|         active_config = std::move(inactive_config); | ||||
|         write_config_and_update_structures(active_config); | ||||
|         *p_open = false; | ||||
|     } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue