mirror of https://github.com/xzeldon/htop.git
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:
parent
af4c412ebf
commit
19b438de10
|
@ -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;
|
||||||
if (!this->staticData) {
|
for (int i = 0; i < 15 && this->functions[i]; i++) {
|
||||||
for (int i = 0; i < this->size; i++) {
|
|
||||||
free(this->functions[i]);
|
free(this->functions[i]);
|
||||||
free(this->keys[i]);
|
|
||||||
}
|
}
|
||||||
free(this->functions);
|
free(this->functions);
|
||||||
|
if (!this->staticData) {
|
||||||
|
for (int i = 0; i < this->size; i++) {
|
||||||
|
free(this->keys[i]);
|
||||||
|
}
|
||||||
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]);
|
||||||
|
|
|
@ -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
133
htop.c
|
@ -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;
|
|
||||||
p->showChildren = !p->showChildren;
|
|
||||||
refreshTimeout = 0;
|
|
||||||
doRecalculate = true;
|
doRecalculate = true;
|
||||||
|
refreshTimeout = 0;
|
||||||
|
}
|
||||||
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;
|
|
||||||
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 {
|
|
||||||
ProcessList_printHeader(pl, Panel_getHeader(panel));
|
|
||||||
}
|
|
||||||
Object_delete(sortPanel);
|
|
||||||
refreshTimeout = 0;
|
refreshTimeout = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sortBy(panel, pl, settings, headerHeight, defaultBar, header);
|
||||||
|
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
2
htop.h
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue