From 19b438de1009047ee425d1f36207c20b9c80d43a Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Wed, 9 Apr 2014 18:02:50 -0300 Subject: [PATCH] Improve discoverability of the expand/collapse feature. It is now accessible via F6 when on tree view (as a bonus, it is now also reachable via the mouse). The function bar now dynamically changes to reflect the toggle nature of the tree-view mode (F5) and the F6 key serves as expand/collapse when on tree mode, and its previous behavior of bringing up the "Sort By" menu (which only made sense on non-tree mode). Users wishing to go to the "Sort By" menu straight from Tree View can still do so with the "<" and ">" keys (the top-compatible keys for sort selection). --- FunctionBar.c | 18 ++++--- htop.1.in | 8 +-- htop.c | 133 +++++++++++++++++++++++++++++++++++--------------- htop.h | 2 + 4 files changed, 112 insertions(+), 49 deletions(-) diff --git a/FunctionBar.c b/FunctionBar.c index 66b9843f..1c6066ae 100644 --- a/FunctionBar.c +++ b/FunctionBar.c @@ -40,15 +40,19 @@ ObjectClass FunctionBar_class = { FunctionBar* FunctionBar_new(const char** functions, const char** keys, int* events) { FunctionBar* this = AllocThis(FunctionBar); - this->functions = (char**) functions; + this->functions = calloc(16, sizeof(char*)); + if (!functions) { + functions = FunctionBar_FLabels; + } + for (int i = 0; i < 15 && functions[i]; i++) { + this->functions[i] = strdup(functions[i]); + } if (keys && events) { this->staticData = false; - this->functions = malloc(sizeof(char*) * 15); this->keys = malloc(sizeof(char*) * 15); this->events = malloc(sizeof(int) * 15); int i = 0; while (i < 15 && functions[i]) { - this->functions[i] = strdup(functions[i]); this->keys[i] = strdup(keys[i]); this->events[i] = events[i]; i++; @@ -56,7 +60,6 @@ FunctionBar* FunctionBar_new(const char** functions, const char** keys, int* eve this->size = i; } else { this->staticData = true; - this->functions = (char**)( functions ? functions : FunctionBar_FLabels ); this->keys = (char**) FunctionBar_FKeys; this->events = FunctionBar_FEvents; this->size = 10; @@ -66,12 +69,14 @@ FunctionBar* FunctionBar_new(const char** functions, const char** keys, int* eve void FunctionBar_delete(Object* cast) { FunctionBar* this = (FunctionBar*) cast; + for (int i = 0; i < 15 && this->functions[i]; i++) { + free(this->functions[i]); + } + free(this->functions); if (!this->staticData) { for (int i = 0; i < this->size; i++) { - free(this->functions[i]); free(this->keys[i]); } - free(this->functions); free(this->keys); free(this->events); } @@ -79,7 +84,6 @@ void FunctionBar_delete(Object* cast) { } void FunctionBar_setLabel(FunctionBar* this, int event, const char* text) { - assert(!this->staticData); for (int i = 0; i < this->size; i++) { if (this->events[i] == event) { free(this->functions[i]); diff --git a/htop.1.in b/htop.1.in index 5b2d754e..a0e2a261 100644 --- a/htop.1.in +++ b/htop.1.in @@ -91,9 +91,11 @@ between them as a tree. Toggling the key will switch between tree and your previously selected sort view. Selecting a sort view will exit tree view. .TP -.B F6, <, > -Select a field for sorting. The current sort field is indicated by a -highlight in the header. +.B F6 +On sorted view, select a field for sorting, also accessible through < and >. +The current sort field is indicated by a highlight in the header. +On tree view, expand or collapse the current subtree. A "+" indicator in the +tree node indicates that it is collapsed. .TP .B F7, ] Increase the selected process's priority (subtract from 'nice' value). diff --git a/htop.c b/htop.c index 48fc6fc7..94ff3a7c 100644 --- a/htop.c +++ b/htop.c @@ -48,6 +48,8 @@ static void printVersionFlag() { exit(0); } +static const char* defaultFunctions[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", "Nice -", "Nice +", "Kill ", "Quit ", NULL}; + static void printHelpFlag() { fputs("htop " VERSION " - " COPYRIGHT "\n" "Released under the GNU GPL.\n\n" @@ -76,7 +78,7 @@ static struct { const char* key; const char* info; } helpLeft[] = { { .key = " H: ", .info = "hide/show user threads" }, { .key = " K: ", .info = "hide/show kernel threads" }, { .key = " F: ", .info = "cursor follows process" }, - { .key = " + -: ", .info = "expand/collapse tree" }, + { .key = " F6 + -: ", .info = "expand/collapse tree" }, { .key = " P M T: ", .info = "sort by CPU%, MEM% or TIME" }, { .key = " I: ", .info = "invert sort order" }, { .key = " F6 >: ", .info = "select sort column" }, @@ -268,10 +270,24 @@ static bool setUserOnly(const char* userName, bool* userOnly, uid_t* userId) { return false; } -static inline void setSortKey(ProcessList* pl, ProcessField sortKey, Panel* panel, Settings* settings) { +static void setTreeView(ProcessList* pl, FunctionBar* fuBar, bool mode) { + if (mode) { + FunctionBar_setLabel(fuBar, KEY_F(5), "Sorted"); + FunctionBar_setLabel(fuBar, KEY_F(6), "Collap"); + } else { + FunctionBar_setLabel(fuBar, KEY_F(5), "Tree "); + FunctionBar_setLabel(fuBar, KEY_F(6), "SortBy"); + } + if (mode != pl->treeView) { + FunctionBar_draw(fuBar, NULL); + } + pl->treeView = mode; +} + +static inline void setSortKey(ProcessList* pl, FunctionBar* fuBar, ProcessField sortKey, Panel* panel, Settings* settings) { pl->sortKey = sortKey; pl->direction = 1; - pl->treeView = false; + setTreeView(pl, fuBar, false); settings->changed = true; ProcessList_printHeader(pl, Panel_getHeader(panel)); } @@ -294,6 +310,35 @@ static void tagAllChildren(Panel* panel, Process* parent) { } } +static bool expandCollapse(Panel* panel) { + Process* p = (Process*) Panel_getSelected(panel); + if (!p) return false; + p->showChildren = !p->showChildren; + return true; +} + +void sortBy(Panel* panel, ProcessList* pl, Settings* settings, int headerHeight, FunctionBar* defaultBar, Header* header) { + Panel* sortPanel = Panel_new(0, 0, 0, 0, true, Class(ListItem)); + Panel_setHeader(sortPanel, "Sort by"); + const char* fuFunctions[] = {"Sort ", "Cancel ", NULL}; + ProcessField* fields = pl->fields; + for (int i = 0; fields[i]; i++) { + char* name = String_trim(Process_fieldNames[fields[i]]); + Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i])); + if (fields[i] == pl->sortKey) + Panel_setSelected(sortPanel, i); + free(name); + } + ListItem* field = (ListItem*) pickFromVector(panel, sortPanel, 15, headerHeight, fuFunctions, defaultBar, header); + if (field) { + settings->changed = true; + setSortKey(pl, defaultBar, field->key, panel, settings); + } else { + ProcessList_printHeader(pl, Panel_getHeader(panel)); + } + Object_delete(sortPanel); +} + int main(int argc, char** argv) { int delay = -1; @@ -437,16 +482,15 @@ int main(int argc, char** argv) { Panel* panel = Panel_new(0, headerHeight, COLS, LINES - headerHeight - 2, false, &Process_class); ProcessList_setPanel(pl, panel); + FunctionBar* defaultBar = FunctionBar_new(defaultFunctions, NULL, NULL); + setTreeView(pl, defaultBar, pl->treeView); + if (sortKey > 0) { pl->sortKey = sortKey; - pl->treeView = false; + setTreeView(pl, defaultBar, false); pl->direction = 1; } ProcessList_printHeader(pl, Panel_getHeader(panel)); - - const char* defaultFunctions[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", - "SortBy", "Nice -", "Nice +", "Kill ", "Quit ", NULL}; - FunctionBar* defaultBar = FunctionBar_new(defaultFunctions, NULL, NULL); IncSet* inc = IncSet_new(defaultBar); @@ -468,6 +512,8 @@ int main(int argc, char** argv) { bool idle = false; + bool collapsed = false; + while (!quit) { gettimeofday(&tv, NULL); newTime = ((double)tv.tv_sec * 10) + ((double)tv.tv_usec / 100000); @@ -490,9 +536,24 @@ int main(int argc, char** argv) { idle = false; } doRefresh = true; + + if (pl->treeView) { + Process* p = (Process*) Panel_getSelected(panel); + if (p) { + if (!p->showChildren && !collapsed) { + FunctionBar_setLabel(defaultBar, KEY_F(6), "Expand"); + FunctionBar_draw(defaultBar, NULL); + } else if (p->showChildren && collapsed) { + FunctionBar_setLabel(defaultBar, KEY_F(6), "Collap"); + FunctionBar_draw(defaultBar, NULL); + } + collapsed = !p->showChildren; + } + } - if (!idle) + if (!idle) { Panel_draw(panel, true); + } int prev = ch; if (inc->active) @@ -524,9 +585,9 @@ int main(int argc, char** argv) { ProcessField field = ProcessList_keyAt(pl, x); if (field == pl->sortKey) { ProcessList_invertSortOrder(pl); - pl->treeView = false; + setTreeView(pl, defaultBar, false); } else { - setSortKey(pl, field, panel, settings); + setSortKey(pl, defaultBar, field, panel, settings); } refreshTimeout = 0; continue; @@ -580,13 +641,13 @@ int main(int argc, char** argv) { case 'M': { refreshTimeout = 0; - setSortKey(pl, PERCENT_MEM, panel, settings); + setSortKey(pl, defaultBar, PERCENT_MEM, panel, settings); break; } case 'T': { refreshTimeout = 0; - setSortKey(pl, TIME, panel, settings); + setSortKey(pl, defaultBar, TIME, panel, settings); break; } case 'c': @@ -608,7 +669,7 @@ int main(int argc, char** argv) { case 'P': { refreshTimeout = 0; - setSortKey(pl, PERCENT_CPU, panel, settings); + setSortKey(pl, defaultBar, PERCENT_CPU, panel, settings); break; } case KEY_F(1): @@ -704,11 +765,10 @@ int main(int argc, char** argv) { case '=': case '-': { - Process* p = (Process*) Panel_getSelected(panel); - if (!p) break; - p->showChildren = !p->showChildren; - refreshTimeout = 0; - doRecalculate = true; + if (expandCollapse(panel)) { + doRecalculate = true; + refreshTimeout = 0; + } break; } case KEY_F(9): @@ -764,31 +824,25 @@ int main(int argc, char** argv) { break; case '<': case ',': - case KEY_F(18): case '>': case '.': + { + sortBy(panel, pl, settings, headerHeight, defaultBar, header); + refreshTimeout = 0; + break; + } + case KEY_F(18): case KEY_F(6): { - Panel* sortPanel = Panel_new(0, 0, 0, 0, true, Class(ListItem)); - Panel_setHeader(sortPanel, "Sort by"); - const char* fuFunctions[] = {"Sort ", "Cancel ", NULL}; - ProcessField* fields = pl->fields; - for (int i = 0; fields[i]; i++) { - char* name = String_trim(Process_fieldNames[fields[i]]); - Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i])); - if (fields[i] == pl->sortKey) - Panel_setSelected(sortPanel, i); - free(name); - } - ListItem* field = (ListItem*) pickFromVector(panel, sortPanel, 15, headerHeight, fuFunctions, defaultBar, header); - if (field) { - settings->changed = true; - setSortKey(pl, field->key, panel, settings); + if (pl->treeView) { + if (expandCollapse(panel)) { + doRecalculate = true; + refreshTimeout = 0; + } } else { - ProcessList_printHeader(pl, Panel_getHeader(panel)); + sortBy(panel, pl, settings, headerHeight, defaultBar, header); + refreshTimeout = 0; } - Object_delete(sortPanel); - refreshTimeout = 0; break; } case 'i': @@ -842,7 +896,8 @@ int main(int argc, char** argv) { case 't': case KEY_F(5): refreshTimeout = 0; - pl->treeView = !pl->treeView; + collapsed = false; + setTreeView(pl, defaultBar, !pl->treeView); if (pl->treeView) pl->direction = 1; ProcessList_printHeader(pl, Panel_getHeader(panel)); ProcessList_expandTree(pl); diff --git a/htop.h b/htop.h index 7338010e..354a2f99 100644 --- a/htop.h +++ b/htop.h @@ -15,6 +15,8 @@ in the source distribution for its full text. typedef bool(*ForeachProcessFn)(Process*, size_t); +void sortBy(Panel* panel, ProcessList* pl, Settings* settings, int headerHeight, FunctionBar* defaultBar, Header* header); + int main(int argc, char** argv); #endif