file types and sorting
coming soon: colors and icons
This commit is contained in:
parent
ebecfb1992
commit
19b3cdefd1
3 changed files with 149 additions and 23 deletions
|
|
@ -7770,7 +7770,7 @@ bool FurnaceGUI::init() {
|
||||||
|
|
||||||
newFilePicker=new FurnaceFilePicker;
|
newFilePicker=new FurnaceFilePicker;
|
||||||
newFilePicker->setHomeDir(getHomeDir());
|
newFilePicker->setHomeDir(getHomeDir());
|
||||||
newFilePicker->open("New File Picker","/home",false);
|
newFilePicker->open("New File Picker","/home",false,{});
|
||||||
|
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
updateROMExportAvail();
|
updateROMExportAvail();
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,13 @@
|
||||||
#include "misc/cpp/imgui_stdlib.h"
|
#include "misc/cpp/imgui_stdlib.h"
|
||||||
#include "../ta-log.h"
|
#include "../ta-log.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
static const char* sizeSuffixes=".KMGTPEZ";
|
static const char* sizeSuffixes=".KMGTPEZ";
|
||||||
|
|
||||||
|
|
@ -55,7 +58,13 @@ void FurnaceFilePicker::readDirectorySub() {
|
||||||
if (strcmp(entry->d_name,"..")==0) continue;
|
if (strcmp(entry->d_name,"..")==0) continue;
|
||||||
|
|
||||||
FileEntry* newEntry=new FileEntry;
|
FileEntry* newEntry=new FileEntry;
|
||||||
|
|
||||||
newEntry->name=entry->d_name;
|
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) {
|
switch (entry->d_type) {
|
||||||
case DT_REG:
|
case DT_REG:
|
||||||
newEntry->type=FP_TYPE_NORMAL;
|
newEntry->type=FP_TYPE_NORMAL;
|
||||||
|
|
@ -64,10 +73,26 @@ void FurnaceFilePicker::readDirectorySub() {
|
||||||
newEntry->type=FP_TYPE_DIR;
|
newEntry->type=FP_TYPE_DIR;
|
||||||
newEntry->isDir=true;
|
newEntry->isDir=true;
|
||||||
break;
|
break;
|
||||||
case DT_LNK:
|
case DT_LNK: {
|
||||||
newEntry->type=FP_TYPE_LINK;
|
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;
|
break;
|
||||||
|
}
|
||||||
case DT_SOCK:
|
case DT_SOCK:
|
||||||
newEntry->type=FP_TYPE_SOCKET;
|
newEntry->type=FP_TYPE_SOCKET;
|
||||||
break;
|
break;
|
||||||
|
|
@ -76,6 +101,16 @@ void FurnaceFilePicker::readDirectorySub() {
|
||||||
break;
|
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);
|
entries.push_back(newEntry);
|
||||||
if (stopReading) {
|
if (stopReading) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -108,6 +143,7 @@ void FurnaceFilePicker::readDirectorySub() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// read file information
|
// read file information
|
||||||
|
entryLock.lock();
|
||||||
struct tm* retTM=localtime_r(&st.st_mtime,&i->time);
|
struct tm* retTM=localtime_r(&st.st_mtime,&i->time);
|
||||||
if (retTM!=NULL) {
|
if (retTM!=NULL) {
|
||||||
i->hasTime=true;
|
i->hasTime=true;
|
||||||
|
|
@ -115,6 +151,7 @@ void FurnaceFilePicker::readDirectorySub() {
|
||||||
|
|
||||||
i->size=st.st_size;
|
i->size=st.st_size;
|
||||||
i->hasSize=true;
|
i->hasSize=true;
|
||||||
|
entryLock.unlock();
|
||||||
}
|
}
|
||||||
haveStat=true;
|
haveStat=true;
|
||||||
}
|
}
|
||||||
|
|
@ -162,25 +199,67 @@ void FurnaceFilePicker::updateEntryName() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceFilePicker::sortFiles() {
|
void FurnaceFilePicker::sortFiles() {
|
||||||
|
std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now();
|
||||||
entryLock.lock();
|
entryLock.lock();
|
||||||
sortedEntries=entries;
|
sortedEntries=entries;
|
||||||
entryLock.unlock();
|
|
||||||
|
|
||||||
// sort by name
|
// 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 true;
|
||||||
if (!a->isDir && b->isDir) return false;
|
if (!a->isDir && b->isDir) return false;
|
||||||
|
|
||||||
String aLower=a->name;
|
switch (sortMode) {
|
||||||
for (char& i: aLower) {
|
case FP_SORT_NAME: {
|
||||||
if (i>='A' && i<='Z') i+='a'-'A';
|
// 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) {
|
// fall back to sorting by name
|
||||||
if (i>='A' && i<='Z') i+='a'-'A';
|
return a->nameLower<b->nameLower;
|
||||||
}
|
|
||||||
return aLower<bLower;
|
|
||||||
});
|
});
|
||||||
|
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() {
|
void FurnaceFilePicker::filterFiles() {
|
||||||
|
|
@ -271,26 +350,60 @@ bool FurnaceFilePicker::draw() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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("c0",ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize(" 999.99G").x);
|
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize(" .eeee").x);
|
||||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize(" 6969/69/69 04:20").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);
|
ImGui::TableSetupScrollFreeze(0,1);
|
||||||
|
|
||||||
|
// header (sort options)
|
||||||
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
|
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
|
||||||
ImGui::TableNextColumn();
|
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::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::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();
|
entryLock.lock();
|
||||||
int index=0;
|
int index=0;
|
||||||
listClipper.Begin(filteredEntries.size());
|
listClipper.Begin(filteredEntries.size());
|
||||||
while (listClipper.Step()) {
|
while (listClipper.Step()) {
|
||||||
for (int _i=listClipper.DisplayStart; _i<listClipper.DisplayEnd; _i++) {
|
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::TableNextRow();
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
|
|
@ -317,6 +430,9 @@ bool FurnaceFilePicker::draw() {
|
||||||
|
|
||||||
ImGui::TextUnformatted(i->name.c_str());
|
ImGui::TextUnformatted(i->name.c_str());
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::TextUnformatted(i->ext.c_str());
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
if (i->hasSize && i->type==FP_TYPE_NORMAL) {
|
if (i->hasSize && i->type==FP_TYPE_NORMAL) {
|
||||||
int sizeShift=0;
|
int sizeShift=0;
|
||||||
|
|
@ -406,7 +522,7 @@ bool FurnaceFilePicker::draw() {
|
||||||
return false;
|
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;
|
if (isOpen) return false;
|
||||||
|
|
||||||
readDirectory(path);
|
readDirectory(path);
|
||||||
|
|
@ -444,7 +560,9 @@ FurnaceFilePicker::FurnaceFilePicker():
|
||||||
stopReading(false),
|
stopReading(false),
|
||||||
isOpen(false),
|
isOpen(false),
|
||||||
isMobile(false),
|
isMobile(false),
|
||||||
|
sortInvert(false),
|
||||||
scheduledSort(0),
|
scheduledSort(0),
|
||||||
|
sortMode(FP_SORT_NAME),
|
||||||
curStatus(FP_STATUS_WAITING) {
|
curStatus(FP_STATUS_WAITING) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,16 @@ class FurnaceFilePicker {
|
||||||
FP_TYPE_PIPE,
|
FP_TYPE_PIPE,
|
||||||
FP_TYPE_SOCKET,
|
FP_TYPE_SOCKET,
|
||||||
};
|
};
|
||||||
|
enum SortModes {
|
||||||
|
FP_SORT_NAME=0,
|
||||||
|
FP_SORT_EXT,
|
||||||
|
FP_SORT_SIZE,
|
||||||
|
FP_SORT_DATE
|
||||||
|
};
|
||||||
struct FileEntry {
|
struct FileEntry {
|
||||||
String path;
|
String path;
|
||||||
String name;
|
String name;
|
||||||
|
String nameLower;
|
||||||
String ext;
|
String ext;
|
||||||
bool hasSize, hasTime, isDir, isSelected;
|
bool hasSize, hasTime, isDir, isSelected;
|
||||||
uint64_t size;
|
uint64_t size;
|
||||||
|
|
@ -62,8 +69,9 @@ class FurnaceFilePicker {
|
||||||
String homeDir;
|
String homeDir;
|
||||||
String entryName;
|
String entryName;
|
||||||
ImGuiListClipper listClipper;
|
ImGuiListClipper listClipper;
|
||||||
bool haveFiles, haveStat, stopReading, isOpen, isMobile;
|
bool haveFiles, haveStat, stopReading, isOpen, isMobile, sortInvert;
|
||||||
int scheduledSort;
|
int scheduledSort;
|
||||||
|
SortModes sortMode;
|
||||||
FilePickerStatus curStatus;
|
FilePickerStatus curStatus;
|
||||||
|
|
||||||
void sortFiles();
|
void sortFiles();
|
||||||
|
|
@ -80,7 +88,7 @@ class FurnaceFilePicker {
|
||||||
const std::vector<FileEntry*>& getSelected();
|
const std::vector<FileEntry*>& getSelected();
|
||||||
void setMobile(bool val);
|
void setMobile(bool val);
|
||||||
bool draw();
|
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 loadSettings(DivConfig& conf);
|
||||||
void saveSettings(DivConfig& conf);
|
void saveSettings(DivConfig& conf);
|
||||||
FurnaceFilePicker();
|
FurnaceFilePicker();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue