From 5a912bdeca4a8b2d1dc1f5467db8ad2e89693ed5 Mon Sep 17 00:00:00 2001 From: ugolnikovE Date: Sun, 10 May 2026 22:46:07 +0300 Subject: [PATCH] header: remember meter positions across layout changes When the user reduces the number of header columns, keep the hidden columns allocated in memory instead of collapsing them into the last visible column. This allows restoring meter positions when switching back to the original layout. Fixes #1941 Signed-off-by: ugolnikovE --- AvailableMetersPanel.c | 2 + CategoriesPanel.c | 24 ++++++ Header.c | 161 +++++++++++++++++++++++++++-------------- Header.h | 7 ++ MetersPanel.c | 20 ++++- 5 files changed, 155 insertions(+), 59 deletions(-) diff --git a/AvailableMetersPanel.c b/AvailableMetersPanel.c index ad1fbf33d..aadbd3d81 100644 --- a/AvailableMetersPanel.c +++ b/AvailableMetersPanel.c @@ -79,6 +79,8 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) { } if (update) { + Header_collapseLayout(this->header); + this->header->metersCopied = false; Settings* settings = this->host->settings; settings->changed = true; settings->lastUpdate++; diff --git a/CategoriesPanel.c b/CategoriesPanel.c index 530093c74..94a491775 100644 --- a/CategoriesPanel.c +++ b/CategoriesPanel.c @@ -36,6 +36,10 @@ static const char* const CategoriesFunctions[] = {" ", " ", " ", static void CategoriesPanel_delete(Object* object) { CategoriesPanel* this = (CategoriesPanel*) object; + if (this->header->metersCopied) { + Header_undoMetersCopy(this->header); + this->header->metersCopied = false; + } Panel_done(&this->super); free(this); } @@ -45,6 +49,20 @@ static void CategoriesPanel_makeMetersPage(CategoriesPanel* this) { MetersPanel** meterPanels = xMallocArray(columns, sizeof(MetersPanel*)); Settings* settings = this->host->settings; + size_t hiddenMeterCount = 0; + for (size_t i = columns; i < this->header->maxColumns; i++) { + hiddenMeterCount += Vector_size(this->header->columns[i]); + } + + if (hiddenMeterCount > 0) { + this->header->columns[columns - 1]->owner = false; + for (size_t i = columns; i < this->header->maxColumns; i++) { + Vector_splice(this->header->columns[columns - 1], this->header->columns[i]); + } + this->header->columns[columns - 1]->owner = true; + } + this->header->metersCopied = hiddenMeterCount > 0; + for (size_t i = 0; i < columns; i++) { char titleBuffer[32]; xSnprintf(titleBuffer, sizeof(titleBuffer), "Column %zu", i + 1); @@ -151,6 +169,12 @@ static HandlerResult CategoriesPanel_eventHandler(Panel* super, int ch) { } if (result == HANDLED) { int size = ScreenManager_size(this->scr); + + if (this->header->metersCopied) { + Header_undoMetersCopy(this->header); + this->header->metersCopied = false; + } + for (int i = 1; i < size; i++) ScreenManager_remove(this->scr, 1); diff --git a/Header.c b/Header.c index 7447f7189..a20839b8c 100644 --- a/Header.c +++ b/Header.c @@ -12,6 +12,7 @@ in the source distribution for its full text. #include #include #include +#include #include #include #include @@ -20,17 +21,20 @@ in the source distribution for its full text. #include "CRT.h" #include "CPUMeter.h" #include "DynamicMeter.h" +#include "HeaderLayout.h" #include "Macros.h" #include "Object.h" #include "Platform.h" #include "ProvideCurses.h" #include "Settings.h" +#include "Vector.h" #include "XUtils.h" Header* Header_new(Machine* host, HeaderLayout hLayout) { Header* this = xCalloc(1, sizeof(Header)); this->columns = xMallocArray(HeaderLayout_getColumns(hLayout), sizeof(Vector*)); + this->maxColumns = HeaderLayout_getColumns(hLayout); this->headerLayout = hLayout; this->host = host; @@ -42,7 +46,7 @@ Header* Header_new(Machine* host, HeaderLayout hLayout) { } void Header_delete(Header* this) { - Header_forEachColumn(this, i) { + for (size_t i = 0; i < this->maxColumns; i++) { Vector_delete(this->columns[i]); } @@ -59,24 +63,47 @@ void Header_setLayout(Header* this, HeaderLayout hLayout) { if (newColumns == oldColumns) return; - if (newColumns > oldColumns) { + if (newColumns > this->maxColumns) { this->columns = xReallocArray(this->columns, newColumns, sizeof(Vector*)); - for (size_t i = oldColumns; i < newColumns; i++) + for (size_t i = this->maxColumns; i < newColumns; i++) this->columns[i] = Vector_new(Class(Meter), true, VECTOR_DEFAULT_SIZE); - } else { - // move meters from to-be-deleted columns into last one - for (size_t i = newColumns; i < oldColumns; i++) { - for (int j = this->columns[i]->items - 1; j >= 0; j--) { - Vector_add(this->columns[newColumns - 1], Vector_take(this->columns[i], j)); - } - Vector_delete(this->columns[i]); - } - this->columns = xReallocArray(this->columns, newColumns, sizeof(Vector*)); + this->maxColumns = newColumns; } Header_calculateHeight(this); } +void Header_undoMetersCopy(Header* this) { + size_t currentColumns = HeaderLayout_getColumns(this->headerLayout); + size_t maxColumns = this->maxColumns; + + size_t duplicateMeters = 0; + for (size_t i = currentColumns; i < maxColumns; i++) { + duplicateMeters += Vector_size(this->columns[i]); + } + + Vector* lastColumn = this->columns[currentColumns - 1]; + lastColumn->owner = false; + + for (size_t j = 0; j < duplicateMeters; j++) { + Vector_remove(lastColumn, Vector_size(lastColumn) - 1); + } + lastColumn->owner = true; +} + +void Header_collapseLayout(Header* this) { + size_t currentColumns = HeaderLayout_getColumns(this->headerLayout); + size_t maxColumns = this->maxColumns; + + for (size_t i = currentColumns; i < maxColumns; i++) { + Vector* column = this->columns[i]; + column->owner = false; + Vector_delete(column); + } + this->columns = xReallocArray(this->columns, currentColumns, sizeof(Vector*)); + this->maxColumns = currentColumns; +} + static void Header_addMeterByName(Header* this, const char* name, MeterModeId mode, size_t column) { assert(column < HeaderLayout_getColumns(this->headerLayout)); @@ -135,6 +162,7 @@ void Header_populateFromSettings(Header* this) { void Header_writeBackToSettings(const Header* this) { Settings* settings = this->host->settings; Settings_setHeaderLayout(settings, this->headerLayout); + const size_t numCols = HeaderLayout_getColumns(this->headerLayout); Header_forEachColumn(this, col) { MeterColumnSetting* colSettings = &settings->hColumns[col]; @@ -146,27 +174,38 @@ void Header_writeBackToSettings(const Header* this) { } free(colSettings->modes); - const Vector* vec = this->columns[col]; - int len = Vector_size(vec); + size_t saveCol = col; + int len = 0; + do { + len += Vector_size(this->columns[saveCol++]); + } while ((col == numCols - 1) && (saveCol < this->maxColumns) && !this->metersCopied); colSettings->names = len ? xCalloc(len + 1, sizeof(*colSettings->names)) : NULL; colSettings->modes = len ? xCalloc(len, sizeof(*colSettings->modes)) : NULL; colSettings->len = len; - for (int i = 0; i < len; i++) { - const Meter* meter = (Meter*) Vector_get(vec, i); - char* name = NULL; - if (meter->param && As_Meter(meter) == &DynamicMeter_class) { - const char* dynamic = DynamicMeter_lookup(settings->dynamicMeters, meter->param); - xAsprintf(&name, "%s(%s)", As_Meter(meter)->name, dynamic); - } else if (meter->param && As_Meter(meter) == &CPUMeter_class) { - xAsprintf(&name, "%s(%u)", As_Meter(meter)->name, meter->param); - } else { - xAsprintf(&name, "%s", As_Meter(meter)->name); + int idx = 0; + saveCol = col; + if (!len) + continue; + do { + const Vector* vec = this->columns[saveCol++]; + for (int i = 0; i < Vector_size(vec); i++) { + const Meter* meter = (Meter*) Vector_get(vec, i); + char* name = NULL; + if (meter->param && As_Meter(meter) == &DynamicMeter_class) { + const char* dynamic = DynamicMeter_lookup(settings->dynamicMeters, meter->param); + xAsprintf(&name, "%s(%s)", As_Meter(meter)->name, dynamic); + } else if (meter->param && As_Meter(meter) == &CPUMeter_class) { + xAsprintf(&name, "%s(%u)", As_Meter(meter)->name, meter->param); + } else { + xAsprintf(&name, "%s", As_Meter(meter)->name); + } + colSettings->names[idx] = name; + colSettings->modes[idx] = meter->mode; + idx++; } - colSettings->names[i] = name; - colSettings->modes[i] = meter->mode; - } + } while ((col == numCols - 1) && (saveCol < this->maxColumns) && !this->metersCopied); } } @@ -204,7 +243,7 @@ void Header_draw(const Header* this) { float roundingLoss = 0.0F; Header_forEachColumn(this, col) { - Vector* meters = this->columns[col]; + size_t drawCol = col; float colWidth = (float)width * HeaderLayout_layouts[this->headerLayout].widths[col] / 100.0F; roundingLoss += colWidth - floorf(colWidth); @@ -213,24 +252,29 @@ void Header_draw(const Header* this) { roundingLoss -= 1.0F; } - for (int y = (pad / 2), i = 0; i < Vector_size(meters); i++) { - Meter* meter = (Meter*) Vector_get(meters, i); + int y = pad / 2; - float actualWidth = colWidth; + do { + Vector* meters = this->columns[drawCol++]; + for (int i = 0; i < Vector_size(meters); i++) { + Meter* meter = (Meter*) Vector_get(meters, i); - /* Let meters in text mode expand to the right on empty neighbors; - except for multi column meters. */ - if (meter->mode == TEXT_METERMODE && !Meter_isMultiColumn(meter)) { - for (int j = 1; j < meter->columnWidthCount; j++) { - actualWidth++; /* separator column */ - actualWidth += (float)width * HeaderLayout_layouts[this->headerLayout].widths[col + j] / 100.0F; + float actualWidth = colWidth; + + /* Let meters in text mode expand to the right on empty neighbors; + except for multi column meters. */ + if (meter->mode == TEXT_METERMODE && !Meter_isMultiColumn(meter)) { + for (int j = 1; j < meter->columnWidthCount; j++) { + actualWidth++; /* separator column */ + actualWidth += (float)width * HeaderLayout_layouts[this->headerLayout].widths[col + j] / 100.0F; + } } - } - assert(meter->draw); - meter->draw(meter, x, y, floorf(actualWidth)); - y += meter->h; - } + assert(meter->draw); + meter->draw(meter, x, y, floorf(actualWidth)); + y += meter->h; + } + } while ((col == numCols - 1) && (drawCol < this->maxColumns) && !this->metersCopied); x += floorf(colWidth); x++; /* separator column */ @@ -238,13 +282,16 @@ void Header_draw(const Header* this) { } void Header_updateData(Header* this) { + const size_t numCols = HeaderLayout_getColumns(this->headerLayout); Header_forEachColumn(this, col) { - Vector* meters = this->columns[col]; - int items = Vector_size(meters); - for (int i = 0; i < items; i++) { - Meter* meter = (Meter*) Vector_get(meters, i); - Meter_updateValues(meter); - } + size_t updCol = col; + do { + Vector* meters = this->columns[updCol++]; + for (int i = 0; i < Vector_size(meters); i++) { + Meter* meter = (Meter*) Vector_get(meters, i); + Meter_updateValues(meter); + } + } while ((col == numCols - 1) && (updCol < this->maxColumns) && !this->metersCopied); } } @@ -282,14 +329,18 @@ int Header_calculateHeight(Header* this) { int maxHeight = pad; Header_forEachColumn(this, col) { - const Vector* meters = this->columns[col]; + size_t calcCol = col; int height = pad; - for (int i = 0; i < Vector_size(meters); i++) { - Meter* meter = (Meter*) Vector_get(meters, i); - meter->columnWidthCount = calcColumnWidthCount(this, meter, pad, col, height); - height += meter->h; - } - maxHeight = MAXIMUM(maxHeight, height); + + do { + const Vector* meters = this->columns[calcCol++]; + for (int i = 0; i < Vector_size(meters); i++) { + Meter* meter = (Meter*) Vector_get(meters, i); + meter->columnWidthCount = calcColumnWidthCount(this, meter, pad, col, height); + height += meter->h; + } + maxHeight = MAXIMUM(maxHeight, height); + } while ((col == HeaderLayout_getColumns(this->headerLayout) - 1) && (calcCol < this->maxColumns) && !this->metersCopied); } if (maxHeight == pad) { diff --git a/Header.h b/Header.h index b0ffd5921..45b5c9570 100644 --- a/Header.h +++ b/Header.h @@ -7,6 +7,7 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include #include #include "HeaderLayout.h" @@ -17,6 +18,8 @@ in the source distribution for its full text. typedef struct Header_ { Vector** columns; + size_t maxColumns; + bool metersCopied; Machine* host; HeaderLayout headerLayout; int pad; @@ -31,6 +34,10 @@ void Header_delete(Header* this); void Header_setLayout(Header* this, HeaderLayout hLayout); +void Header_undoMetersCopy(Header* this); + +void Header_collapseLayout(Header* this); + void Header_populateFromSettings(Header* this); void Header_writeBackToSettings(const Header* this); diff --git a/MetersPanel.c b/MetersPanel.c index 418229e2d..a1ea98789 100644 --- a/MetersPanel.c +++ b/MetersPanel.c @@ -98,6 +98,7 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { int selected = Panel_getSelectedIndex(super); HandlerResult result = IGNORED; bool sideMove = false; + bool modified = false; switch (ch) { case 0x0a: @@ -136,8 +137,11 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { case KEY_F(7): case '[': case '-': - Vector_moveUp(this->meters, selected); - Panel_moveSelectedUp(super); + if (selected > 0) { + Vector_moveUp(this->meters, selected); + Panel_moveSelectedUp(super); + modified = true; + } result = HANDLED; break; case KEY_DOWN: @@ -147,8 +151,11 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { case KEY_F(8): case ']': case '+': - Vector_moveDown(this->meters, selected); - Panel_moveSelectedDown(super); + if (selected + 1 < Vector_size(this->meters)) { + Vector_moveDown(this->meters, selected); + Panel_moveSelectedDown(super); + modified = true; + } result = HANDLED; break; case KEY_F(6): @@ -178,6 +185,7 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { Panel_remove(super, selected); } MetersPanel_setMoving(this, false); + modified = true; result = HANDLED; break; case EVENT_PANEL_LOST_FOCUS: @@ -189,6 +197,10 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { if (result == HANDLED || sideMove) { Header* header = this->scr->header; + if (modified || sideMove) { + Header_collapseLayout(header); + header->metersCopied = false; + } this->settings->changed = true; this->settings->lastUpdate++; Header_calculateHeight(header);