-
-
Notifications
You must be signed in to change notification settings - Fork 50
Port ProcessTreeView to GTK4 #525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
83bf4bf
35e7641
236bc00
21f5432
98b801f
780a157
1bd04dd
a05b0bd
ade573e
ee8b5e1
8e6d601
9907a24
b6c2933
d37b1e1
ead83cd
a0927f2
6b66db3
952481b
d5b2492
336bde8
af1686b
63e94ba
6424b20
92d3a60
0c921f1
932954a
131fe82
74615a0
d3265cd
c42c3bb
3922627
9833f56
ab9bee1
c8b3014
c07348f
111f4db
df026ba
6b80f21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
|
|
||
| /* This class holds data from Process class to use in the ColumnView */ | ||
| public class Monitor.ProcessRowData : GLib.Object { | ||
| public Icon icon { get; set; } | ||
| public string name { get; set; } | ||
| public int cpu { get; set; } | ||
| public uint64 memory { get; set; } | ||
| public int pid { get; set; } | ||
| public string cmd { get; set; } | ||
| public Gee.HashMap<string, Binding> bindings = new Gee.HashMap<string, Binding> (); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| public class Monitor.TreeViewFilter : GLib.Object { | ||
| private string _needle; | ||
| public string needle { | ||
| get { | ||
| return _needle; | ||
| } | ||
| set { | ||
| name_filter.search = value; | ||
| cmd_filter.search = value; | ||
|
Comment on lines
+8
to
+9
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about using Something like this (note that I've neither compiled nor tested this): public class Monitor.TreeViewFilter : GLib.Object {
public string needle { get; set; }
public Gtk.FilterListModel model_out;
private Gtk.AnyFilter any_filter;
private Gtk.CustomFilter pid_filter;
public TreeViewFilter (GLib.ListModel? model) {
var name_filter = build_str_filter ("name");
var cmd_filter = build_str_filter ("cmd");
// since the pid property is an int, we need to use a custom filter to convert it to a string
pid_filter = new Gtk.CustomFilter ((obj) => {
var item = (ProcessRowData) obj;
bool pid_found = item.pid.to_string ().contains (needle.casefold ()) || false;
return pid_found;
});
any_filter = new Gtk.AnyFilter ();
any_filter.append (name_filter);
any_filter.append (cmd_filter);
any_filter.append (pid_filter);
model_out = new Gtk.FilterListModel (model, any_filter);
bind_property ("needle", name_filter, "search", SYNC_CREATE);
bind_property ("needle", cmd_filter, "search", SYNC_CREATE);
}
private Gtk.StringFilter build_str_filter (string column_name) {
var expression = new Gtk.PropertyExpression (typeof (ProcessRowData), null, column_name);
return new Gtk.StringFilter (expression) {
ignore_case = true,
match_mode = SUBSTRING,
search = needle
};
}
} |
||
| _needle = value; | ||
| } | ||
| } | ||
| public Gtk.FilterListModel model_out; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't this be |
||
| private Gtk.AnyFilter any_filter; | ||
| private Gtk.StringFilter name_filter; | ||
| private Gtk.StringFilter cmd_filter; | ||
| private Gtk.CustomFilter pid_filter; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason this is a instance variable instead of a local variable in the constructor? It's not used out of the constructor. |
||
|
|
||
| public TreeViewFilter (GLib.ListModel? model) { | ||
| name_filter = build_str_filter ("name"); | ||
| cmd_filter = build_str_filter ("cmd"); | ||
|
|
||
| // since the pid property is an int, we need to use a custom filter to convert it to a string | ||
| pid_filter = new Gtk.CustomFilter ((obj) => { | ||
| var item = (ProcessRowData) obj; | ||
| bool pid_found = item.pid.to_string ().contains (needle.casefold ()) || false; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is But I'm fine as it is if you want to explicit this for readability. |
||
| return pid_found; | ||
| }); | ||
|
|
||
| any_filter = new Gtk.AnyFilter (); | ||
| any_filter.append (name_filter); | ||
| any_filter.append (cmd_filter); | ||
| any_filter.append (pid_filter); | ||
|
|
||
| model_out = new Gtk.FilterListModel (model, any_filter); | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably extra newline? Our code usually not have a newline at the beginning and ending of constructors/methods/classes.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| private Gtk.StringFilter build_str_filter (string column_name) { | ||
| var expression = new Gtk.PropertyExpression (typeof (ProcessRowData), null, column_name); | ||
| return new Gtk.StringFilter (expression) { | ||
| ignore_case = true, | ||
| match_mode = SUBSTRING, | ||
| search = needle | ||
| }; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,30 +3,50 @@ | |
| * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io) | ||
| */ | ||
|
|
||
| public enum Monitor.Column { | ||
| ICON, | ||
| NAME, | ||
| CPU, | ||
| MEMORY, | ||
| PID, | ||
| CMD | ||
| } | ||
| public class Monitor.TreeViewModel : GLib.Object { | ||
| private static GLib.Once<TreeViewModel> instance; | ||
| public static unowned TreeViewModel get_default () { | ||
| return instance.once (() => { return new TreeViewModel (); }); | ||
| } | ||
|
|
||
| public class Monitor.TreeViewModel : Gtk.TreeStore { | ||
| public ProcessManager process_manager; | ||
| private Gee.Map<int, Gtk.TreeIter ? > process_rows; | ||
|
|
||
| public TreeViewFilter filtered; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if Vala allows this, but can't we make this |
||
| public Gtk.SingleSelection selection_model; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't this be |
||
|
|
||
| public signal void added_first_row (); | ||
| public signal void process_selected (Process process); | ||
|
|
||
| public Gtk.Sorter sorter { | ||
| get { | ||
| return sorted.sorter; | ||
| } | ||
| set { | ||
| sorted.sorter = value; | ||
| } | ||
| } | ||
|
|
||
| private GLib.ListStore store; | ||
| private Gtk.SortListModel sorted; | ||
|
|
||
| private Gee.Map<int, ProcessRowData ?> process_rows; | ||
|
|
||
|
|
||
| construct { | ||
| process_rows = new Gee.HashMap<int, Gtk.TreeIter ? > (); | ||
|
|
||
| set_column_types (new Type[] { | ||
| typeof (string), | ||
| typeof (string), | ||
| typeof (double), | ||
| typeof (int64), | ||
| typeof (int), | ||
| typeof (string), | ||
| process_rows = new Gee.HashMap<int, ProcessRowData ?> (); | ||
| store = new GLib.ListStore (typeof (ProcessRowData)); | ||
| sorted = new Gtk.SortListModel (store, null); | ||
|
|
||
| filtered = new TreeViewFilter (sorted); | ||
|
|
||
| selection_model = new Gtk.SingleSelection (filtered.model_out) { | ||
| autoselect = true | ||
| }; | ||
|
|
||
| selection_model.notify["selected-item"].connect ((sender, property) => { | ||
| var row_data = (ProcessRowData) selection_model.get_selected_item (); | ||
| Process process = process_manager.get_process (row_data.pid); | ||
| process_selected (process); | ||
| }); | ||
|
|
||
| process_manager = ProcessManager.get_default (); | ||
|
|
@@ -37,6 +57,16 @@ public class Monitor.TreeViewModel : Gtk.TreeStore { | |
| Idle.add (() => { add_running_processes (); return false; }); | ||
| } | ||
|
|
||
| public Gtk.StringSorter str_sorter (string column_name) { | ||
| return new Gtk.StringSorter (new Gtk.PropertyExpression (typeof (ProcessRowData), null, column_name)); | ||
| } | ||
|
|
||
| public Gtk.NumericSorter num_sorter (string column_name) { | ||
| return new Gtk.NumericSorter (new Gtk.PropertyExpression (typeof (ProcessRowData), null, column_name)) { | ||
| sort_order = Gtk.SortType.DESCENDING | ||
| }; | ||
| } | ||
|
|
||
| private void add_running_processes () { | ||
| debug ("add_running_processes"); | ||
| var running_processes = process_manager.get_process_list (); | ||
|
|
@@ -49,46 +79,53 @@ public class Monitor.TreeViewModel : Gtk.TreeStore { | |
| if (process != null && !process_rows.has_key (process.stat.pid)) { | ||
| debug ("Add process %d Parent PID: %d", process.stat.pid, process.stat.ppid); | ||
| // add the process to the model | ||
| Gtk.TreeIter iter; | ||
| append (out iter, null); // null means top-level | ||
|
|
||
| // donno what is going on, but maybe just use a string instead of Icon ?? | ||
| // coz it lagz | ||
| // string icon_name = process.icon.to_string (); | ||
|
|
||
| set (iter, | ||
| Column.NAME, process.application_name, | ||
| Column.ICON, process.icon.to_string (), | ||
| Column.PID, process.stat.pid, | ||
| Column.CMD, process.command, | ||
| -1); | ||
| var row = new ProcessRowData () { | ||
| icon = process.icon, | ||
| name = process.application_name, | ||
| cpu = (int) process.cpu_percentage, | ||
| memory = process.mem_usage, | ||
| pid = process.stat.pid, | ||
| cmd = process.command | ||
| }; | ||
|
|
||
| store.append (row); | ||
|
|
||
| if (process_rows.size < 1) { | ||
| added_first_row (); | ||
| } | ||
| // add the process to our cache of process_rows | ||
| process_rows.set (process.stat.pid, iter); | ||
| process_rows.set (process.stat.pid, row); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| private void update_model () { | ||
| public void update_model () { | ||
|
Comment on lines
-75
to
+103
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this now a public method? |
||
| foreach (int pid in process_rows.keys) { | ||
| Process process = process_manager.get_process (pid); | ||
| Gtk.TreeIter iter = process_rows[pid]; | ||
| set (iter, | ||
| Column.CPU, process.cpu_percentage, | ||
| Column.MEMORY, process.mem_usage, | ||
| -1); | ||
| var process_row = process_rows.get (pid); | ||
|
|
||
| uint pos; | ||
| if (store.find (process_row, out pos)) { | ||
| var item = (ProcessRowData) store.get_item (pos); | ||
| item.cpu = (int) process.cpu_percentage; | ||
| item.memory = process.mem_usage; | ||
| sorter.changed (DIFFERENT); | ||
| } else { | ||
| debug ("Failed to find process row for pid %d", pid); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason this is
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also I recommend to use the early return pattern so that the expected route doesn't get too nested. Something like this: uint pos;
if (!store.find (process_row, out pos)) {
debug ("Failed to find process row for pid %d", pid);
return;
}
var item = (ProcessRowData) store.get_item (pos);
item.cpu = (int) process.cpu_percentage;
item.memory = process.mem_usage;
sorter.changed (DIFFERENT); |
||
| } | ||
| } | ||
| } | ||
|
|
||
| private void remove_process (int pid) { | ||
| debug ("remove process %d from model".printf (pid)); | ||
| // if process rows has pid | ||
| if (process_rows.has_key (pid)) { | ||
| var cached_iter = process_rows.get (pid); | ||
| remove (ref cached_iter); | ||
| uint pos; | ||
| var process_row = process_rows.get (pid); | ||
| if (store.find (process_row, out pos)) { | ||
| store.remove (pos); | ||
| } | ||
| process_rows.unset (pid); | ||
| } | ||
| } | ||
|
|
||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our source code in the elementary Organization usually have a copyright header here, so we should add it here. The same comment goes to the other new files added in this PR.