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).
This commit is contained in:
Hisham Muhammad 2014-04-09 18:02:50 -03:00
parent af4c412ebf
commit 19b438de10
4 changed files with 112 additions and 49 deletions

View File

@ -40,15 +40,19 @@ ObjectClass FunctionBar_class = {
FunctionBar* FunctionBar_new(const char** functions, const char** keys, int* events) { FunctionBar* FunctionBar_new(const char** functions, const char** keys, int* events) {
FunctionBar* this = AllocThis(FunctionBar); 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) { if (keys && events) {
this->staticData = false; this->staticData = false;
this->functions = malloc(sizeof(char*) * 15);
this->keys = malloc(sizeof(char*) * 15); this->keys = malloc(sizeof(char*) * 15);
this->events = malloc(sizeof(int) * 15); this->events = malloc(sizeof(int) * 15);
int i = 0; int i = 0;
while (i < 15 && functions[i]) { while (i < 15 && functions[i]) {
this->functions[i] = strdup(functions[i]);
this->keys[i] = strdup(keys[i]); this->keys[i] = strdup(keys[i]);
this->events[i] = events[i]; this->events[i] = events[i];
i++; i++;
@ -56,7 +60,6 @@ FunctionBar* FunctionBar_new(const char** functions, const char** keys, int* eve
this->size = i; this->size = i;
} else { } else {
this->staticData = true; this->staticData = true;
this->functions = (char**)( functions ? functions : FunctionBar_FLabels );
this->keys = (char**) FunctionBar_FKeys; this->keys = (char**) FunctionBar_FKeys;
this->events = FunctionBar_FEvents; this->events = FunctionBar_FEvents;
this->size = 10; this->size = 10;
@ -66,12 +69,14 @@ FunctionBar* FunctionBar_new(const char** functions, const char** keys, int* eve
void FunctionBar_delete(Object* cast) { void FunctionBar_delete(Object* cast) {
FunctionBar* this = (FunctionBar*) 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) { if (!this->staticData) {
for (int i = 0; i < this->size; i++) { for (int i = 0; i < this->size; i++) {
free(this->functions[i]);
free(this->keys[i]); free(this->keys[i]);
} }
free(this->functions);
free(this->keys); free(this->keys);
free(this->events); free(this->events);
} }
@ -79,7 +84,6 @@ void FunctionBar_delete(Object* cast) {
} }
void FunctionBar_setLabel(FunctionBar* this, int event, const char* text) { void FunctionBar_setLabel(FunctionBar* this, int event, const char* text) {
assert(!this->staticData);
for (int i = 0; i < this->size; i++) { for (int i = 0; i < this->size; i++) {
if (this->events[i] == event) { if (this->events[i] == event) {
free(this->functions[i]); free(this->functions[i]);

View File

@ -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 your previously selected sort view. Selecting a sort view will exit
tree view. tree view.
.TP .TP
.B F6, <, > .B F6
Select a field for sorting. The current sort field is indicated by a On sorted view, select a field for sorting, also accessible through < and >.
highlight in the header. 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 .TP
.B F7, ] .B F7, ]
Increase the selected process's priority (subtract from 'nice' value). Increase the selected process's priority (subtract from 'nice' value).

133
htop.c
View File

@ -48,6 +48,8 @@ static void printVersionFlag() {
exit(0); exit(0);
} }
static const char* defaultFunctions[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", "Nice -", "Nice +", "Kill ", "Quit ", NULL};
static void printHelpFlag() { static void printHelpFlag() {
fputs("htop " VERSION " - " COPYRIGHT "\n" fputs("htop " VERSION " - " COPYRIGHT "\n"
"Released under the GNU GPL.\n\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 = " H: ", .info = "hide/show user threads" },
{ .key = " K: ", .info = "hide/show kernel threads" }, { .key = " K: ", .info = "hide/show kernel threads" },
{ .key = " F: ", .info = "cursor follows process" }, { .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 = " P M T: ", .info = "sort by CPU%, MEM% or TIME" },
{ .key = " I: ", .info = "invert sort order" }, { .key = " I: ", .info = "invert sort order" },
{ .key = " F6 >: ", .info = "select sort column" }, { .key = " F6 >: ", .info = "select sort column" },
@ -268,10 +270,24 @@ static bool setUserOnly(const char* userName, bool* userOnly, uid_t* userId) {
return false; 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->sortKey = sortKey;
pl->direction = 1; pl->direction = 1;
pl->treeView = false; setTreeView(pl, fuBar, false);
settings->changed = true; settings->changed = true;
ProcessList_printHeader(pl, Panel_getHeader(panel)); 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 main(int argc, char** argv) {
int delay = -1; int delay = -1;
@ -437,17 +482,16 @@ int main(int argc, char** argv) {
Panel* panel = Panel_new(0, headerHeight, COLS, LINES - headerHeight - 2, false, &Process_class); Panel* panel = Panel_new(0, headerHeight, COLS, LINES - headerHeight - 2, false, &Process_class);
ProcessList_setPanel(pl, panel); ProcessList_setPanel(pl, panel);
FunctionBar* defaultBar = FunctionBar_new(defaultFunctions, NULL, NULL);
setTreeView(pl, defaultBar, pl->treeView);
if (sortKey > 0) { if (sortKey > 0) {
pl->sortKey = sortKey; pl->sortKey = sortKey;
pl->treeView = false; setTreeView(pl, defaultBar, false);
pl->direction = 1; pl->direction = 1;
} }
ProcessList_printHeader(pl, Panel_getHeader(panel)); 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); IncSet* inc = IncSet_new(defaultBar);
ProcessList_scan(pl); ProcessList_scan(pl);
@ -468,6 +512,8 @@ int main(int argc, char** argv) {
bool idle = false; bool idle = false;
bool collapsed = false;
while (!quit) { while (!quit) {
gettimeofday(&tv, NULL); gettimeofday(&tv, NULL);
newTime = ((double)tv.tv_sec * 10) + ((double)tv.tv_usec / 100000); newTime = ((double)tv.tv_sec * 10) + ((double)tv.tv_usec / 100000);
@ -491,8 +537,23 @@ int main(int argc, char** argv) {
} }
doRefresh = true; doRefresh = true;
if (!idle) 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) {
Panel_draw(panel, true); Panel_draw(panel, true);
}
int prev = ch; int prev = ch;
if (inc->active) if (inc->active)
@ -524,9 +585,9 @@ int main(int argc, char** argv) {
ProcessField field = ProcessList_keyAt(pl, x); ProcessField field = ProcessList_keyAt(pl, x);
if (field == pl->sortKey) { if (field == pl->sortKey) {
ProcessList_invertSortOrder(pl); ProcessList_invertSortOrder(pl);
pl->treeView = false; setTreeView(pl, defaultBar, false);
} else { } else {
setSortKey(pl, field, panel, settings); setSortKey(pl, defaultBar, field, panel, settings);
} }
refreshTimeout = 0; refreshTimeout = 0;
continue; continue;
@ -580,13 +641,13 @@ int main(int argc, char** argv) {
case 'M': case 'M':
{ {
refreshTimeout = 0; refreshTimeout = 0;
setSortKey(pl, PERCENT_MEM, panel, settings); setSortKey(pl, defaultBar, PERCENT_MEM, panel, settings);
break; break;
} }
case 'T': case 'T':
{ {
refreshTimeout = 0; refreshTimeout = 0;
setSortKey(pl, TIME, panel, settings); setSortKey(pl, defaultBar, TIME, panel, settings);
break; break;
} }
case 'c': case 'c':
@ -608,7 +669,7 @@ int main(int argc, char** argv) {
case 'P': case 'P':
{ {
refreshTimeout = 0; refreshTimeout = 0;
setSortKey(pl, PERCENT_CPU, panel, settings); setSortKey(pl, defaultBar, PERCENT_CPU, panel, settings);
break; break;
} }
case KEY_F(1): case KEY_F(1):
@ -704,11 +765,10 @@ int main(int argc, char** argv) {
case '=': case '=':
case '-': case '-':
{ {
Process* p = (Process*) Panel_getSelected(panel); if (expandCollapse(panel)) {
if (!p) break; doRecalculate = true;
p->showChildren = !p->showChildren; refreshTimeout = 0;
refreshTimeout = 0; }
doRecalculate = true;
break; break;
} }
case KEY_F(9): case KEY_F(9):
@ -764,31 +824,25 @@ int main(int argc, char** argv) {
break; break;
case '<': case '<':
case ',': case ',':
case KEY_F(18):
case '>': case '>':
case '.': case '.':
{
sortBy(panel, pl, settings, headerHeight, defaultBar, header);
refreshTimeout = 0;
break;
}
case KEY_F(18):
case KEY_F(6): case KEY_F(6):
{ {
Panel* sortPanel = Panel_new(0, 0, 0, 0, true, Class(ListItem)); if (pl->treeView) {
Panel_setHeader(sortPanel, "Sort by"); if (expandCollapse(panel)) {
const char* fuFunctions[] = {"Sort ", "Cancel ", NULL}; doRecalculate = true;
ProcessField* fields = pl->fields; refreshTimeout = 0;
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);
} else { } else {
ProcessList_printHeader(pl, Panel_getHeader(panel)); sortBy(panel, pl, settings, headerHeight, defaultBar, header);
refreshTimeout = 0;
} }
Object_delete(sortPanel);
refreshTimeout = 0;
break; break;
} }
case 'i': case 'i':
@ -842,7 +896,8 @@ int main(int argc, char** argv) {
case 't': case 't':
case KEY_F(5): case KEY_F(5):
refreshTimeout = 0; refreshTimeout = 0;
pl->treeView = !pl->treeView; collapsed = false;
setTreeView(pl, defaultBar, !pl->treeView);
if (pl->treeView) pl->direction = 1; if (pl->treeView) pl->direction = 1;
ProcessList_printHeader(pl, Panel_getHeader(panel)); ProcessList_printHeader(pl, Panel_getHeader(panel));
ProcessList_expandTree(pl); ProcessList_expandTree(pl);

2
htop.h
View File

@ -15,6 +15,8 @@ in the source distribution for its full text.
typedef bool(*ForeachProcessFn)(Process*, size_t); 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); int main(int argc, char** argv);
#endif #endif