file types and sorting

coming soon: colors and icons
This commit is contained in:
tildearrow 2025-09-26 05:14:29 -05:00
parent ebecfb1992
commit 19b3cdefd1
3 changed files with 149 additions and 23 deletions

View file

@ -7770,7 +7770,7 @@ bool FurnaceGUI::init() {
newFilePicker=new FurnaceFilePicker;
newFilePicker->setHomeDir(getHomeDir());
newFilePicker->open("New File Picker","/home",false);
newFilePicker->open("New File Picker","/home",false,{});
updateWindowTitle();
updateROMExportAvail();

View file

@ -25,10 +25,13 @@
#include "misc/cpp/imgui_stdlib.h"
#include "../ta-log.h"
#include <algorithm>
#include <chrono>
#include <dirent.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
static const char* sizeSuffixes=".KMGTPEZ";
@ -55,7 +58,13 @@ void FurnaceFilePicker::readDirectorySub() {
if (strcmp(entry->d_name,"..")==0) continue;
FileEntry* newEntry=new FileEntry;
newEntry->name=entry->d_name;
newEntry->nameLower=entry->d_name;
for (char& i: newEntry->nameLower) {
if (i>='A' && i<='Z') i+='a'-'A';
}
switch (entry->d_type) {
case DT_REG:
newEntry->type=FP_TYPE_NORMAL;
@ -64,10 +73,26 @@ void FurnaceFilePicker::readDirectorySub() {
newEntry->type=FP_TYPE_DIR;
newEntry->isDir=true;
break;
case DT_LNK:
case DT_LNK: {
newEntry->type=FP_TYPE_LINK;
// TODO: resolve link
// resolve link to determine whether this is a directory
String readLinkPath;
DIR* readLinkDir=NULL;
if (*path.rbegin()=='/') {
readLinkPath=path+newEntry->name;
} else {
readLinkPath=path+'/'+newEntry->name;
}
// silly, but works.
logV("Read a symlink...");
readLinkDir=opendir(readLinkPath.c_str());
if (readLinkDir!=NULL) {
logV("Is file");
newEntry->isDir=true;
closedir(readLinkDir);
}
break;
}
case DT_SOCK:
newEntry->type=FP_TYPE_SOCKET;
break;
@ -76,6 +101,16 @@ void FurnaceFilePicker::readDirectorySub() {
break;
}
if (!newEntry->isDir) {
size_t extPos=newEntry->name.rfind('.');
if (extPos!=String::npos) {
newEntry->ext=newEntry->name.substr(extPos);
for (char& i: newEntry->ext) {
if (i>='A' && i<='Z') i+='a'-'A';
}
}
}
entries.push_back(newEntry);
if (stopReading) {
break;
@ -108,6 +143,7 @@ void FurnaceFilePicker::readDirectorySub() {
}
// read file information
entryLock.lock();
struct tm* retTM=localtime_r(&st.st_mtime,&i->time);
if (retTM!=NULL) {
i->hasTime=true;
@ -115,6 +151,7 @@ void FurnaceFilePicker::readDirectorySub() {
i->size=st.st_size;
i->hasSize=true;
entryLock.unlock();
}
haveStat=true;
}
@ -162,25 +199,67 @@ void FurnaceFilePicker::updateEntryName() {
}
void FurnaceFilePicker::sortFiles() {
std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now();
entryLock.lock();
sortedEntries=entries;
entryLock.unlock();
// sort by name
std::sort(sortedEntries.begin(),sortedEntries.end(),[](const FileEntry* a, const FileEntry* b) -> bool {
std::sort(sortedEntries.begin(),sortedEntries.end(),[this](const FileEntry* a, const FileEntry* b) -> bool {
if (a->isDir && !b->isDir) return true;
if (!a->isDir && b->isDir) return false;
String aLower=a->name;
for (char& i: aLower) {
if (i>='A' && i<='Z') i+='a'-'A';
switch (sortMode) {
case FP_SORT_NAME: {
// don't do anything. this is handled below.
break;
}
case FP_SORT_EXT: {
int result=a->ext.compare(b->ext);
// only sort if extensions differ
if (result!=0) {
return result<0;
}
break;
}
case FP_SORT_SIZE: {
// only sort if sizes differ
if (a->size!=b->size) {
return a->size<b->size;
}
break;
}
case FP_SORT_DATE: {
// only sort if dates differ
if (a->time.tm_year==b->time.tm_year) {
if (a->time.tm_mon==b->time.tm_mon) {
if (a->time.tm_mday==b->time.tm_mday) {
if (a->time.tm_hour==b->time.tm_hour) {
if (a->time.tm_min==b->time.tm_min) {
if (a->time.tm_sec==b->time.tm_sec) {
// fall back to sorting by name
return a->nameLower<b->nameLower;
}
return a->time.tm_sec<b->time.tm_sec;
}
return a->time.tm_min<b->time.tm_min;
}
return a->time.tm_hour<b->time.tm_hour;
}
return a->time.tm_mday<b->time.tm_mday;
}
return a->time.tm_mon<b->time.tm_mon;
}
return a->time.tm_year<b->time.tm_year;
break;
}
}
String bLower=b->name;
for (char& i: bLower) {
if (i>='A' && i<='Z') i+='a'-'A';
}
return aLower<bLower;
// fall back to sorting by name
return a->nameLower<b->nameLower;
});
entryLock.unlock();
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
logV("sortFiles() took %dµs",std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count());
}
void FurnaceFilePicker::filterFiles() {
@ -271,26 +350,60 @@ bool FurnaceFilePicker::draw() {
}
}
} else {
if (ImGui::BeginTable("FileList",3,ImGuiTableFlags_Borders|ImGuiTableFlags_ScrollY,tableSize)) {
// this is the list view. I might add other view modes in the future...
if (ImGui::BeginTable("FileList",4,ImGuiTableFlags_Borders|ImGuiTableFlags_ScrollY,tableSize)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize(" 999.99G").x);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize(" 6969/69/69 04:20").x);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize(" .eeee").x);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize(" 999.99G").x);
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize(" 6969/69/69 04:20").x);
ImGui::TableSetupScrollFreeze(0,1);
// header (sort options)
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableNextColumn();
ImGui::Text("Name");
if (ImGui::Selectable("Name##SortName")) {
if (sortMode==FP_SORT_NAME) {
sortInvert=!sortInvert;
} else {
sortMode=FP_SORT_NAME;
scheduledSort=1;
}
}
ImGui::TableNextColumn();
ImGui::Text("Size");
if (ImGui::Selectable("Type##SortType")) {
if (sortMode==FP_SORT_EXT) {
sortInvert=!sortInvert;
} else {
sortMode=FP_SORT_EXT;
scheduledSort=1;
}
}
ImGui::TableNextColumn();
ImGui::Text("Date");
if (ImGui::Selectable("Size##SortSize")) {
if (sortMode==FP_SORT_SIZE) {
sortInvert=!sortInvert;
} else {
sortMode=FP_SORT_SIZE;
scheduledSort=1;
}
}
ImGui::TableNextColumn();
if (ImGui::Selectable("Date##SortDate")) {
if (sortMode==FP_SORT_DATE) {
sortInvert=!sortInvert;
} else {
sortMode=FP_SORT_DATE;
scheduledSort=1;
}
}
// file list
entryLock.lock();
int index=0;
listClipper.Begin(filteredEntries.size());
while (listClipper.Step()) {
for (int _i=listClipper.DisplayStart; _i<listClipper.DisplayEnd; _i++) {
FileEntry* i=filteredEntries[_i];
FileEntry* i=filteredEntries[sortInvert?(filteredEntries.size()-_i-1):_i];
ImGui::TableNextRow();
ImGui::TableNextColumn();
@ -317,6 +430,9 @@ bool FurnaceFilePicker::draw() {
ImGui::TextUnformatted(i->name.c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(i->ext.c_str());
ImGui::TableNextColumn();
if (i->hasSize && i->type==FP_TYPE_NORMAL) {
int sizeShift=0;
@ -406,7 +522,7 @@ bool FurnaceFilePicker::draw() {
return false;
}
bool FurnaceFilePicker::open(String name, String path, bool modal) {
bool FurnaceFilePicker::open(String name, String path, bool modal, const std::vector<String>& filter) {
if (isOpen) return false;
readDirectory(path);
@ -444,7 +560,9 @@ FurnaceFilePicker::FurnaceFilePicker():
stopReading(false),
isOpen(false),
isMobile(false),
sortInvert(false),
scheduledSort(0),
sortMode(FP_SORT_NAME),
curStatus(FP_STATUS_WAITING) {
}

View file

@ -38,9 +38,16 @@ class FurnaceFilePicker {
FP_TYPE_PIPE,
FP_TYPE_SOCKET,
};
enum SortModes {
FP_SORT_NAME=0,
FP_SORT_EXT,
FP_SORT_SIZE,
FP_SORT_DATE
};
struct FileEntry {
String path;
String name;
String nameLower;
String ext;
bool hasSize, hasTime, isDir, isSelected;
uint64_t size;
@ -62,8 +69,9 @@ class FurnaceFilePicker {
String homeDir;
String entryName;
ImGuiListClipper listClipper;
bool haveFiles, haveStat, stopReading, isOpen, isMobile;
bool haveFiles, haveStat, stopReading, isOpen, isMobile, sortInvert;
int scheduledSort;
SortModes sortMode;
FilePickerStatus curStatus;
void sortFiles();
@ -80,7 +88,7 @@ class FurnaceFilePicker {
const std::vector<FileEntry*>& getSelected();
void setMobile(bool val);
bool draw();
bool open(String name, String path, bool modal);
bool open(String name, String path, bool modal, const std::vector<String>& filter);
void loadSettings(DivConfig& conf);
void saveSettings(DivConfig& conf);
FurnaceFilePicker();