From 400c8f281a0b9ff7081cd6c55aff4d3ea758ad33 Mon Sep 17 00:00:00 2001 From: nakst <> Date: Sun, 21 Nov 2021 19:41:42 +0000 Subject: [PATCH] fixed item sorting --- apps/file_manager/ui.cpp | 25 ++++++- apps/samples/list.cpp | 26 ++++--- desktop/list_view.cpp | 145 ++++++++++++++++++++++++++++++++++++--- desktop/os.header | 4 +- shared/common.cpp | 4 ++ shared/strings.cpp | 7 ++ util/api_table.ini | 1 - 7 files changed, 186 insertions(+), 26 deletions(-) diff --git a/apps/file_manager/ui.cpp b/apps/file_manager/ui.cpp index 2a4da35..9fbd0fb 100644 --- a/apps/file_manager/ui.cpp +++ b/apps/file_manager/ui.cpp @@ -829,11 +829,30 @@ int ListCallback(EsElement *element, EsMessage *message) { } } else if (message->type == ES_MSG_LIST_VIEW_COLUMN_MENU) { EsMenu *menu = EsMenuCreate(message->columnMenu.source); - uint32_t index = (uint32_t) message->columnMenu.index; + uint32_t index = message->columnMenu.columnID; + const char *ascending = nullptr; + const char *descending = nullptr; + + if (index == COLUMN_NAME) { + ascending = interfaceString_CommonSortAToZ; + descending = interfaceString_CommonSortZToA; + } else if (index == COLUMN_TYPE) { + ascending = interfaceString_CommonSortAToZ; + descending = interfaceString_CommonSortZToA; + } else if (index == COLUMN_SIZE) { + ascending = interfaceString_CommonSortSmallToLarge; + descending = interfaceString_CommonSortLargeToSmall; + } + +#define COLUMN_NAME (0) +#define COLUMN_TYPE (1) +#define COLUMN_SIZE (2) + + EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(CommonSortHeader)); EsMenuAddItem(menu, instance->viewSettings.sortColumn == index ? ES_MENU_ITEM_CHECKED : 0, - INTERFACE_STRING(CommonSortAscending), InstanceChangeSortColumn, index); + ascending, -1, InstanceChangeSortColumn, index); EsMenuAddItem(menu, instance->viewSettings.sortColumn == (index | (1 << 8)) ? ES_MENU_ITEM_CHECKED : 0, - INTERFACE_STRING(CommonSortDescending), InstanceChangeSortColumn, index | (1 << 8)); + descending, -1, InstanceChangeSortColumn, index | (1 << 8)); EsMenuShow(menu); } else if (message->type == ES_MSG_LIST_VIEW_GET_COLUMN_SORT) { if (message->getColumnSort.index == (instance->viewSettings.sortColumn & 0xFF)) { diff --git a/apps/samples/list.cpp b/apps/samples/list.cpp index 040986e..b5dacfa 100644 --- a/apps/samples/list.cpp +++ b/apps/samples/list.cpp @@ -14,7 +14,7 @@ const EsListViewEnumString colorStrings[] = { }; void AddPerson(EsListView *list, const char *name, int age, int favoriteColor) { - EsListViewIndex index = EsListViewFixedItemInsert(list); + EsListViewIndex index = EsListViewFixedItemInsert(list, (void *) name /* data */); EsListViewFixedItemSetString (list, index, COLUMN_NAME, name); EsListViewFixedItemSetInteger(list, index, COLUMN_AGE, age); EsListViewFixedItemSetInteger(list, index, COLUMN_FAVORITE_COLOR, favoriteColor); @@ -30,20 +30,24 @@ void _start() { EsInstance *instance = EsInstanceCreate(message, "List", -1); EsPanel *wrapper = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER); - EsListView *list = EsListViewCreate(wrapper, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_FIXED_ITEMS); - EsListViewRegisterColumn(list, COLUMN_NAME, "Name", -1, - ES_FLAGS_DEFAULT, 150); - EsListViewRegisterColumn(list, COLUMN_AGE, "Age", -1, - ES_TEXT_H_RIGHT | ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS, 100); - EsListViewRegisterColumn(list, COLUMN_FAVORITE_COLOR, "Favorite color", -1, - ES_DRAW_CONTENT_RICH_TEXT | ES_LIST_VIEW_COLUMN_FIXED_FORMAT_ENUM_STRING | ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS, 150); + uint64_t flags; + flags = ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_FIXED_ITEMS | ES_LIST_VIEW_SINGLE_SELECT; + EsListView *list = EsListViewCreate(wrapper, flags); + flags = ES_LIST_VIEW_COLUMN_HAS_MENU; + EsListViewRegisterColumn(list, COLUMN_NAME, "Name", -1, flags, 150); + flags = ES_LIST_VIEW_COLUMN_HAS_MENU | ES_TEXT_H_RIGHT | ES_DRAW_CONTENT_TABULAR + | ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS | ES_LIST_VIEW_COLUMN_FIXED_SORT_SIZE; + EsListViewRegisterColumn(list, COLUMN_AGE, "Age", -1, flags, 100); + flags = ES_LIST_VIEW_COLUMN_HAS_MENU | ES_DRAW_CONTENT_RICH_TEXT + | ES_LIST_VIEW_COLUMN_FIXED_FORMAT_ENUM_STRING | ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS; + EsListViewRegisterColumn(list, COLUMN_FAVORITE_COLOR, "Favorite color", -1, flags, 150); EsListViewFixedItemSetEnumStringsForColumn(list, COLUMN_FAVORITE_COLOR, colorStrings, sizeof(colorStrings) / sizeof(colorStrings[0])); EsListViewAddAllColumns(list); - AddPerson(list, "Alice", 10, COLOR_RED); - AddPerson(list, "Bob", 20, COLOR_GREEN); + AddPerson(list, "Alice", 40, COLOR_RED); + AddPerson(list, "Bob", 10, COLOR_GREEN); AddPerson(list, "Cameron", 30, COLOR_BLUE); - AddPerson(list, "Daniel", 40, COLOR_RED); + AddPerson(list, "Daniel", 20, COLOR_RED); } } } diff --git a/desktop/list_view.cpp b/desktop/list_view.cpp index 8866ead..a66de94 100644 --- a/desktop/list_view.cpp +++ b/desktop/list_view.cpp @@ -62,6 +62,8 @@ struct ListViewColumn { }; int ListViewProcessItemMessage(EsElement *element, EsMessage *message); +void ListViewSetSortAscending(EsMenu *menu, EsGeneric context); +void ListViewSetSortDescending(EsMenu *menu, EsGeneric context); struct EsListView : EsElement { ScrollPane scroll; @@ -123,7 +125,12 @@ struct EsListView : EsElement { // Fixed item storage: Array fixedItems; + Array fixedItemIndices; // For sorting. Converts the actual list index into an index for fixedItems. ptrdiff_t fixedItemSelection; + uint32_t fixedItemSortColumnID; +#define LIST_SORT_DIRECTION_ASCENDING (1) +#define LIST_SORT_DIRECTION_DESCENDING (2) + uint8_t fixedItemSortDirection; inline EsRectangle GetListBounds() { EsRectangle bounds = GetBounds(); @@ -1894,6 +1901,7 @@ struct EsListView : EsElement { } else if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT && (flags & ES_LIST_VIEW_FIXED_ITEMS)) { uintptr_t index = message->getContent.index; EsAssert(index < fixedItems.Length()); + index = fixedItemIndices[index]; ListViewFixedItemData emptyData = {}; ListViewFixedItem *item = &fixedItems[index]; ListViewColumn *column = ®isteredColumns[(flags & ES_LIST_VIEW_COLUMNS) ? activeColumns[message->getContent.activeColumnIndex] : 0]; @@ -1972,6 +1980,31 @@ struct EsListView : EsElement { #undef BOOLEAN_FORMAT } else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED && (flags & ES_LIST_VIEW_FIXED_ITEMS)) { message->selectItem.isSelected = message->selectItem.index == fixedItemSelection; + } else if (message->type == ES_MSG_LIST_VIEW_COLUMN_MENU && (flags & ES_LIST_VIEW_FIXED_ITEMS)) { + EsMenu *menu = EsMenuCreate(message->columnMenu.source); + menu->userData = this; + + ListViewColumn *column = ®isteredColumns[activeColumns[message->columnMenu.activeColumnIndex]]; + uint32_t sortMode = column->flags & ES_LIST_VIEW_COLUMN_FIXED_SORT_MASK; + uint64_t checkAscending = (fixedItemSortDirection == LIST_SORT_DIRECTION_ASCENDING && column->id == fixedItemSortColumnID) ? ES_MENU_ITEM_CHECKED : 0; + uint64_t checkDescending = (fixedItemSortDirection == LIST_SORT_DIRECTION_DESCENDING && column->id == fixedItemSortColumnID) ? ES_MENU_ITEM_CHECKED : 0; + + if (sortMode != ES_LIST_VIEW_COLUMN_FIXED_SORT_NONE) { + EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(CommonSortHeader)); + + if (sortMode == ES_LIST_VIEW_COLUMN_FIXED_SORT_DEFAULT) { + EsMenuAddItem(menu, checkAscending, INTERFACE_STRING(CommonSortAToZ), ListViewSetSortAscending, column->id); + EsMenuAddItem(menu, checkDescending, INTERFACE_STRING(CommonSortZToA), ListViewSetSortDescending, column->id); + } else if (sortMode == ES_LIST_VIEW_COLUMN_FIXED_SORT_TIME) { + EsMenuAddItem(menu, checkAscending, INTERFACE_STRING(CommonSortOldToNew), ListViewSetSortAscending, column->id); + EsMenuAddItem(menu, checkDescending, INTERFACE_STRING(CommonSortNewToOld), ListViewSetSortDescending, column->id); + } else if (sortMode == ES_LIST_VIEW_COLUMN_FIXED_SORT_SIZE) { + EsMenuAddItem(menu, checkAscending, INTERFACE_STRING(CommonSortSmallToLarge), ListViewSetSortAscending, column->id); + EsMenuAddItem(menu, checkDescending, INTERFACE_STRING(CommonSortLargeToSmall), ListViewSetSortDescending, column->id); + } + } + + EsMenuShow(menu); } else { return 0; } @@ -2011,6 +2044,8 @@ int ListViewColumnHeaderMessage(EsElement *element, EsMessage *message) { splitter->InternalMove(splitter->style->preferredWidth, element->height, x + column->width * theming.scale - splitterLeft, 0); x += column->width * theming.scale + view->secondaryCellStyle->gapMajor; } + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN || message->type == ES_MSG_MOUSE_RIGHT_DOWN) { + return ES_HANDLED; } return 0; @@ -2351,8 +2386,10 @@ int ListViewColumnHeaderItemMessage(EsElement *element, EsMessage *message) { } else if (message->type == ES_MSG_MOUSE_LEFT_CLICK && (column->flags & ES_LIST_VIEW_COLUMN_HAS_MENU)) { EsMessage m = { ES_MSG_LIST_VIEW_COLUMN_MENU }; m.columnMenu.source = element; - m.columnMenu.index = element->userData.u; + m.columnMenu.activeColumnIndex = element->userData.u; + m.columnMenu.columnID = view->registeredColumns[element->userData.u].id; EsMessageSend(view, &m); + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { } else { return 0; } @@ -2533,6 +2570,7 @@ EsListViewIndex EsListViewFixedItemInsert(EsListView *view, EsGeneric data, EsLi item.data = data; item.iconID = iconID; view->fixedItems.Insert(item, index); + view->fixedItemIndices.Insert(index, index); ListViewFixedItemData emptyData = {}; @@ -2604,8 +2642,8 @@ bool EsListViewFixedItemFindIndex(EsListView *view, EsGeneric data, EsListViewIn EsAssert(view->flags & ES_LIST_VIEW_FIXED_ITEMS); EsMessageMutexCheck(); - for (uintptr_t i = 0; i < view->fixedItems.Length(); i++) { - if (view->fixedItems[i].data == data) { + for (uintptr_t i = 0; i < view->fixedItemIndices.Length(); i++) { + if (view->fixedItems[view->fixedItemIndices[i]].data == data) { *index = i; return true; } @@ -2623,7 +2661,7 @@ bool EsListViewFixedItemSelect(EsListView *view, EsGeneric data) { if (found) { EsListViewSelect(view, 0, index); - // TODO Maybe you should have to separately call EsListViewFocusItem to get this behaviour. + // TODO Maybe you should have to separately call EsListViewFocusItem to get this behaviour? EsListViewFocusItem(view, 0, index); view->EnsureItemVisible(0, index, 2 /* center */); } @@ -2639,16 +2677,17 @@ bool EsListViewFixedItemRemove(EsListView *view, EsGeneric data) { if (found) { EsListViewRemove(view, 0, index, 1); + EsListViewIndex fixedIndex = view->fixedItemIndices[index]; for (uintptr_t i = 0; i < view->registeredColumns.Length(); i++) { ListViewColumn *column = &view->registeredColumns[i]; - if ((uintptr_t) index < column->items.Length()) { + if ((uintptr_t) fixedIndex < column->items.Length()) { if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_STRINGS) { - EsHeapFree(column->items[index].s.string); + EsHeapFree(column->items[fixedIndex].s.string); } - column->items.Delete(index); + column->items.Delete(fixedIndex); if (!column->items.Length()) { column->items.Free(); @@ -2656,7 +2695,14 @@ bool EsListViewFixedItemRemove(EsListView *view, EsGeneric data) { } } - view->fixedItems.Delete(index); + view->fixedItems.Delete(fixedIndex); + view->fixedItemIndices.Delete(index); + + for (uintptr_t i = 0; i < view->fixedItemIndices.Length(); i++) { + if (view->fixedItemIndices[i] > fixedIndex) { + view->fixedItemIndices[i]--; + } + } } return found; @@ -2669,7 +2715,7 @@ bool EsListViewFixedItemGetSelected(EsListView *view, EsGeneric *data) { if (view->fixedItemSelection == -1 || view->fixedItemSelection >= (intptr_t) view->fixedItems.Length()) { return false; } else { - *data = view->fixedItems[view->fixedItemSelection].data; + *data = view->fixedItems[view->fixedItemIndices[view->fixedItemSelection]].data; return true; } } @@ -2687,6 +2733,87 @@ void EsListViewFixedItemSetEnumStringsForColumn(EsListView *view, uint32_t colum EsAssert(false); } +#define LIST_VIEW_SORT_FUNCTION(_name, _line) \ + ES_MACRO_SORT(_name, EsListViewIndex, { \ + ListViewFixedItemData *left = (ListViewFixedItemData *) &context->items[*_left]; \ + ListViewFixedItemData *right = (ListViewFixedItemData *) &context->items[*_right]; \ + result = _line; \ + }, ListViewColumn *) + +LIST_VIEW_SORT_FUNCTION(ListViewSortByStringsAscending, EsStringCompare(left->s.string, left->s.bytes, right->s.string, right->s.bytes)); +LIST_VIEW_SORT_FUNCTION(ListViewSortByStringsDescending, -EsStringCompare(left->s.string, left->s.bytes, right->s.string, right->s.bytes)); +LIST_VIEW_SORT_FUNCTION(ListViewSortByEnumsAscending, EsStringCompare(context->enumStrings[left->i].string, context->enumStrings[left->i].stringBytes, + context->enumStrings[right->i].string, context->enumStrings[right->i].stringBytes)); +LIST_VIEW_SORT_FUNCTION(ListViewSortByEnumsDescending, -EsStringCompare(context->enumStrings[left->i].string, context->enumStrings[left->i].stringBytes, + context->enumStrings[right->i].string, context->enumStrings[right->i].stringBytes)); +LIST_VIEW_SORT_FUNCTION(ListViewSortByIntegersAscending, left->i > right->i ? 1 : left->i == right->i ? 0 : -1); +LIST_VIEW_SORT_FUNCTION(ListViewSortByIntegersDescending, left->i < right->i ? 1 : left->i == right->i ? 0 : -1); +LIST_VIEW_SORT_FUNCTION(ListViewSortByDoublesAscending, left->d > right->d ? 1 : left->d == right->d ? 0 : -1); +LIST_VIEW_SORT_FUNCTION(ListViewSortByDoublesDescending, left->d < right->d ? 1 : left->d == right->d ? 0 : -1); + +void ListViewSetSortDirection(EsListView *view, uint32_t columnID, uint8_t direction) { + ListViewColumn *column = nullptr; + + for (uintptr_t i = 0; i < view->registeredColumns.Length(); i++) { + if (view->registeredColumns[i].id == columnID) { + column = &view->registeredColumns[i]; + break; + } + } + + EsAssert(column); + + if (view->fixedItemSortColumnID == columnID && view->fixedItemSortDirection == direction) { + return; + } + + view->fixedItemSortColumnID = columnID; + view->fixedItemSortDirection = direction; + + EsAssert(view->fixedItems.Length() == view->fixedItemIndices.Length()); + + void (*sortFunction)(EsListViewIndex *, size_t, ListViewColumn *) = nullptr; + + if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_STRINGS) { + sortFunction = (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByStringsDescending : ListViewSortByStringsAscending); + } else if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS) { + if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_FORMAT_MASK) == ES_LIST_VIEW_COLUMN_FIXED_FORMAT_ENUM_STRING) { + sortFunction = (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByEnumsDescending : ListViewSortByEnumsAscending); + } else { + sortFunction = (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByIntegersDescending : ListViewSortByIntegersAscending); + } + } else if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_DOUBLES) { + sortFunction = (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByDoublesDescending : ListViewSortByDoublesAscending); + } else { + EsAssert(false); + } + + EsListViewIndex previousSelectionIndex = view->fixedItemSelection >= 0 && (uintptr_t) view->fixedItemSelection < view->fixedItemIndices.Length() + ? view->fixedItemIndices[view->fixedItemSelection] : -1; + + sortFunction(view->fixedItemIndices.array, view->fixedItems.Length(), column); + EsListViewInvalidateAll(view); + + if (previousSelectionIndex != -1) { + for (uintptr_t i = 0; i < view->fixedItemIndices.Length(); i++) { + if (view->fixedItemIndices[i] == previousSelectionIndex) { + EsListViewSelect(view, 0, i); + EsListViewFocusItem(view, 0, i); + view->EnsureItemVisible(0, i, 2 /* center */); + break; + } + } + } +} + +void ListViewSetSortAscending(EsMenu *menu, EsGeneric context) { + ListViewSetSortDirection((EsListView *) menu->userData.p, context.u, LIST_SORT_DIRECTION_ASCENDING); +} + +void ListViewSetSortDescending(EsMenu *menu, EsGeneric context) { + ListViewSetSortDirection((EsListView *) menu->userData.p, context.u, LIST_SORT_DIRECTION_DESCENDING); +} + int ListViewInlineTextboxMessage(EsElement *element, EsMessage *message) { int response = ProcessTextboxMessage(element, message); diff --git a/desktop/os.header b/desktop/os.header index d66049f..f75b462 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -1699,8 +1699,9 @@ struct EsMessageFocus { }; struct EsMessageColumnMenu { - uint8_t index; EsElement *source; + uint32_t columnID; + uint16_t activeColumnIndex; }; struct EsMessageGetColumnSort { @@ -2621,7 +2622,6 @@ function bool EsListViewFixedItemRemove(EsListView *view, EsGeneric data); // Re function void EsListViewFixedItemSetString(EsListView *view, EsListViewIndex index, uint32_t columnID, STRING string = BLANK_STRING); function void EsListViewFixedItemSetDouble(EsListView *view, EsListViewIndex index, uint32_t columnID, double number); function void EsListViewFixedItemSetInteger(EsListView *view, EsListViewIndex index, uint32_t columnID, int64_t number); -function bool EsListViewFixedItemFindIndex(EsListView *view, EsGeneric data, EsListViewIndex *index); // Returns false if the item was not found. function bool EsListViewFixedItemSelect(EsListView *view, EsGeneric data); // Returns false if the item was not found. function bool EsListViewFixedItemGetSelected(EsListView *view, EsGeneric *data); // Returns false if no item was selected. function void EsListViewFixedItemSetEnumStringsForColumn(EsListView *view, uint32_t columnID, const EsListViewEnumString *strings, size_t stringCount); diff --git a/shared/common.cpp b/shared/common.cpp index 763f441..8b6d75f 100644 --- a/shared/common.cpp +++ b/shared/common.cpp @@ -980,6 +980,10 @@ int EsStringCompare(const char *s1, ptrdiff_t _length1, const char *s2, ptrdiff_ size_t length1 = _length1, length2 = _length2; while (length1 || length2) { + // Skip over rich text markup. + if (*s1 == '\a') while (length1 && *s1 != ']') s1++, length1--; + if (*s2 == '\a') while (length2 && *s2 != ']') s2++, length2--; + if (!length1) return -1; if (!length2) return 1; diff --git a/shared/strings.cpp b/shared/strings.cpp index 5146047..5c8255f 100644 --- a/shared/strings.cpp +++ b/shared/strings.cpp @@ -56,8 +56,15 @@ DEFINE_INTERFACE_STRING(CommonSearchPrompt2, "Enter text to search for."); DEFINE_INTERFACE_STRING(CommonItemFolder, "Folder"); DEFINE_INTERFACE_STRING(CommonItemFile, "File"); +DEFINE_INTERFACE_STRING(CommonSortHeader, "Sort" ELLIPSIS); DEFINE_INTERFACE_STRING(CommonSortAscending, "Sort ascending"); DEFINE_INTERFACE_STRING(CommonSortDescending, "Sort descending"); +DEFINE_INTERFACE_STRING(CommonSortAToZ, "A to Z"); +DEFINE_INTERFACE_STRING(CommonSortZToA, "Z to A"); +DEFINE_INTERFACE_STRING(CommonSortSmallToLarge, "Smallest first"); +DEFINE_INTERFACE_STRING(CommonSortLargeToSmall, "Largest first"); +DEFINE_INTERFACE_STRING(CommonSortOldToNew, "Oldest first"); +DEFINE_INTERFACE_STRING(CommonSortNewToOld, "Newest first"); DEFINE_INTERFACE_STRING(CommonDriveHDD, "Hard disk"); DEFINE_INTERFACE_STRING(CommonDriveSSD, "SSD"); diff --git a/util/api_table.ini b/util/api_table.ini index abec80b..0d69a08 100644 --- a/util/api_table.ini +++ b/util/api_table.ini @@ -423,7 +423,6 @@ EsSliderSetValue=422 EsClipboardCloseAndAdd=423 EsListViewFixedItemInsert=424 EsListViewFixedItemSetInteger=425 -EsListViewFixedItemFindIndex=426 EsListViewFixedItemSelect=427 EsListViewFixedItemGetSelected=428 EsClipboardHasFormat=429