essence-os/apps/file_manager/ui.cpp

1253 lines
47 KiB
C++

// This file is part of the Essence operating system.
// It is released under the terms of the MIT license -- see LICENSE.md.
// Written by: nakst.
// TODO Custom columns.
void InstanceFolderPathChanged(Instance *instance, bool fromLoadFolder) {
if (fromLoadFolder) {
// Don't free the old path; it's used in the history stack.
} else {
StringDestroy(&instance->path);
}
instance->path = StringDuplicate(instance->folder->path);
EsTextboxSelectAll(instance->breadcrumbBar);
EsTextboxInsert(instance->breadcrumbBar, STRING(instance->path));
uint8_t _buffer[256];
EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) };
instance->folder->containerHandler->getVisibleName(&buffer, instance->path);
EsWindowSetTitle(instance->window, (char *) buffer.out, buffer.position);
EsWindowSetIcon(instance->window, knownFileTypes[instance->folder->containerHandler->getFileType(instance->path)].iconID);
size_t itemCount;
EsListViewEnumeratedVisibleItem *items = EsListViewEnumerateVisibleItems(instance->list, &itemCount);
for (uintptr_t i = 0; i < itemCount; i++) ListItemCreated(items[i].element, items[i].index, true);
EsHeapFree(items);
}
bool InstanceLoadFolder(Instance *instance, String path /* takes ownership */, int historyMode) {
if (instance->folder && !instance->folder->refreshing && StringEquals(path, instance->folder->path)) {
// The path hasn't changed; ignore.
StringDestroy(&path);
return true;
}
instance->issuedPasteTask = nullptr;
Task task = {};
task.context = historyMode;
task.cDescription = interfaceString_FileManagerOpenFolderTask;
EsListViewIndex focusedIndex;
if (EsListViewGetFocusedItem(instance->list, nullptr, &focusedIndex)) {
String name = instance->listContents[focusedIndex].entry->GetName();
task.string = StringDuplicate(name);
}
InstanceRemoveContents(instance);
FolderAttachInstance(instance, path, false);
StringDestroy(&path);
task.callback = [] (Instance *instance, Task *) {
Folder *folder = instance->folder;
EsMutexAcquire(&folder->modifyEntriesMutex);
if (!folder->doneInitialEnumeration) {
// TODO Reporting errors.
folder->itemHandler->enumerate(folder);
if (folder->containerHandler->getTotalSize) {
folder->containerHandler->getTotalSize(folder);
}
folder->driveRemoved = false;
folder->refreshing = false;
folder->doneInitialEnumeration = true;
}
EsMutexRelease(&folder->modifyEntriesMutex);
};
task.then = [] (Instance *instance, Task *task) {
// TODO Check if folder was marked for refresh.
if (instance->closed) {
return;
}
int historyMode = task->context.i & 0xFF;
int flags = task->context.i;
Folder *folder = instance->folder;
folder->attachedInstances.Add(instance);
// Add the path to the history array.
HistoryEntry historyEntry = {};
historyEntry.path = instance->path;
historyEntry.focusedItem = task->string;
if (historyMode == LOAD_FOLDER_BACK) {
instance->pathForwardHistory.Add(historyEntry);
} else if (historyMode == LOAD_FOLDER_FORWARD) {
instance->pathBackwardHistory.Add(historyEntry);
} else if (historyMode == LOAD_FOLDER_START || historyMode == LOAD_FOLDER_REFRESH) {
} else {
instance->pathBackwardHistory.Add(historyEntry);
for (int i = 0; i < (int) instance->pathForwardHistory.Length(); i++) {
StringDestroy(&instance->pathForwardHistory[i].path);
StringDestroy(&instance->pathForwardHistory[i].focusedItem);
}
instance->pathForwardHistory.SetLength(0);
}
// Update commands.
EsCommandSetEnabled(&instance->commandGoBackwards, instance->pathBackwardHistory.Length());
EsCommandSetEnabled(&instance->commandGoForwards, instance->pathForwardHistory.Length());
EsCommandSetEnabled(&instance->commandGoParent, PathCountSections(folder->path) > 1);
EsCommandSetEnabled(&instance->commandNewFolder, folder->itemHandler->createChildFolder && !folder->readOnly);
EsCommandSetEnabled(&instance->commandRename, false);
EsCommandSetEnabled(&instance->commandRefresh, true);
// Load the view settings for the folder.
// If the folder does not have any settings, inherit from closest ancestor with settings.
bool foundViewSettings = false;
size_t lastMatchBytes = 0;
ptrdiff_t updateLRU = -1;
if (folder->path.bytes < sizeof(folderViewSettings[0].path)) {
for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) {
String path = StringFromLiteralWithSize(folderViewSettings[i].path, folderViewSettings[i].pathBytes);
bool matchFull = StringEquals(path, folder->path);
bool matchPartial = matchFull || PathHasPrefix(folder->path, path);
if (matchFull || (matchPartial && lastMatchBytes < path.bytes)) {
foundViewSettings = true;
instance->viewSettings = folderViewSettings[i].settings;
updateLRU = i;
if (matchFull) break;
else lastMatchBytes = path.bytes; // Keep looking for a closer ancestor.
}
}
}
if (updateLRU != -1) {
FolderViewSettingsEntry entry = folderViewSettings[updateLRU];
folderViewSettings.Delete(updateLRU);
folderViewSettings.Add(entry);
}
if (!foundViewSettings) {
if (folder->itemHandler->getDefaultViewSettings) {
folder->itemHandler->getDefaultViewSettings(folder, &instance->viewSettings);
} else {
// TODO Get default from configuration.
instance->viewSettings.sortColumn = COLUMN_NAME;
instance->viewSettings.viewType = VIEW_TILES;
}
}
InstanceRefreshViewType(instance);
// Update the user interface.
EsListViewSetEmptyMessage(instance->list, folder->cEmptyMessage);
InstanceAddContents(instance, &folder->entries);
InstanceFolderPathChanged(instance, true);
EsListViewInvalidateAll(instance->placesView);
if (~flags & LOAD_FOLDER_NO_FOCUS) EsElementFocus(instance->list);
};
BlockingTaskQueue(instance, task);
return true;
}
void InstanceRefreshViewType(Instance *instance) {
EsCommandSetCheck(&instance->commandViewDetails, instance->viewSettings.viewType == VIEW_DETAILS ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
EsCommandSetCheck(&instance->commandViewTiles, instance->viewSettings.viewType == VIEW_TILES ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
EsCommandSetCheck(&instance->commandViewThumbnails, instance->viewSettings.viewType == VIEW_THUMBNAILS ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
if (instance->viewSettings.viewType == VIEW_DETAILS) {
EsListViewChangeStyles(instance->list, EsStyleIntern(&styleFolderView), ES_STYLE_LIST_ITEM, 0, 0, ES_LIST_VIEW_COLUMNS, ES_LIST_VIEW_TILED);
EsListViewAddAllColumns(instance->list);
} else if (instance->viewSettings.viewType == VIEW_TILES) {
EsListViewChangeStyles(instance->list, EsStyleIntern(&styleFolderViewTiled), ES_STYLE_LIST_ITEM_TILE, 0, 0, ES_LIST_VIEW_TILED, ES_LIST_VIEW_COLUMNS);
} else if (instance->viewSettings.viewType == VIEW_THUMBNAILS) {
EsListViewChangeStyles(instance->list, EsStyleIntern(&styleFolderViewTiled), EsStyleIntern(&styleFolderItemThumbnail), 0, 0, ES_LIST_VIEW_TILED, ES_LIST_VIEW_COLUMNS);
}
}
void InstanceRemoveItemSelectionCommands(Instance *instance) {
EsCommandSetCallback(EsCommandByID(instance, ES_COMMAND_CUT), nullptr);
EsCommandSetCallback(EsCommandByID(instance, ES_COMMAND_COPY), nullptr);
EsCommandSetCallback(EsCommandByID(instance, ES_COMMAND_PASTE), nullptr);
}
void InstanceUpdateItemSelectionCountCommands(Instance *instance) {
EsCommandSetEnabled(&instance->commandRename, instance->selectedItemCount == 1 && instance->folder->itemHandler->renameItem && !instance->folder->readOnly);
#define COMMAND_SET(id, callback, enabled) \
do { EsCommand *command = EsCommandByID(instance, id); \
EsCommandSetEnabled(command, enabled); \
EsCommandSetCallback(command, callback); } while(0)
if (EsElementIsFocused(instance->list)) {
COMMAND_SET(ES_COMMAND_CUT, CommandCut, instance->selectedItemCount >= 1 && instance->folder->itemHandler->canCut && !instance->folder->readOnly);
COMMAND_SET(ES_COMMAND_COPY, CommandCopy, instance->selectedItemCount >= 1 && instance->folder->itemHandler->canCopy);
COMMAND_SET(ES_COMMAND_PASTE, CommandPaste, instance->folder->itemHandler->canPaste && EsClipboardHasData(ES_CLIPBOARD_PRIMARY) && !instance->folder->readOnly);
}
}
int InstanceCompareFolderEntries(FolderEntry *left, FolderEntry *right, uint16_t sortColumn) {
bool descending = sortColumn & (1 << 8);
sortColumn &= 0xFF;
int result = 0;
if (!left->isFolder && right->isFolder) {
result = 1;
} else if (!right->isFolder && left->isFolder) {
result = -1;
} else {
if (sortColumn == COLUMN_NAME) {
result = EsStringCompare(STRING(left->GetName()), STRING(right->GetName()));
} else if (sortColumn == COLUMN_TYPE) {
result = EsStringCompare(STRING(left->GetExtension()), STRING(right->GetExtension()));
} else if (sortColumn == COLUMN_SIZE) {
if (right->size < left->size) result = 1;
else if (right->size > left->size) result = -1;
else result = 0;
}
if (!result && sortColumn != COLUMN_NAME) {
result = EsStringCompare(STRING(left->GetName()), STRING(right->GetName()));
}
if (!result) {
if ((uintptr_t) right > (uintptr_t) left) result = 1;
else if ((uintptr_t) right < (uintptr_t) left) result = -1;
else result = 0;
}
}
return descending ? -result : result;
}
bool InstanceAddInternal(Instance *instance, ListEntry *entry) {
// TODO Filtering.
entry->entry->handles++;
if (entry->selected) {
instance->selectedItemCount++;
instance->selectedItemsTotalSize += entry->entry->size;
InstanceUpdateItemSelectionCountCommands(instance);
}
return true;
}
void InstanceRemoveInternal(Instance *instance, ListEntry *entry) {
FolderEntryCloseHandle(instance->folder, entry->entry);
if (entry->selected) {
instance->selectedItemCount--;
instance->selectedItemsTotalSize -= entry->entry->size;
InstanceUpdateItemSelectionCountCommands(instance);
}
}
void InstanceUpdateStatusString(Instance *instance) {
// TODO Localization.
char buffer[1024];
size_t bytes;
size_t itemCount = instance->listContents.Length();
if (instance->selectedItemCount) {
bytes = EsStringFormat(buffer, sizeof(buffer), "Selected %d item%z (%D)",
instance->selectedItemCount, instance->selectedItemCount == 1 ? "" : "s", instance->selectedItemsTotalSize);
} else {
if (itemCount) {
if (instance->folder->spaceUsed) {
if (instance->folder->spaceTotal) {
bytes = EsStringFormat(buffer, sizeof(buffer), "%d item%z " HYPHENATION_POINT " %D out of %D used",
itemCount, itemCount == 1 ? "" : "s", instance->folder->spaceUsed, instance->folder->spaceTotal);
} else {
bytes = EsStringFormat(buffer, sizeof(buffer), "%d item%z (%D)",
itemCount, itemCount == 1 ? "" : "s", instance->folder->spaceUsed);
}
} else {
bytes = EsStringFormat(buffer, sizeof(buffer), "%d item%z",
itemCount, itemCount == 1 ? "" : "s");
}
} else {
if (instance->folder && instance->folder->itemHandler->type == NAMESPACE_HANDLER_INVALID) {
bytes = EsStringFormat(buffer, sizeof(buffer), "Invalid path");
} else {
bytes = EsStringFormat(buffer, sizeof(buffer), "Empty folder");
}
}
}
EsTextDisplaySetContents(instance->status, buffer, bytes);
}
void InstanceAddSingle(Instance *instance, ListEntry entry) {
// Call with the message mutex acquired.
if (!InstanceAddInternal(instance, &entry)) {
return; // Filtered out.
}
uintptr_t low = 0, high = instance->listContents.Length();
while (low < high) {
uintptr_t middle = (low + high) / 2;
int compare = InstanceCompareFolderEntries(instance->listContents[middle].entry, entry.entry, instance->viewSettings.sortColumn);
if (compare == 0) {
// The entry is already in the list.
EsListViewInvalidateContent(instance->list, 0, middle);
InstanceRemoveInternal(instance, &entry);
ListEntry *existingEntry = &instance->listContents[middle];
if (existingEntry->selected) {
instance->selectedItemsTotalSize += existingEntry->entry->size - existingEntry->entry->previousSize;
InstanceUpdateStatusString(instance);
}
return;
} else if (compare > 0) {
high = middle;
} else {
low = middle + 1;
}
}
instance->listContents.Insert(entry, low);
EsListViewInsert(instance->list, 0, low, 1);
if (entry.selected) {
EsListViewSelect(instance->list, 0, low);
EsListViewFocusItem(instance->list, 0, low);
}
InstanceUpdateStatusString(instance);
}
ES_MACRO_SORT(InstanceSortListContents, ListEntry, {
result = InstanceCompareFolderEntries(_left->entry, _right->entry, context);
}, uint16_t);
void InstanceSelectByName(Instance *instance, String name, bool addToExistingSelection, bool focusItem) {
for (uintptr_t i = 0; i < instance->listContents.Length(); i++) {
if (0 == EsStringCompareRaw(STRING(instance->listContents[i].entry->GetName()), STRING(name))) {
EsListViewSelect(instance->list, 0, i, addToExistingSelection);
if (focusItem) EsListViewFocusItem(instance->list, 0, i);
break;
}
}
}
void InstanceAddContents(Instance *instance, HashTable *newEntries) {
// Call with the message mutex acquired.
size_t oldListEntryCount = instance->listContents.Length();
for (uintptr_t i = 0; i < newEntries->slotCount; i++) {
if (!newEntries->slots[i].key.used) {
continue;
}
ListEntry entry = {};
entry.entry = (FolderEntry *) newEntries->slots[i].value;
if (InstanceAddInternal(instance, &entry)) {
instance->listContents.Add(entry);
}
}
if (oldListEntryCount) {
EsListViewRemove(instance->list, 0, 0, oldListEntryCount);
}
InstanceSortListContents(instance->listContents.array, instance->listContents.Length(), instance->viewSettings.sortColumn);
if (instance->listContents.Length()) {
EsListViewInsert(instance->list, 0, 0, instance->listContents.Length());
if (instance->delayedFocusItem.bytes) {
InstanceSelectByName(instance, instance->delayedFocusItem, false, true);
StringDestroy(&instance->delayedFocusItem);
}
}
InstanceUpdateStatusString(instance);
}
ListEntry InstanceRemoveSingle(Instance *instance, FolderEntry *folderEntry) {
uintptr_t low = 0, high = instance->listContents.Length();
while (low <= high) {
uintptr_t middle = (low + high) / 2;
int compare = InstanceCompareFolderEntries(instance->listContents[middle].entry, folderEntry, instance->viewSettings.sortColumn);
if (compare == 0) {
ListEntry entry = instance->listContents[middle];
InstanceRemoveInternal(instance, &entry);
EsListViewRemove(instance->list, 0, middle, 1);
instance->listContents.Delete(middle);
InstanceUpdateStatusString(instance);
return entry;
} else if (compare > 0) {
high = middle;
} else {
low = middle + 1;
}
}
// It wasn't in the list.
return {};
}
void InstanceRemoveContents(Instance *instance) {
for (uintptr_t i = 0; i < instance->listContents.Length(); i++) {
InstanceRemoveInternal(instance, &instance->listContents[i]);
}
EsAssert(instance->selectedItemCount == 0); // After removing all items none should be selected.
if (instance->listContents.Length()) {
EsListViewRemove(instance->list, 0, 0, instance->listContents.Length());
EsListViewContentChanged(instance->list);
}
instance->listContents.Free();
InstanceUpdateStatusString(instance);
}
ListEntry *InstanceGetSelectedListEntry(Instance *instance) {
for (uintptr_t i = 0; i < instance->listContents.Length(); i++) {
ListEntry *entry = &instance->listContents[i];
if (entry->selected) {
return entry;
}
}
return nullptr;
}
void InstanceViewSettingsUpdated(Instance *instance) {
if (!instance->folder) return; // We were called early in initialization of the instance; ignore.
if (instance->path.bytes < sizeof(folderViewSettings[0].path)) {
bool foundViewSettings = false;
for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) {
if (folderViewSettings[i].pathBytes == instance->path.bytes
&& 0 == EsMemoryCompare(folderViewSettings[i].path, STRING(instance->path))) {
foundViewSettings = true;
folderViewSettings[i].settings = instance->viewSettings;
break;
}
}
if (!foundViewSettings) {
if (folderViewSettings.Length() == FOLDER_VIEW_SETTINGS_MAXIMUM_ENTRIES) {
folderViewSettings.Delete(0);
}
FolderViewSettingsEntry *entry = folderViewSettings.Add();
EsMemoryCopy(entry->path, STRING(instance->path));
entry->pathBytes = instance->path.bytes;
entry->settings = instance->viewSettings;
}
}
ConfigurationSave();
}
void InstanceChangeSortColumn(EsMenu *menu, EsGeneric context) {
Instance *instance = menu->instance;
instance->viewSettings.sortColumn = context.u;
InstanceViewSettingsUpdated(instance);
InstanceSortListContents(instance->listContents.array, instance->listContents.Length(), instance->viewSettings.sortColumn);
EsListViewContentChanged(instance->list);
}
ES_FUNCTION_OPTIMISE_O3
void ThumbnailResize(uint32_t *bits, uint32_t originalWidth, uint32_t originalHeight, uint32_t targetWidth, uint32_t targetHeight) {
// NOTE Modifies the original bits!
// NOTE It looks like this only gets vectorised in -O3.
// TODO Move into the API.
float cx = (float) originalWidth / targetWidth;
float cy = (float) originalHeight / targetHeight;
for (uint32_t i = 0; i < originalHeight; i++) {
uint32_t *output = bits + i * originalWidth;
uint32_t *input = output;
for (uint32_t j = 0; j < targetWidth; j++) {
uint32_t sumAlpha = 0, sumRed = 0, sumGreen = 0, sumBlue = 0;
uint32_t count = (uint32_t) ((j + 1) * cx) - (uint32_t) (j * cx);
for (uint32_t k = 0; k < count; k++, input++) {
uint32_t pixel = *input;
sumAlpha += (pixel >> 24) & 0xFF;
sumRed += (pixel >> 16) & 0xFF;
sumGreen += (pixel >> 8) & 0xFF;
sumBlue += (pixel >> 0) & 0xFF;
}
sumAlpha /= count;
sumRed /= count;
sumGreen /= count;
sumBlue /= count;
*output = (sumAlpha << 24) | (sumRed << 16) | (sumGreen << 8) | (sumBlue << 0);
output++;
}
}
for (uint32_t i = 0; i < targetWidth; i++) {
uint32_t *output = bits + i;
uint32_t *input = output;
for (uint32_t j = 0; j < targetHeight; j++) {
uint32_t sumAlpha = 0, sumRed = 0, sumGreen = 0, sumBlue = 0;
uint32_t count = (uint32_t) ((j + 1) * cy) - (uint32_t) (j * cy);
for (uint32_t k = 0; k < count; k++, input += originalWidth) {
uint32_t pixel = *input;
sumAlpha += (pixel >> 24) & 0xFF;
sumRed += (pixel >> 16) & 0xFF;
sumGreen += (pixel >> 8) & 0xFF;
sumBlue += (pixel >> 0) & 0xFF;
}
sumAlpha /= count;
sumRed /= count;
sumGreen /= count;
sumBlue /= count;
*output = (sumAlpha << 24) | (sumRed << 16) | (sumGreen << 8) | (sumBlue << 0);
output += originalWidth;
}
}
for (uint32_t i = 0; i < targetHeight; i++) {
for (uint32_t j = 0; j < targetWidth; j++) {
bits[i * targetWidth + j] = bits[i * originalWidth + j];
}
}
}
void ThumbnailGenerateTask(Instance *, Task *task) {
EsMessageMutexAcquire();
Thumbnail *thumbnail = thumbnailCache.Get(&task->id);
bool cancelTask = !thumbnail || thumbnail->referenceCount == 0 || EsWorkIsExiting();
EsMessageMutexRelease();
if (cancelTask) {
return; // There are no longer any list items visible for this file.
}
EsFileInformation information = EsFileOpen(STRING(task->string), ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND);
if (ES_SUCCESS != information.error) {
return; // The file could not be loaded.
}
if (information.size > 64 * 1024 * 1024) {
EsHandleClose(information.handle);
return; // The file is too large.
}
size_t fileBytes;
void *file = EsFileReadAllFromHandle(information.handle, &fileBytes);
EsHandleClose(information.handle);
if (!file) {
return; // The file could not be loaded.
}
// TODO Allow applications to register their own thumbnail generators.
uint32_t originalWidth, originalHeight;
uint32_t *originalBits = (uint32_t *) EsImageLoad(file, fileBytes, &originalWidth, &originalHeight, 4);
EsHeapFree(file);
if (!originalBits) {
return; // The image could not be loaded.
}
// TODO Determine the best value for these constants -- maybe base it off the current UI scale factor?
uint32_t thumbnailMaximumWidth = 143;
uint32_t thumbnailMaximumHeight = 80;
EsRectangle targetRectangle = EsRectangleFit(ES_RECT_2S(thumbnailMaximumWidth, thumbnailMaximumHeight), ES_RECT_2S(originalWidth, originalHeight), false);
uint32_t targetWidth = ES_RECT_WIDTH(targetRectangle), targetHeight = ES_RECT_HEIGHT(targetRectangle);
uint32_t *targetBits;
if (targetWidth == originalWidth && targetHeight == originalHeight) {
targetBits = originalBits;
} else {
ThumbnailResize(originalBits, originalWidth, originalHeight, targetWidth, targetHeight);
targetBits = (uint32_t *) EsHeapReallocate(originalBits, targetWidth * targetHeight * 4, false);
}
EsMessageMutexAcquire();
thumbnail = thumbnailCache.Get(&task->id);
if (thumbnail) {
if (thumbnail->bits) EsHeapFree(thumbnail->bits);
thumbnail->bits = targetBits;
thumbnail->width = targetWidth;
thumbnail->height = targetHeight;
// TODO Submit width/height properties.
}
EsMessageMutexRelease();
}
void ThumbnailGenerateTaskComplete(Instance *, Task *task) {
Thumbnail *thumbnail = thumbnailCache.Get(&task->id);
if (thumbnail) {
thumbnail->generatingTasksInProgress--;
}
String parent = PathGetParent(task->string);
for (uintptr_t i = 0; i < instances.Length(); i++) {
if (!instances[i]->closed && StringEquals(parent, instances[i]->path)) { // TODO Support recursive views.
EsListViewInvalidateAll(instances[i]->list);
}
}
StringDestroy(&task->string);
}
void ThumbnailGenerateIfNeeded(Folder *folder, FolderEntry *entry, bool fromFolderRename, bool modified) {
FileType *fileType = FolderEntryGetType(folder, entry);
if (!fileType->hasThumbnailGenerator) {
return; // The file type does not support thumbnail generation.
}
Thumbnail *thumbnail;
// TODO Remove from LRU if needed.
if (modified) {
thumbnail = thumbnailCache.Get(&entry->id);
if (!thumbnail || (!thumbnail->generatingTasksInProgress && !thumbnail->bits)) {
return; // The thumbnail is not in use.
}
} else {
thumbnail = thumbnailCache.Put(&entry->id);
if (!fromFolderRename) {
thumbnail->referenceCount++;
}
if ((thumbnail->generatingTasksInProgress && !fromFolderRename) || thumbnail->bits) {
return; // The thumbnail is already being/has already been generated.
}
}
thumbnail->generatingTasksInProgress++;
String path = StringAllocateAndFormat("%s%s", STRFMT(folder->path), STRFMT(entry->GetInternalName()));
Task task = {
.string = path,
.id = entry->id,
.callback = ThumbnailGenerateTask,
.then = ThumbnailGenerateTaskComplete,
};
NonBlockingTaskQueue(task);
}
void ListItemCreated(EsElement *element, uintptr_t index, bool fromFolderRename) {
Instance *instance = element->instance;
if (instance->viewSettings.viewType != VIEW_THUMBNAILS && instance->viewSettings.viewType != VIEW_TILES) {
return; // The current view does not display thumbnails.
}
ThumbnailGenerateIfNeeded(instance->folder, instance->listContents[index].entry, fromFolderRename, false /* not modified */);
}
Thumbnail *ListItemGetThumbnail(EsElement *element) {
Instance *instance = element->instance;
ListEntry *entry = &instance->listContents[EsListViewGetIndexFromItem(element)];
Thumbnail *thumbnail = thumbnailCache.Get(&entry->entry->id);
return thumbnail;
}
int ListItemMessage(EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_DESTROY) {
Thumbnail *thumbnail = ListItemGetThumbnail(element);
if (thumbnail) {
thumbnail->referenceCount--;
// TODO When the referenceCount drops to 0, put it on a LRU list.
}
} else if (message->type == ES_MSG_PAINT_ICON) {
if (element->instance->viewSettings.viewType == VIEW_THUMBNAILS || element->instance->viewSettings.viewType == VIEW_TILES) {
Thumbnail *thumbnail = ListItemGetThumbnail(element);
if (thumbnail && thumbnail->bits) {
EsRectangle destination = EsPainterBoundsClient(message->painter);
EsRectangle source = ES_RECT_2S(thumbnail->width, thumbnail->height);
destination = EsRectangleFit(destination, source, true /* allow scaling up */);
// EsDrawBlock(message->painter, EsRectangleAdd(destination, ES_RECT_1(2)), 0x20000000);
EsDrawBitmapScaled(message->painter, destination, source, thumbnail->bits, thumbnail->width * 4, 0xFF);
return ES_HANDLED;
}
}
}
return 0;
}
int ListCallback(EsElement *element, EsMessage *message) {
Instance *instance = element->instance;
if (message->type == ES_MSG_FOCUSED_START || message->type == ES_MSG_PRIMARY_CLIPBOARD_UPDATED) {
InstanceUpdateItemSelectionCountCommands(instance);
return 0;
} else if (message->type == ES_MSG_DESTROY) {
if (EsElementIsFocused(element)) {
InstanceRemoveItemSelectionCommands(instance);
}
} else if (message->type == ES_MSG_FOCUSED_END) {
InstanceRemoveItemSelectionCommands(instance);
return 0;
} else if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) {
int column = message->getContent.columnID, index = message->getContent.index;
EsAssert(index < (int) instance->listContents.Length() && index >= 0);
ListEntry *listEntry = &instance->listContents[index];
FolderEntry *entry = listEntry->entry;
FileType *fileType = FolderEntryGetType(instance->folder, entry);
if (column == COLUMN_NAME) {
String name = entry->GetName();
EsBufferFormat(message->getContent.buffer, "%s", name.bytes, name.text);
message->getContent.icon = fileType->iconID;
} else if (column == COLUMN_TYPE) {
EsBufferFormat(message->getContent.buffer, "%s", fileType->nameBytes, fileType->name);
} else if (column == COLUMN_SIZE) {
if (!entry->sizeUnknown) {
EsBufferFormat(message->getContent.buffer, "%D", entry->size);
}
}
} else if (message->type == ES_MSG_LIST_VIEW_GET_ITEM_DATA) {
int column = message->getItemData.columnID, index = message->getItemData.index;
EsAssert(index < (int) instance->listContents.Length() && index >= 0);
ListEntry *listEntry = &instance->listContents[index];
FolderEntry *entry = listEntry->entry;
FileType *fileType = FolderEntryGetType(instance->folder, entry);
if (column == COLUMN_NAME) {
String name = entry->GetName();
message->getItemData.s = name.text;
message->getItemData.sBytes = name.bytes;
} else if (column == COLUMN_TYPE) {
message->getItemData.s = fileType->name;
message->getItemData.sBytes = fileType->nameBytes;
} else if (column == COLUMN_SIZE) {
message->getItemData.i = entry->size;
}
} else if (message->type == ES_MSG_LIST_VIEW_GET_SUMMARY) {
int index = message->getContent.index;
EsAssert(index < (int) instance->listContents.Length() && index >= 0);
ListEntry *listEntry = &instance->listContents[index];
FolderEntry *entry = listEntry->entry;
FileType *fileType = FolderEntryGetType(instance->folder, entry);
String name = entry->GetName();
bool isOpen = false;
{
// Check if the file is an open document.
// TODO Is this slow?
String path = instance->folder->itemHandler->getPathForChild(instance->folder, entry);
for (uintptr_t i = 0; i < openDocuments.Length(); i++) {
if (StringEquals(openDocuments[i], path)) {
isOpen = true;
break;
}
}
StringDestroy(&path);
}
EsBufferFormat(message->getContent.buffer, "%z%s\n\a2w4]%s " HYPHENATION_POINT " %D",
isOpen ? "\aw6]" : "",
name.bytes, name.text, fileType->nameBytes, fileType->name, entry->size);
message->getContent.icon = fileType->iconID;
message->getContent.drawContentFlags = ES_DRAW_CONTENT_RICH_TEXT;
} else if (message->type == ES_MSG_LIST_VIEW_SELECT_RANGE) {
for (intptr_t i = message->selectRange.fromIndex; i <= message->selectRange.toIndex; i++) {
ListEntry *entry = &instance->listContents[i];
if (entry->selected) { instance->selectedItemCount--; instance->selectedItemsTotalSize -= entry->entry->size; }
entry->selected = message->selectRange.toggle ? !entry->selected : message->selectRange.select;
if (entry->selected) { instance->selectedItemCount++; instance->selectedItemsTotalSize += entry->entry->size; }
}
InstanceUpdateItemSelectionCountCommands(instance);
StringDestroy(&instance->delayedFocusItem);
InstanceUpdateStatusString(instance);
} else if (message->type == ES_MSG_LIST_VIEW_SELECT) {
ListEntry *entry = &instance->listContents[message->selectItem.index];
if (entry->selected) { instance->selectedItemCount--; instance->selectedItemsTotalSize -= entry->entry->size; }
entry->selected = message->selectItem.isSelected;
if (entry->selected) { instance->selectedItemCount++; instance->selectedItemsTotalSize += entry->entry->size; }
InstanceUpdateItemSelectionCountCommands(instance);
StringDestroy(&instance->delayedFocusItem);
InstanceUpdateStatusString(instance);
} else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) {
ListEntry *entry = &instance->listContents[message->selectItem.index];
message->selectItem.isSelected = entry->selected;
} else if (message->type == ES_MSG_LIST_VIEW_CHOOSE_ITEM) {
ListEntry *listEntry = &instance->listContents[message->chooseItem.index];
if (listEntry) {
FolderEntry *entry = listEntry->entry;
if (entry->isFolder) {
String path = instance->folder->itemHandler->getPathForChild(instance->folder, entry);
if (EsKeyboardIsCtrlHeld() || message->chooseItem.source == ES_LIST_VIEW_CHOOSE_ITEM_MIDDLE_CLICK) {
EsApplicationStartupRequest request = {};
request.id = EsInstanceGetStartupRequest(instance).id;
request.filePath = path.text;
request.filePathBytes = path.bytes;
request.flags = ES_APPLICATION_STARTUP_IN_SAME_CONTAINER | ES_APPLICATION_STARTUP_NO_DOCUMENT;
EsApplicationStart(instance, &request);
StringDestroy(&path);
} else {
InstanceLoadFolder(instance, path);
}
} else if (StringEquals(entry->GetExtension(), StringFromLiteral("esx"))) {
// TODO Temporary.
String path = StringAllocateAndFormat("%s%s", STRFMT(instance->folder->path), STRFMT(entry->GetInternalName()));
if (StringEquals(path, StringFromLiteral("0:/Essence/Desktop.esx"))
|| StringEquals(path, StringFromLiteral("0:/Essence/Kernel.esx"))) {
EsDialogShow(instance->window, INTERFACE_STRING(FileManagerOpenFileError),
INTERFACE_STRING(FileManagerCannotOpenSystemFile),
ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON);
} else {
EsApplicationRunTemporary(STRING(path));
}
StringDestroy(&path);
} else {
FileType *fileType = FolderEntryGetType(instance->folder, entry);
if (fileType->openHandler) {
String path = StringAllocateAndFormat("%s%s", STRFMT(instance->folder->path), STRFMT(entry->GetInternalName()));
EsApplicationStartupRequest request = {};
request.id = fileType->openHandler;
request.filePath = path.text;
request.filePathBytes = path.bytes;
request.flags = EsKeyboardIsCtrlHeld() || message->chooseItem.source == ES_LIST_VIEW_CHOOSE_ITEM_MIDDLE_CLICK
? ES_APPLICATION_STARTUP_IN_SAME_CONTAINER : ES_FLAGS_DEFAULT;
EsApplicationStart(instance, &request);
StringDestroy(&path);
} else {
EsDialogShow(instance->window, INTERFACE_STRING(FileManagerOpenFileError),
INTERFACE_STRING(FileManagerNoRegisteredApplicationsForFile),
ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON);
}
}
}
} else if (message->type == ES_MSG_LIST_VIEW_COLUMN_MENU) {
EsMenu *menu = EsMenuCreate(message->columnMenu.source);
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,
ascending, -1, InstanceChangeSortColumn, index);
EsMenuAddItem(menu, instance->viewSettings.sortColumn == (index | (1 << 8)) ? ES_MENU_ITEM_CHECKED : 0,
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)) {
return (instance->viewSettings.sortColumn & (1 << 8)) ? ES_LIST_VIEW_COLUMN_SORT_DESCENDING : ES_LIST_VIEW_COLUMN_SORT_ASCENDING;
}
} else if (message->type == ES_MSG_LIST_VIEW_CREATE_ITEM) {
EsElement *element = message->createItem.item;
element->messageUser = ListItemMessage;
ListItemCreated(element, message->createItem.index, false);
} else if (message->type == ES_MSG_LIST_VIEW_CONTEXT_MENU) {
EsMenu *menu = EsMenuCreate(element, ES_MENU_AT_CURSOR);
EsOpenDocumentInformation information;
ListEntry *entry = &instance->listContents[message->selectItem.index];
String path = instance->folder->itemHandler->getPathForChild(instance->folder, entry->entry);
EsOpenDocumentQueryInformation(STRING(path), &information);
StringDestroy(&path);
if (information.isOpen) {
char buffer[256];
size_t bytes = EsStringFormat(buffer, sizeof(buffer), interfaceString_FileManagerFileOpenIn,
(size_t) information.applicationNameBytes, (const char *) information.applicationName);
EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, buffer, bytes);
EsMenuAddSeparator(menu);
}
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonClipboardCut), EsCommandByID(instance, ES_COMMAND_CUT));
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonClipboardCopy), EsCommandByID(instance, ES_COMMAND_COPY));
EsMenuAddSeparator(menu);
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(FileManagerRenameAction), &instance->commandRename);
EsMenuShow(menu);
} else if (message->type == ES_MSG_MOUSE_RIGHT_CLICK) {
EsMenu *menu = EsMenuCreate(element, ES_MENU_AT_CURSOR);
EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(FileManagerListContextActions));
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonClipboardPaste), EsCommandByID(instance, ES_COMMAND_PASTE));
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonSelectionSelectAll), EsCommandByID(instance, ES_COMMAND_SELECT_ALL));
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(FileManagerNewFolderToolbarItem), &instance->commandNewFolder);
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(FileManagerRefresh), &instance->commandRefresh);
EsMenuAddSeparator(menu);
#define ADD_VIEW_TYPE_MENU_ITEM(_command, _string) \
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(_string), _command)
EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(CommonListViewType));
ADD_VIEW_TYPE_MENU_ITEM(&instance->commandViewThumbnails, CommonListViewTypeThumbnails);
ADD_VIEW_TYPE_MENU_ITEM(&instance->commandViewTiles, CommonListViewTypeTiles);
ADD_VIEW_TYPE_MENU_ITEM(&instance->commandViewDetails, CommonListViewTypeDetails);
#undef ADD_VIEW_TYPE_MENU_ITEM
EsMenuNextColumn(menu);
#define ADD_SORT_COLUMN_MENU_ITEM(_column, _string) \
EsMenuAddItem(menu, instance->viewSettings.sortColumn == (_column) ? ES_MENU_ITEM_CHECKED : ES_FLAGS_DEFAULT, \
INTERFACE_STRING(_string), InstanceChangeSortColumn, _column)
EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(CommonSortAscending));
ADD_SORT_COLUMN_MENU_ITEM(COLUMN_NAME, FileManagerColumnName);
ADD_SORT_COLUMN_MENU_ITEM(COLUMN_TYPE, FileManagerColumnType);
ADD_SORT_COLUMN_MENU_ITEM(COLUMN_SIZE, FileManagerColumnSize);
EsMenuAddSeparator(menu);
EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(CommonSortDescending));
ADD_SORT_COLUMN_MENU_ITEM(COLUMN_NAME | (1 << 8), FileManagerColumnName);
ADD_SORT_COLUMN_MENU_ITEM(COLUMN_TYPE | (1 << 8), FileManagerColumnType);
ADD_SORT_COLUMN_MENU_ITEM(COLUMN_SIZE | (1 << 8), FileManagerColumnSize);
#undef ADD_SORT_COLUMN_MENU_ITEM
EsMenuShow(menu);
} else {
return 0;
}
return ES_HANDLED;
}
int PlacesViewCallback(EsElement *element, EsMessage *message) {
Instance *instance = element->instance;
if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) {
int group = message->getContent.group;
int index = message->getContent.index;
if (group == PLACES_VIEW_GROUP_DRIVES) {
// TODO Use namespace lookup.
if (index == 0) {
EsBufferFormat(message->getContent.buffer, "%z", interfaceString_FileManagerPlacesDrives);
message->getContent.icon = ES_ICON_COMPUTER_LAPTOP;
} else {
Drive *drive = &drives[index - 1];
EsBufferFormat(message->getContent.buffer, "%s", drive->information.labelBytes, drive->information.label);
message->getContent.icon = EsIconIDFromDriveType(drive->information.driveType);
}
} else if (group == PLACES_VIEW_GROUP_BOOKMARKS) {
if (index == 0) {
EsBufferFormat(message->getContent.buffer, "%z", interfaceString_FileManagerPlacesBookmarks);
message->getContent.icon = ES_ICON_HELP_ABOUT;
} else {
// TODO The namespace lookup might be expensive. Perhaps these should be cached?
NamespaceGetVisibleName(message->getContent.buffer, bookmarks[index - 1]);
message->getContent.icon = NamespaceGetIcon(bookmarks[index - 1]);
}
}
} else if (message->type == ES_MSG_LIST_VIEW_SELECT && message->selectItem.isSelected) {
if (message->selectItem.group == PLACES_VIEW_GROUP_DRIVES) {
if (message->selectItem.index == 0) {
InstanceLoadFolder(instance, StringAllocateAndFormat("%z", interfaceString_FileManagerDrivesPage), LOAD_FOLDER_NO_FOCUS);
} else {
Drive *drive = &drives[message->selectItem.index - 1];
InstanceLoadFolder(instance, StringAllocateAndFormat("%s/", drive->prefixBytes, drive->prefix), LOAD_FOLDER_NO_FOCUS);
}
} else if (message->selectItem.group == PLACES_VIEW_GROUP_BOOKMARKS && message->selectItem.index) {
String string = bookmarks[message->selectItem.index - 1];
InstanceLoadFolder(instance, StringAllocateAndFormat("%s", STRFMT(string)), LOAD_FOLDER_NO_FOCUS);
}
} else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) {
if (message->selectItem.group == PLACES_VIEW_GROUP_DRIVES) {
if (message->selectItem.index == 0) {
message->selectItem.isSelected = 0 == EsStringCompareRaw(INTERFACE_STRING(FileManagerDrivesPage),
instance->path.text, instance->path.bytes);
} else {
Drive *drive = &drives[message->selectItem.index - 1];
message->selectItem.isSelected = 0 == EsStringCompareRaw(drive->prefix, drive->prefixBytes,
instance->path.text, instance->path.bytes - 1);
}
} else if (message->selectItem.group == PLACES_VIEW_GROUP_BOOKMARKS && message->selectItem.index) {
String string = bookmarks[message->selectItem.index - 1];
message->selectItem.isSelected = 0 == EsStringCompareRaw(string.text, string.bytes,
instance->path.text, instance->path.bytes);
}
} else if (message->type == ES_MSG_LIST_VIEW_CONTEXT_MENU) {
if (message->selectItem.group == PLACES_VIEW_GROUP_BOOKMARKS && !message->selectItem.index) {
bool isCurrentFolderBookmarked = false;
for (uintptr_t i = 0; i < bookmarks.Length(); i++) {
if (0 == EsStringCompareRaw(STRING(bookmarks[i]), STRING(instance->path))) {
isCurrentFolderBookmarked = true;
break;
}
}
EsMenu *menu = EsMenuCreate(element, ES_MENU_AT_CURSOR);
if (isCurrentFolderBookmarked) {
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(FileManagerBookmarksRemoveHere), [] (EsMenu *menu, EsGeneric) {
BookmarkRemove(menu->instance->path);
});
} else {
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(FileManagerBookmarksAddHere), [] (EsMenu *menu, EsGeneric) {
BookmarkAdd(menu->instance->path);
});
}
EsMenuShow(menu);
}
} else {
return 0;
}
return ES_HANDLED;
}
int BreadcrumbBarMessage(EsElement *element, EsMessage *message) {
Instance *instance = element->instance;
EsTextbox *textbox = (EsTextbox *) element;
if (message->type == ES_MSG_TEXTBOX_ACTIVATE_BREADCRUMB) {
String section = PathGetSection(instance->folder->path, message->activateBreadcrumb);
size_t bytes = section.text + section.bytes - instance->folder->path.text;
String path = StringAllocateAndFormat("%s%z",
bytes, instance->folder->path.text,
instance->folder->path.text[bytes - 1] != '/' ? "/" : "");
InstanceLoadFolder(instance, path);
} else if (message->type == ES_MSG_TEXTBOX_EDIT_END) {
String section;
section.text = EsTextboxGetContents(textbox, &section.bytes);
section.allocated = section.bytes;
String path = StringAllocateAndFormat("%s%z",
section.bytes, section.text,
section.text[section.bytes - 1] != '/' ? "/" : "");
if (!InstanceLoadFolder(instance, path)) {
StringDestroy(&section);
return ES_REJECTED;
}
StringDestroy(&section);
} else if (message->type == ES_MSG_TEXTBOX_GET_BREADCRUMB) {
if (!instance->folder || PathCountSections(instance->folder->path) == message->getBreadcrumb.index) {
return ES_REJECTED;
}
String path = PathGetParent(instance->folder->path, message->getBreadcrumb.index);
NamespaceGetVisibleName(message->getBreadcrumb.buffer, path);
message->getBreadcrumb.icon = message->getBreadcrumb.index ? 0 : NamespaceGetIcon(path);
return ES_HANDLED;
}
return 0;
}
#define ADD_BUTTON_TO_TOOLBAR(_command, _label, _icon, _accessKey, _name) \
{ \
_name = EsButtonCreate(buttonGroup, ES_FLAGS_DEFAULT, 0, _label); \
EsButtonSetIcon(_name, _icon); \
EsCommandAddButton(&instance->_command, _name); \
_name->accessKey = _accessKey; \
}
#define ADD_BUTTON_TO_STATUS_BAR(_command, _label, _icon, _accessKey, _name) \
{ \
_name = EsButtonCreate(buttonGroup, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_STATUS_BAR, _label); \
EsButtonSetIcon(_name, _icon); \
EsCommandAddButton(&instance->_command, _name); \
_name->accessKey = _accessKey; \
}
void InstanceCreateUI(Instance *instance) {
EsButton *button;
EsWindowSetIcon(instance->window, ES_ICON_SYSTEM_FILE_MANAGER);
InstanceRegisterCommands(instance);
EsPanel *rootPanel = EsPanelCreate(instance->window, ES_CELL_FILL | ES_PANEL_VERTICAL);
EsSplitter *splitter = EsSplitterCreate(rootPanel, ES_SPLITTER_HORIZONTAL | ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_WITH_STATUS_BAR_CONTENT);
// Places:
instance->placesView = EsListViewCreate(splitter, ES_CELL_EXPAND | ES_CELL_COLLAPSABLE | ES_LIST_VIEW_SINGLE_SELECT | ES_CELL_V_FILL,
EsStyleIntern(&stylePlacesView), ES_STYLE_LIST_ITEM, ES_STYLE_LIST_ITEM, 0);
instance->placesView->accessKey = 'P';
instance->placesView->messageUser = PlacesViewCallback;
EsListViewInsertGroup(instance->placesView, PLACES_VIEW_GROUP_BOOKMARKS, ES_LIST_VIEW_GROUP_HAS_HEADER | ES_LIST_VIEW_GROUP_INDENT);
EsListViewInsertGroup(instance->placesView, PLACES_VIEW_GROUP_DRIVES, ES_LIST_VIEW_GROUP_HAS_HEADER | ES_LIST_VIEW_GROUP_INDENT);
if (bookmarks.Length()) EsListViewInsert(instance->placesView, PLACES_VIEW_GROUP_BOOKMARKS, 1, bookmarks.Length());
EsListViewInsert(instance->placesView, PLACES_VIEW_GROUP_DRIVES, 1, drives.Length());
// Main list:
instance->list = EsListViewCreate(splitter, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_MULTI_SELECT, EsStyleIntern(&styleFolderView));
instance->list->accessKey = 'L';
instance->list->messageUser = ListCallback;
EsListViewRegisterColumn(instance->list, COLUMN_NAME, INTERFACE_STRING(FileManagerColumnName), ES_LIST_VIEW_COLUMN_HAS_MENU);
EsListViewRegisterColumn(instance->list, COLUMN_TYPE, INTERFACE_STRING(FileManagerColumnType), ES_LIST_VIEW_COLUMN_HAS_MENU);
EsListViewRegisterColumn(instance->list, COLUMN_SIZE, INTERFACE_STRING(FileManagerColumnSize),
ES_LIST_VIEW_COLUMN_HAS_MENU | ES_TEXT_H_RIGHT | ES_LIST_VIEW_COLUMN_DATA_INTEGERS | ES_LIST_VIEW_COLUMN_FORMAT_BYTES);
EsListViewAddAllColumns(instance->list);
EsListViewInsertGroup(instance->list, 0);
// Toolbar:
EsElement *toolbar = EsWindowGetToolbar(instance->window);
EsPanel *buttonGroup = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL | ES_ELEMENT_AUTO_GROUP);
ADD_BUTTON_TO_TOOLBAR(commandGoBackwards, nullptr, ES_ICON_GO_PREVIOUS_SYMBOLIC, 'B', button);
EsSpacerCreate(buttonGroup, ES_CELL_V_FILL, ES_STYLE_TOOLBAR_BUTTON_GROUP_SEPARATOR);
ADD_BUTTON_TO_TOOLBAR(commandGoForwards, nullptr, ES_ICON_GO_NEXT_SYMBOLIC, 'F', button);
EsSpacerCreate(buttonGroup, ES_CELL_V_FILL, ES_STYLE_TOOLBAR_BUTTON_GROUP_SEPARATOR);
ADD_BUTTON_TO_TOOLBAR(commandGoParent, nullptr, ES_ICON_GO_UP_SYMBOLIC, 'U', button);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, ES_STYLE_TOOLBAR_SPACER);
instance->breadcrumbBar = EsTextboxCreate(toolbar, ES_CELL_H_FILL | ES_TEXTBOX_EDIT_BASED | ES_TEXTBOX_REJECT_EDIT_IF_LOST_FOCUS);
instance->breadcrumbBar->messageUser = BreadcrumbBarMessage;
instance->breadcrumbBar->accessKey = 'A';
EsTextboxUseBreadcrumbOverlay(instance->breadcrumbBar);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, ES_STYLE_TOOLBAR_SPACER);
buttonGroup = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL);
ADD_BUTTON_TO_TOOLBAR(commandNewFolder, interfaceString_FileManagerNewFolderToolbarItem, ES_ICON_FOLDER_NEW_SYMBOLIC, 'N', instance->newFolderButton);
// Status bar:
EsPanel *statusBar = EsPanelCreate(rootPanel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_STATUS_BAR);
instance->status = EsTextDisplayCreate(statusBar, ES_CELL_H_FILL);
buttonGroup = EsPanelCreate(statusBar, ES_PANEL_HORIZONTAL | ES_ELEMENT_AUTO_GROUP);
ADD_BUTTON_TO_STATUS_BAR(commandViewDetails, nullptr, ES_ICON_VIEW_LIST_SYMBOLIC, 0, button);
EsSpacerCreate(buttonGroup, ES_CELL_V_FILL, ES_STYLE_TOOLBAR_BUTTON_GROUP_SEPARATOR);
ADD_BUTTON_TO_STATUS_BAR(commandViewTiles, nullptr, ES_ICON_VIEW_LIST_COMPACT_SYMBOLIC, 0, button);
EsSpacerCreate(buttonGroup, ES_CELL_V_FILL, ES_STYLE_TOOLBAR_BUTTON_GROUP_SEPARATOR);
ADD_BUTTON_TO_STATUS_BAR(commandViewThumbnails, nullptr, ES_ICON_VIEW_GRID_SYMBOLIC, 0, button);
// Load initial folder:
EsApplicationStartupRequest startupRequest = EsInstanceGetStartupRequest(instance);
String path;
if (startupRequest.filePathBytes) {
uintptr_t directoryEnd = startupRequest.filePathBytes;
for (uintptr_t i = 0; i < (uintptr_t) startupRequest.filePathBytes; i++) {
if (startupRequest.filePath[i] == '/') {
directoryEnd = i + 1;
}
}
instance->delayedFocusItem = StringAllocateAndFormat("%s", startupRequest.filePathBytes - directoryEnd, startupRequest.filePath + directoryEnd);
path = StringAllocateAndFormat("%s", directoryEnd, startupRequest.filePath);
} else {
path = StringAllocateAndFormat("0:/");
}
InstanceLoadFolder(instance, path, LOAD_FOLDER_START);
}
void InstanceReportError(Instance *instance, int error, EsError code) {
EsPrint("FM error %d/%d.\n", error, code);
// TODO Error messages.
const char *message = interfaceString_FileManagerGenericError;
if (code == ES_ERROR_ALREADY_EXISTS) {
message = interfaceString_FileManagerItemAlreadyExistsError;
} else if (code == ES_ERROR_FILE_DOES_NOT_EXIST) {
message = interfaceString_FileManagerItemDoesNotExistError;
} else if (code == ES_ERROR_PERMISSION_NOT_GRANTED) {
message = interfaceString_FileManagerPermissionNotGrantedError;
}
EsDialogShow(instance->window, errorTypeStrings[error], -1, message, -1,
ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON);
}
void InstanceDestroy(Instance *instance) {
StringDestroy(&instance->path);
StringDestroy(&instance->delayedFocusItem);
for (uintptr_t i = 0; i < instance->pathBackwardHistory.Length(); i++) {
StringDestroy(&instance->pathBackwardHistory[i].path);
StringDestroy(&instance->pathBackwardHistory[i].focusedItem);
}
for (uintptr_t i = 0; i < instance->pathForwardHistory.Length(); i++) {
StringDestroy(&instance->pathForwardHistory[i].path);
StringDestroy(&instance->pathForwardHistory[i].focusedItem);
}
for (uintptr_t i = 0; i < instance->listContents.Length(); i++) {
FolderEntryCloseHandle(instance->folder, instance->listContents[i].entry);
}
FolderDetachInstance(instance);
instance->pathBackwardHistory.Free();
instance->pathForwardHistory.Free();
instance->listContents.Free();
}