mirror of
https://github.com/wiidev/usbloadergx.git
synced 2024-11-22 11:19:17 +01:00
- fix tooltips in GameCarousel
- change gui_keyboard stuff - change filebrowser stuff
This commit is contained in:
parent
608dd91a21
commit
505e4cd09c
@ -193,6 +193,7 @@ GuiFileBrowser::~GuiFileBrowser()
|
||||
|
||||
void GuiFileBrowser::SetFocus(int f)
|
||||
{
|
||||
LOCK(this);
|
||||
focus = f;
|
||||
|
||||
for(int i=0; i<FILEBROWSERSIZE; i++)
|
||||
@ -204,11 +205,13 @@ void GuiFileBrowser::SetFocus(int f)
|
||||
|
||||
void GuiFileBrowser::DisableTriggerUpdate(bool set)
|
||||
{
|
||||
LOCK(this);
|
||||
triggerdisabled = set;
|
||||
}
|
||||
|
||||
void GuiFileBrowser::ResetState()
|
||||
{
|
||||
LOCK(this);
|
||||
state = STATE_DEFAULT;
|
||||
stateChan = -1;
|
||||
selectedItem = 0;
|
||||
@ -221,6 +224,7 @@ void GuiFileBrowser::ResetState()
|
||||
|
||||
void GuiFileBrowser::TriggerUpdate()
|
||||
{
|
||||
LOCK(this);
|
||||
listChanged = true;
|
||||
}
|
||||
|
||||
@ -229,6 +233,7 @@ void GuiFileBrowser::TriggerUpdate()
|
||||
*/
|
||||
void GuiFileBrowser::Draw()
|
||||
{
|
||||
LOCK(this);
|
||||
if(!this->IsVisible())
|
||||
return;
|
||||
|
||||
@ -249,6 +254,7 @@ void GuiFileBrowser::Draw()
|
||||
|
||||
void GuiFileBrowser::Update(GuiTrigger * t)
|
||||
{
|
||||
LOCK(this);
|
||||
if(state == STATE_DISABLED || !t || triggerdisabled)
|
||||
return;
|
||||
|
||||
@ -263,7 +269,7 @@ void GuiFileBrowser::Update(GuiTrigger * t)
|
||||
if(scrollbarBoxBtn->GetState() == STATE_HELD &&
|
||||
scrollbarBoxBtn->GetStateChan() == t->chan &&
|
||||
t->wpad.ir.valid &&
|
||||
browser.numEntries > FILEBROWSERSIZE
|
||||
browser->browserList.size() > FILEBROWSERSIZE
|
||||
)
|
||||
{
|
||||
scrollbarBoxBtn->SetPosition(20,-10);
|
||||
@ -274,15 +280,15 @@ void GuiFileBrowser::Update(GuiTrigger * t)
|
||||
else if(positionWiimote > scrollbarBoxBtn->GetMaxY())
|
||||
positionWiimote = scrollbarBoxBtn->GetMaxY();
|
||||
|
||||
browser.pageIndex = (positionWiimote * browser.numEntries)/136.0 - selectedItem;
|
||||
browser->pageIndex = (positionWiimote * browser->browserList.size())/136.0 - selectedItem;
|
||||
|
||||
if(browser.pageIndex <= 0)
|
||||
if(browser->pageIndex <= 0)
|
||||
{
|
||||
browser.pageIndex = 0;
|
||||
browser->pageIndex = 0;
|
||||
}
|
||||
else if(browser.pageIndex+FILEBROWSERSIZE >= browser.numEntries)
|
||||
else if(browser->pageIndex+FILEBROWSERSIZE >= (int)browser->browserList.size())
|
||||
{
|
||||
browser.pageIndex = browser.numEntries-FILEBROWSERSIZE;
|
||||
browser->pageIndex = browser->browserList.size()-FILEBROWSERSIZE;
|
||||
}
|
||||
listChanged = true;
|
||||
focus = false;
|
||||
@ -314,32 +320,32 @@ void GuiFileBrowser::Update(GuiTrigger * t)
|
||||
*/
|
||||
if(t->Right())
|
||||
{
|
||||
if(browser.pageIndex < browser.numEntries && browser.numEntries > FILEBROWSERSIZE)
|
||||
if(browser->pageIndex < (int)browser->browserList.size() && browser->browserList.size() > FILEBROWSERSIZE)
|
||||
{
|
||||
browser.pageIndex += FILEBROWSERSIZE;
|
||||
if(browser.pageIndex+FILEBROWSERSIZE >= browser.numEntries)
|
||||
browser.pageIndex = browser.numEntries-FILEBROWSERSIZE;
|
||||
browser->pageIndex += FILEBROWSERSIZE;
|
||||
if(browser->pageIndex+FILEBROWSERSIZE >= (int)browser->browserList.size())
|
||||
browser->pageIndex = browser->browserList.size()-FILEBROWSERSIZE;
|
||||
listChanged = true;
|
||||
}
|
||||
}
|
||||
else if(t->Left())
|
||||
{
|
||||
if(browser.pageIndex > 0)
|
||||
if(browser->pageIndex > 0)
|
||||
{
|
||||
browser.pageIndex -= FILEBROWSERSIZE;
|
||||
if(browser.pageIndex < 0)
|
||||
browser.pageIndex = 0;
|
||||
browser->pageIndex -= FILEBROWSERSIZE;
|
||||
if(browser->pageIndex < 0)
|
||||
browser->pageIndex = 0;
|
||||
listChanged = true;
|
||||
}
|
||||
}
|
||||
else if(t->Down())
|
||||
{
|
||||
if(browser.pageIndex + selectedItem + 1 < browser.numEntries)
|
||||
if(browser->pageIndex + selectedItem + 1 < (int)browser->browserList.size())
|
||||
{
|
||||
if(selectedItem == FILEBROWSERSIZE-1)
|
||||
{
|
||||
// move list down by 1
|
||||
browser.pageIndex++;
|
||||
browser->pageIndex++;
|
||||
listChanged = true;
|
||||
}
|
||||
else if(fileList[selectedItem+1]->IsVisible())
|
||||
@ -351,10 +357,10 @@ void GuiFileBrowser::Update(GuiTrigger * t)
|
||||
}
|
||||
else if(t->Up())
|
||||
{
|
||||
if(selectedItem == 0 && browser.pageIndex + selectedItem > 0)
|
||||
if(selectedItem == 0 && browser->pageIndex + selectedItem > 0)
|
||||
{
|
||||
// move list up by 1
|
||||
browser.pageIndex--;
|
||||
browser->pageIndex--;
|
||||
listChanged = true;
|
||||
}
|
||||
else if(selectedItem > 0)
|
||||
@ -370,17 +376,21 @@ void GuiFileBrowser::Update(GuiTrigger * t)
|
||||
{
|
||||
if(listChanged)
|
||||
{
|
||||
if(browser.pageIndex+i < browser.numEntries)
|
||||
bool haveselected = false;
|
||||
if(browser->pageIndex+i < (int)browser->browserList.size())
|
||||
{
|
||||
if(fileList[i]->GetState() == STATE_DISABLED)
|
||||
fileList[i]->SetState(STATE_DEFAULT);
|
||||
|
||||
if(fileList[i]->GetState() == STATE_SELECTED)
|
||||
haveselected = true;
|
||||
|
||||
fileList[i]->SetVisible(true);
|
||||
|
||||
fileListText[i]->SetText(browserList[browser.pageIndex+i].displayname);
|
||||
fileListTextOver[i]->SetText(browserList[browser.pageIndex+i].displayname);
|
||||
fileListText[i]->SetText(browser->browserList[browser->pageIndex+i].displayname);
|
||||
fileListTextOver[i]->SetText(browser->browserList[browser->pageIndex+i].displayname);
|
||||
|
||||
if(browserList[browser.pageIndex+i].isdir) // directory
|
||||
if(browser->browserList[browser->pageIndex+i].isdir) // directory
|
||||
{
|
||||
fileList[i]->SetIcon(fileListFolder[i]);
|
||||
fileListText[i]->SetPosition(30,0);
|
||||
@ -427,6 +437,9 @@ void GuiFileBrowser::Update(GuiTrigger * t)
|
||||
fileList[i]->SetVisible(false);
|
||||
fileList[i]->SetState(STATE_DISABLED);
|
||||
}
|
||||
if(!haveselected && browser->pageIndex < (int)browser->browserList.size())
|
||||
fileList[i]->SetState(STATE_SELECTED, t->chan);
|
||||
|
||||
}
|
||||
|
||||
if(i != selectedItem && fileList[i]->GetState() == STATE_SELECTED)
|
||||
@ -445,7 +458,6 @@ void GuiFileBrowser::Update(GuiTrigger * t)
|
||||
if(fileList[i]->GetState() == STATE_SELECTED)
|
||||
{
|
||||
selectedItem = i;
|
||||
browser.selIndex = browser.pageIndex + i;
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,11 +468,11 @@ void GuiFileBrowser::Update(GuiTrigger * t)
|
||||
}
|
||||
else
|
||||
{
|
||||
position = 136*(browser.pageIndex + FILEBROWSERSIZE/2.0) / (browser.numEntries*1.0);
|
||||
position = 136*(browser->pageIndex + FILEBROWSERSIZE/2.0) / (browser->browserList.size()*1.0);
|
||||
|
||||
if(browser.pageIndex/(FILEBROWSERSIZE/2.0) < 1)
|
||||
if(browser->pageIndex/(FILEBROWSERSIZE/2.0) < 1)
|
||||
position = -10;
|
||||
else if((browser.pageIndex+FILEBROWSERSIZE)/(FILEBROWSERSIZE*1.0) >= (browser.numEntries)/(FILEBROWSERSIZE*1.0))
|
||||
else if((browser->pageIndex+FILEBROWSERSIZE)/(FILEBROWSERSIZE*1.0) >= (browser->browserList.size())/(FILEBROWSERSIZE*1.0))
|
||||
position = 156;
|
||||
}
|
||||
|
||||
|
@ -344,10 +344,7 @@ void GuiGameCarousel::Update(GuiTrigger * t)
|
||||
else
|
||||
gamename->SetText((char*)NULL);
|
||||
if(selectedItem_old>=0)
|
||||
{
|
||||
game[selectedItem_old]->SetEffect(EFFECT_SCALE, -1, 100);
|
||||
game[selectedItem_old]->RemoveToolTip();
|
||||
}
|
||||
}
|
||||
// navigation
|
||||
if(focus && gameCnt>6)
|
||||
|
@ -32,10 +32,10 @@ GuiKeyboard::GuiKeyboard(char * t, u32 max, int min, int lang)
|
||||
focus = 0; // allow focus
|
||||
alignmentHor = ALIGN_CENTRE;
|
||||
alignmentVert = ALIGN_MIDDLE;
|
||||
strncpy(kbtextstr, t, max); // do not change from strncpy to strlcpy, it needs to be padded with 0
|
||||
kbtextstr[max] = 0;
|
||||
kbtextmaxlen = max;
|
||||
|
||||
kbtextmaxlen = max>sizeof(kbtextstr) ? sizeof(kbtextstr) : max; // limit max up to sizeof(kbtextstr)
|
||||
// strlcpy(kbtextstr, t, kbtextmaxlen);
|
||||
strncpy(kbtextstr, t, kbtextmaxlen); // strncpy is needed to fill the rest with \0
|
||||
kbtextstr[sizeof(kbtextstr)-1] = 0; // terminate with \0
|
||||
//QWERTY//
|
||||
if (mode == 0){
|
||||
Key thekeys[4][11] = {
|
||||
@ -471,6 +471,7 @@ GuiKeyboard::GuiKeyboard(char * t, u32 max, int min, int lang)
|
||||
//keySpace->SetEffectGrow();
|
||||
this->Append(keySpace);
|
||||
|
||||
char txt[2] = { 0, 0 };
|
||||
for(int i=0; i<4; i++)
|
||||
{
|
||||
for(int j=0; j<11; j++)
|
||||
@ -479,7 +480,8 @@ GuiKeyboard::GuiKeyboard(char * t, u32 max, int min, int lang)
|
||||
{
|
||||
keyImg[i][j] = new GuiImage(key);
|
||||
keyImgOver[i][j] = new GuiImage(keyOver);
|
||||
keyTxt[i][j] = new GuiText(NULL, 20, (GXColor){0, 0, 0, 0xff});
|
||||
txt[0] = keys[i][j].ch;
|
||||
keyTxt[i][j] = new GuiText(txt, 20, (GXColor){0, 0, 0, 0xff});
|
||||
keyTxt[i][j]->SetAlignment(ALIGN_CENTRE, ALIGN_BOTTOM);
|
||||
keyTxt[i][j]->SetPosition(0, -10);
|
||||
keyBtn[i][j] = new GuiButton(keyImg[i][j], keyImgOver[i][j], 0, 3, (j*42+21*i+40)+eurocheck, i*42+120, trigA, keySoundOver, keySoundClick,1);
|
||||
@ -563,11 +565,11 @@ void GuiKeyboard::Update(GuiTrigger * t)
|
||||
catch (const std::exception& e) { }
|
||||
}
|
||||
|
||||
bool update = false;
|
||||
bool changedShiftKey = false;
|
||||
|
||||
if(keySpace->GetState() == STATE_CLICKED)
|
||||
{
|
||||
if(strlen(kbtextstr) < kbtextmaxlen)
|
||||
if(strlen(kbtextstr) < kbtextmaxlen-1) // -1 --> kbtextmaxlen means with terminating '\0'
|
||||
{
|
||||
kbtextstr[strlen(kbtextstr)] = ' ';
|
||||
kbText->SetText(kbtextstr);
|
||||
@ -577,20 +579,22 @@ void GuiKeyboard::Update(GuiTrigger * t)
|
||||
else if(keyBack->GetState() == STATE_CLICKED)
|
||||
{
|
||||
if (strlen(kbtextstr) >(m)){
|
||||
kbtextstr[strlen(kbtextstr)-1] = 0;
|
||||
kbText->SetText(kbtextstr);}
|
||||
kbtextstr[strlen(kbtextstr)-1] = 0;
|
||||
kbText->SetText(kbtextstr);
|
||||
}
|
||||
keyBack->SetState(STATE_SELECTED, t->chan);
|
||||
}
|
||||
else if(keyClear->GetState() == STATE_CLICKED)
|
||||
{ clearMore:
|
||||
if (strlen(kbtextstr) >(m)){
|
||||
kbtextstr[strlen(kbtextstr)-1] = 0;
|
||||
kbText->SetText(kbtextstr);
|
||||
goto clearMore;}
|
||||
{
|
||||
while (strlen(kbtextstr) >(m)){
|
||||
kbtextstr[strlen(kbtextstr)-1] = 0;
|
||||
kbText->SetText(kbtextstr);
|
||||
}
|
||||
keyClear->SetState(STATE_SELECTED, t->chan);
|
||||
}
|
||||
else if(keyShift->GetState() == STATE_CLICKED)
|
||||
{
|
||||
changedShiftKey =true;
|
||||
shift ^= 1;
|
||||
if(alt) alt ^= 1;
|
||||
if(alt2) alt2 ^= 1;
|
||||
@ -598,6 +602,7 @@ void GuiKeyboard::Update(GuiTrigger * t)
|
||||
}
|
||||
else if(keyAlt->GetState() == STATE_CLICKED)
|
||||
{
|
||||
changedShiftKey =true;
|
||||
alt ^= 1;
|
||||
if(shift) shift ^= 1;
|
||||
if(alt2) alt2 ^= 1;
|
||||
@ -605,6 +610,7 @@ void GuiKeyboard::Update(GuiTrigger * t)
|
||||
}
|
||||
else if(keyAlt2->GetState() == STATE_CLICKED)
|
||||
{
|
||||
changedShiftKey =true;
|
||||
alt2 ^= 1;
|
||||
if(shift) shift ^= 1;
|
||||
if(alt) alt ^= 1;
|
||||
@ -612,68 +618,59 @@ void GuiKeyboard::Update(GuiTrigger * t)
|
||||
}
|
||||
else if(keyCaps->GetState() == STATE_CLICKED)
|
||||
{
|
||||
changedShiftKey =true;
|
||||
caps ^= 1;
|
||||
keyCaps->SetState(STATE_SELECTED, t->chan);
|
||||
}
|
||||
|
||||
|
||||
bool update = false;
|
||||
|
||||
char txt[2] = { 0, 0 };
|
||||
|
||||
startloop:
|
||||
|
||||
for(int i=0; i<4; i++)
|
||||
do
|
||||
{
|
||||
for(int j=0; j<11; j++)
|
||||
update = false;
|
||||
for(int i=0; i<4; i++)
|
||||
{
|
||||
if(keys[i][j].ch != '\0')
|
||||
for(int j=0; j<11; j++)
|
||||
{
|
||||
if(shift || caps)
|
||||
txt[0] = keys[i][j].chShift;
|
||||
else if(alt)
|
||||
txt[0] = keys[i][j].chalt;
|
||||
else if(alt2)
|
||||
txt[0] = keys[i][j].chalt2;
|
||||
else
|
||||
txt[0] = keys[i][j].ch;
|
||||
|
||||
keyTxt[i][j]->SetText(txt);
|
||||
|
||||
if(keyBtn[i][j]->GetState() == STATE_CLICKED)
|
||||
if(keys[i][j].ch != '\0')
|
||||
{
|
||||
if(strlen(kbtextstr) < kbtextmaxlen)
|
||||
if(shift || caps)
|
||||
txt[0] = keys[i][j].chShift;
|
||||
else if(alt)
|
||||
txt[0] = keys[i][j].chalt;
|
||||
else if(alt2)
|
||||
txt[0] = keys[i][j].chalt2;
|
||||
else
|
||||
txt[0] = keys[i][j].ch;
|
||||
|
||||
if(changedShiftKey) // change text only if needed
|
||||
keyTxt[i][j]->SetText(txt);
|
||||
|
||||
if(keyBtn[i][j]->GetState() == STATE_CLICKED)
|
||||
{
|
||||
if(shift || caps)
|
||||
if(strlen(kbtextstr) < kbtextmaxlen-1) // -1 --> kbtextmaxlen means with term. '\0'
|
||||
{
|
||||
kbtextstr[strlen(kbtextstr)] = keys[i][j].chShift;
|
||||
kbtextstr[strlen(kbtextstr)] = txt[0];
|
||||
kbText->SetText(kbtextstr);
|
||||
}
|
||||
else if(alt)
|
||||
{
|
||||
kbtextstr[strlen(kbtextstr)] = keys[i][j].chalt;
|
||||
}
|
||||
else if(alt2)
|
||||
{
|
||||
kbtextstr[strlen(kbtextstr)] = keys[i][j].chalt2;
|
||||
}
|
||||
else
|
||||
{
|
||||
kbtextstr[strlen(kbtextstr)] = keys[i][j].ch;
|
||||
}
|
||||
}
|
||||
kbText->SetText(kbtextstr);
|
||||
keyBtn[i][j]->SetState(STATE_SELECTED, t->chan);
|
||||
keyBtn[i][j]->SetState(STATE_SELECTED, t->chan);
|
||||
|
||||
|
||||
if(shift || alt || alt2)
|
||||
{
|
||||
if(shift) shift ^= 1;
|
||||
if(alt) alt ^= 1;
|
||||
if(alt2) alt2 ^= 1;
|
||||
update = true;
|
||||
goto startloop;
|
||||
if(shift || alt || alt2)
|
||||
{
|
||||
if(shift) shift ^= 1;
|
||||
if(alt) alt ^= 1;
|
||||
if(alt2) alt2 ^= 1;
|
||||
update = true;
|
||||
changedShiftKey = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(update);
|
||||
|
||||
kbText->SetPosition(0, 53);
|
||||
|
||||
|
@ -31,28 +31,37 @@ bool findfile(const char * filename, const char * path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool subfoldercreate(char * fullpath) {
|
||||
//check forsubfolders
|
||||
char dircheck[300];
|
||||
char dirnoslash[300];
|
||||
char * pch = NULL;
|
||||
u32 cnt = 0;
|
||||
struct stat st;
|
||||
bool subfoldercreate(const char * fullpath) {
|
||||
//check forsubfolders
|
||||
char dir[300];
|
||||
char * pch = NULL;
|
||||
u32 len;
|
||||
struct stat st;
|
||||
|
||||
snprintf(dirnoslash, strlen(fullpath), "%s", fullpath);
|
||||
|
||||
if (stat(fullpath, &st) != 0) {
|
||||
pch = strrchr(dirnoslash, '/');
|
||||
cnt = pch-dirnoslash;
|
||||
snprintf(dircheck, cnt+2, "%s", dirnoslash);
|
||||
subfoldercreate(dircheck);
|
||||
};
|
||||
|
||||
if (mkdir(dirnoslash, 0777) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
strlcpy(dir, fullpath, sizeof(dir));
|
||||
len = strlen(dir);
|
||||
if(len && len< sizeof(dir)-2 && dir[len-1] != '/');
|
||||
{
|
||||
dir[len++] = '/';
|
||||
dir[len] = '\0';
|
||||
}
|
||||
if (stat(dir, &st) != 0) // fullpath not exist?
|
||||
{
|
||||
while(len && dir[len-1] == '/')
|
||||
dir[--len] = '\0'; // remove all trailing /
|
||||
pch = strrchr(dir, '/');
|
||||
if(pch == NULL) return false;
|
||||
*pch = '\0';
|
||||
if(subfoldercreate(dir))
|
||||
{
|
||||
*pch = '/';
|
||||
if (mkdir(dir, 0777) == -1)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
char * GetFileName(int i) {
|
||||
|
@ -8,7 +8,7 @@ extern "C" {
|
||||
bool findfile(const char * filename, const char * path);
|
||||
char * GetFileName(int i);
|
||||
int GetAllDirFiles(char * filespath);
|
||||
bool subfoldercreate(char * fullpath);
|
||||
bool subfoldercreate(const char * fullpath);
|
||||
bool checkfile(char * path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -15,9 +15,10 @@
|
||||
#include <string.h>
|
||||
#include <wiiuse/wpad.h>
|
||||
#include <sys/dir.h>
|
||||
#include <sys/iosupport.h>
|
||||
#include <malloc.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "filebrowser.h"
|
||||
#include "menu.h"
|
||||
|
||||
#include "listfiles.h"
|
||||
@ -25,6 +26,7 @@
|
||||
#include "PromptWindows.h"
|
||||
#include "libwiigui/gui.h"
|
||||
#include "sys.h"
|
||||
#include "filebrowser.h"
|
||||
|
||||
/*** Extern variables ***/
|
||||
extern GuiWindow * mainWindow;
|
||||
@ -35,421 +37,498 @@ extern u8 reset;
|
||||
extern void ResumeGui();
|
||||
extern void HaltGui();
|
||||
|
||||
BROWSERINFO browser;
|
||||
BROWSERENTRY * browserList = NULL; // list of files/folders in browser
|
||||
static int curDevice = -1;
|
||||
static std::vector<BROWSERINFO> browsers;
|
||||
BROWSERINFO *browser = NULL;
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* FileFilterCallbacks
|
||||
* return: 1-visible 0-hidden
|
||||
***************************************************************************/
|
||||
int noDIRS(BROWSERENTRY *Entry, void* Args)
|
||||
{
|
||||
return !Entry->isdir;
|
||||
}
|
||||
int noFILES(BROWSERENTRY *Entry, void* Args)
|
||||
{
|
||||
return Entry->isdir;
|
||||
}
|
||||
int noEXT(BROWSERENTRY *Entry, void* Args)
|
||||
{
|
||||
if(!Entry->isdir)
|
||||
{
|
||||
char *cptr = strrchr(Entry->displayname, '.');
|
||||
if(cptr && cptr!= Entry->displayname) *cptr = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ResetBrowser(BROWSERINFO *browser);
|
||||
/****************************************************************************
|
||||
* InitBrowsers()
|
||||
* Clears the file browser memory, and allocates one initial entry
|
||||
***************************************************************************/
|
||||
int InitBrowsers() {
|
||||
curDevice = -1;
|
||||
browsers.clear();
|
||||
browser = NULL;
|
||||
char rootdir[ROOTDIRLEN];
|
||||
for(int i=3; i<STD_MAX; i++)
|
||||
{
|
||||
if(strcmp(devoptab_list[i]->name, "stdnull"))
|
||||
{
|
||||
snprintf(rootdir, sizeof(rootdir) , "%s:/", devoptab_list[i]->name);
|
||||
if(DIR_ITER *dir = diropen(rootdir))
|
||||
{
|
||||
dirclose(dir);
|
||||
BROWSERINFO browser;
|
||||
browser.dir[0] = '\0';
|
||||
strcpy(browser.rootdir, rootdir);
|
||||
ResetBrowser(&browser);
|
||||
browsers.push_back(browser);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!browsers.size()) return -1;
|
||||
curDevice = 0;
|
||||
browser = &browsers[curDevice];
|
||||
return 0;
|
||||
}
|
||||
/****************************************************************************
|
||||
* ResetBrowser()
|
||||
* Clears the file browser memory, and allocates one initial entry
|
||||
***************************************************************************/
|
||||
void ResetBrowser() {
|
||||
browser.numEntries = 0;
|
||||
browser.selIndex = 0;
|
||||
browser.pageIndex = 0;
|
||||
|
||||
// Clear any existing values
|
||||
if (browserList != NULL) {
|
||||
free(browserList);
|
||||
browserList = NULL;
|
||||
}
|
||||
// set aside space for 1 entry
|
||||
browserList = (BROWSERENTRY *)malloc(sizeof(BROWSERENTRY));
|
||||
memset(browserList, 0, sizeof(BROWSERENTRY));
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* UpdateDirName()
|
||||
* Update curent directory name for file browser
|
||||
***************************************************************************/
|
||||
int UpdateDirName() {
|
||||
int size=0;
|
||||
char * test;
|
||||
char temp[1024];
|
||||
|
||||
/* current directory doesn't change */
|
||||
if (strcmp(browserList[browser.selIndex].filename,".") == 0) {
|
||||
return 0;
|
||||
}
|
||||
/* go up to parent directory */
|
||||
else if (strcmp(browserList[browser.selIndex].filename,"..") == 0) {
|
||||
/* determine last subdirectory namelength */
|
||||
sprintf(temp,"%s",browser.dir);
|
||||
test = strtok(temp,"/");
|
||||
while (test != NULL) {
|
||||
size = strlen(test);
|
||||
test = strtok(NULL,"/");
|
||||
}
|
||||
|
||||
/* remove last subdirectory name */
|
||||
size = strlen(browser.dir) - size - 1;
|
||||
browser.dir[size] = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
/* Open a directory */
|
||||
else {
|
||||
/* test new directory namelength */
|
||||
if ((strlen(browser.dir)+1+strlen(browserList[browser.selIndex].filename)) < MAXPATHLEN) {
|
||||
/* update current directory name */
|
||||
sprintf(browser.dir, "%s/%s",browser.dir, browserList[browser.selIndex].filename);
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
void ResetBrowser(BROWSERINFO *browser) {
|
||||
browser->pageIndex = 0;
|
||||
browser->browserList.clear();
|
||||
/*
|
||||
// Clear any existing values
|
||||
if (browser->browserList != NULL) {
|
||||
free(browser->browserList);
|
||||
browser->browserList = NULL;
|
||||
}
|
||||
// set aside space for 1 entry
|
||||
browser->browserList = (BROWSERENTRY *)malloc(sizeof(BROWSERENTRY));
|
||||
if(browser->browserList)
|
||||
memset(browser->browserList, 0, sizeof(BROWSERENTRY));
|
||||
*/
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* FileSortCallback
|
||||
*
|
||||
* Quick sort callback to sort file entries with the following order:
|
||||
* sort callback to sort file entries with the following order:
|
||||
* .
|
||||
* ..
|
||||
* <dirs>
|
||||
* <files>
|
||||
***************************************************************************/
|
||||
int FileSortCallback(const void *f1, const void *f2) {
|
||||
/* Special case for implicit directories */
|
||||
if (((BROWSERENTRY *)f1)->filename[0] == '.' || ((BROWSERENTRY *)f2)->filename[0] == '.') {
|
||||
if (strcmp(((BROWSERENTRY *)f1)->filename, ".") == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (strcmp(((BROWSERENTRY *)f2)->filename, ".") == 0) {
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(((BROWSERENTRY *)f1)->filename, "..") == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (strcmp(((BROWSERENTRY *)f2)->filename, "..") == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
//int FileSortCallback(const void *f1, const void *f2) {
|
||||
bool operator< (const BROWSERENTRY &f1, const BROWSERENTRY &f2) {
|
||||
/* Special case for implicit directories */
|
||||
if (f1.filename[0] == '.' || f2.filename[0] == '.') {
|
||||
if (strcmp(f1.filename, ".") == 0) {
|
||||
return true;
|
||||
}
|
||||
if (strcmp(f2.filename, ".") == 0) {
|
||||
return false;
|
||||
}
|
||||
if (strcmp(f1.filename, "..") == 0) {
|
||||
return true;
|
||||
}
|
||||
if (strcmp(f2.filename, "..") == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* If one is a file and one is a directory the directory is first. */
|
||||
if (((BROWSERENTRY *)f1)->isdir && !(((BROWSERENTRY *)f2)->isdir)) return -1;
|
||||
if (!(((BROWSERENTRY *)f1)->isdir) && ((BROWSERENTRY *)f2)->isdir) return 1;
|
||||
/* If one is a file and one is a directory the directory is first. */
|
||||
if (f1.isdir && !(f2.isdir)) return true;
|
||||
if (!(f1.isdir) && f2.isdir) return false;
|
||||
|
||||
return stricmp(((BROWSERENTRY *)f1)->filename, ((BROWSERENTRY *)f2)->filename);
|
||||
return stricmp(f1.filename, f2.filename)<0;
|
||||
}
|
||||
|
||||
int ParseFilter(FILTERCASCADE *Filter, BROWSERENTRY* Entry)
|
||||
{
|
||||
while(Filter)
|
||||
{
|
||||
if(Filter->filter && Filter->filter(Entry, Filter->filter_args) == 0)
|
||||
return 0;
|
||||
Filter = Filter->next;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/***************************************************************************
|
||||
* Browse subdirectories
|
||||
**************************************************************************/
|
||||
int
|
||||
ParseDirectory() {
|
||||
DIR_ITER *dir = NULL;
|
||||
char fulldir[MAXPATHLEN];
|
||||
char filename[MAXPATHLEN];
|
||||
struct stat filestat;
|
||||
int ParseDirectory(const char* Path, int Flags, FILTERCASCADE *Filter) {
|
||||
DIR_ITER *dir = NULL;
|
||||
char fulldir[MAXPATHLEN];
|
||||
char filename[MAXPATHLEN];
|
||||
struct stat filestat;
|
||||
unsigned int i;
|
||||
|
||||
// reset browser
|
||||
ResetBrowser();
|
||||
if(curDevice == -1)
|
||||
if(InitBrowsers()) return -1; // InitBrowser fails
|
||||
|
||||
// open the directory
|
||||
sprintf(fulldir, "%s%s", browser.rootdir, browser.dir); // add currentDevice to path
|
||||
dir = diropen(fulldir);
|
||||
if(Path) // note in this codeblock use filename temporary
|
||||
{
|
||||
strlcpy(fulldir, Path, sizeof(fulldir));
|
||||
if(*fulldir && fulldir[strlen(fulldir)-1] != '/') // a file
|
||||
{
|
||||
char * chrp = strrchr(fulldir, '/');
|
||||
if(chrp) chrp[1] = 0;
|
||||
}
|
||||
if(strchr(fulldir, ':') == NULL) // Path has no device device
|
||||
{
|
||||
getcwd(filename, sizeof(filename)); // save the current working dir
|
||||
if(*fulldir==0) // if path is empty
|
||||
strlcpy(fulldir, filename, sizeof(fulldir)); // we use the current working dir
|
||||
else
|
||||
{ // path is not empty
|
||||
if(chdir(fulldir)); // sets the path to concatenate and validate
|
||||
{
|
||||
if(Flags & (FB_TRYROOTDIR | FB_TRYSTDDEV))
|
||||
if(chdir("/") && !(Flags & FB_TRYSTDDEV))// try to set root if is needed
|
||||
return -1;
|
||||
}
|
||||
if(getcwd(fulldir, sizeof(fulldir))) return -1; // gets the concatenated current working dir
|
||||
chdir(filename); // restore the saved cwd
|
||||
}
|
||||
}
|
||||
for(i=0; i<browsers.size(); i++) // searchs the browser who match the path
|
||||
{
|
||||
if(strnicmp(fulldir, browsers[i].rootdir, strlen(browsers[i].rootdir)-1 /*means without trailing '/'*/) == 0)
|
||||
{
|
||||
browser = &browsers[curDevice];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i != browsers.size()) // found browser
|
||||
{
|
||||
curDevice = i;
|
||||
browser = &browsers[curDevice];
|
||||
strcpy(browser->dir, &fulldir[strlen(browser->rootdir)]);
|
||||
}
|
||||
else if(Flags & FB_TRYSTDDEV)
|
||||
{
|
||||
curDevice = 0;
|
||||
browser = &browsers[curDevice]; // when no browser was found and
|
||||
browser->dir[0] = 0; // we alowed try StdDevice and try RootDir
|
||||
strlcpy(fulldir, browser->rootdir, sizeof(fulldir)); // set the first browser with root-dir
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
snprintf(fulldir, sizeof(fulldir), "%s%s", browser->rootdir, browser->dir);
|
||||
|
||||
// if we can't open the dir, try opening the root dir
|
||||
if (dir == NULL) {
|
||||
sprintf(browser.dir,"/");
|
||||
dir = diropen(browser.rootdir);
|
||||
if (dir == NULL) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// reset browser
|
||||
ResetBrowser(browser);
|
||||
|
||||
// index files/folders
|
||||
int entryNum = 0;
|
||||
// open the directory
|
||||
if((dir = diropen(fulldir)) == NULL)
|
||||
{
|
||||
if(Flags & FB_TRYROOTDIR)
|
||||
{
|
||||
browser->dir[0] = 0;
|
||||
if((dir = diropen(browser->rootdir)) == NULL)
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (dirnext(dir,filename,&filestat) == 0) {
|
||||
if (strcmp(filename,".") != 0) {
|
||||
BROWSERENTRY * newBrowserList = (BROWSERENTRY *)realloc(browserList, (entryNum+1) * sizeof(BROWSERENTRY));
|
||||
while (dirnext(dir,filename,&filestat) == 0)
|
||||
{
|
||||
if (strcmp(filename,".") != 0)
|
||||
{
|
||||
BROWSERENTRY newEntry;
|
||||
memset(&newEntry, 0, sizeof(BROWSERENTRY)); // clear the new entry
|
||||
strlcpy(newEntry.filename, filename, sizeof(newEntry.filename));
|
||||
strlcpy(newEntry.displayname, filename, sizeof(newEntry.displayname));
|
||||
newEntry.length = filestat.st_size;
|
||||
newEntry.isdir = (filestat.st_mode & S_IFDIR) == 0 ? 0 : 1; // flag this as a dir
|
||||
if(ParseFilter(Filter, &newEntry))
|
||||
browser->browserList.push_back(newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
if (!newBrowserList) { // failed to allocate required memory
|
||||
ResetBrowser();
|
||||
entryNum = -1;
|
||||
break;
|
||||
} else {
|
||||
browserList = newBrowserList;
|
||||
}
|
||||
memset(&(browserList[entryNum]), 0, sizeof(BROWSERENTRY)); // clear the new entry
|
||||
// close directory
|
||||
dirclose(dir);
|
||||
|
||||
strncpy(browserList[entryNum].filename, filename, MAXJOLIET);
|
||||
|
||||
if (strcmp(filename,"..") == 0) {
|
||||
sprintf(browserList[entryNum].displayname, "..");
|
||||
} else {
|
||||
strcpy(browserList[entryNum].displayname, filename); // crop name for display
|
||||
}
|
||||
|
||||
browserList[entryNum].length = filestat.st_size;
|
||||
browserList[entryNum].isdir = (filestat.st_mode & _IFDIR) == 0 ? 0 : 1; // flag this as a dir
|
||||
|
||||
entryNum++;
|
||||
}
|
||||
}
|
||||
|
||||
// close directory
|
||||
dirclose(dir);
|
||||
|
||||
// Sort the file list
|
||||
qsort(browserList, entryNum, sizeof(BROWSERENTRY), FileSortCallback);
|
||||
|
||||
browser.numEntries = entryNum;
|
||||
return entryNum;
|
||||
// Sort the file list
|
||||
std::sort(browser->browserList.begin(), browser->browserList.end());
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* BrowserChangeFolder
|
||||
*
|
||||
* Update current directory and set new entry list if directory has changed
|
||||
***************************************************************************/
|
||||
int BrowserChangeFolder() {
|
||||
if (!UpdateDirName())
|
||||
return -1;
|
||||
|
||||
ParseDirectory();
|
||||
|
||||
return browser.numEntries;
|
||||
int ParseDirectory(int Device, int Flags, FILTERCASCADE *Filter)
|
||||
{
|
||||
if(Device >=0 && Device < (int)browsers.size())
|
||||
{
|
||||
int old_curDevice = curDevice;
|
||||
curDevice = Device;
|
||||
browser = &browsers[curDevice];
|
||||
if(ParseDirectory((char*)NULL, Flags, Filter) == 0) return 0;
|
||||
curDevice = old_curDevice;
|
||||
browser = &browsers[old_curDevice];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* BrowseDevice
|
||||
* Displays a list of files on the selected device
|
||||
***************************************************************************/
|
||||
int BrowseDevice(int device) {
|
||||
sprintf(browser.dir, "/");
|
||||
switch (device) {
|
||||
case SD:
|
||||
sprintf(browser.rootdir, "SD:");
|
||||
break;
|
||||
case USB:
|
||||
sprintf(browser.rootdir, "USB:");
|
||||
break;
|
||||
}
|
||||
ParseDirectory(); // Parse root directory
|
||||
return browser.numEntries;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* MenuBrowseDevice
|
||||
* Displays a list of files on the selected path
|
||||
***************************************************************************/
|
||||
|
||||
int BrowseDevice(char * var, int force) {
|
||||
int BrowseDevice(char * Path, int Path_size, int Flags, FILTERCASCADE *Filter/*=NULL*/)
|
||||
{
|
||||
int result=-1;
|
||||
int i;
|
||||
|
||||
int result=-1;
|
||||
int i;
|
||||
char currentdir[90];
|
||||
int curDivice = -1;
|
||||
int forced =force;
|
||||
if(InitBrowsers() || ParseDirectory(Path, Flags, Filter))
|
||||
{
|
||||
WindowPrompt(tr("Error"),0,tr("Ok"));
|
||||
return -1;
|
||||
}
|
||||
int menu = MENU_NONE;
|
||||
|
||||
if (forced>-1) {
|
||||
if (BrowseDevice(forced) > 0) {
|
||||
curDivice = forced;
|
||||
goto main;
|
||||
}
|
||||
}
|
||||
/*
|
||||
GuiText titleTxt("Browse Files", 28, (GXColor){0, 0, 0, 230});
|
||||
titleTxt.SetAlignment(ALIGN_LEFT, ALIGN_TOP);
|
||||
titleTxt.SetPosition(70,20);
|
||||
*/
|
||||
GuiTrigger trigA;
|
||||
trigA.SetSimpleTrigger(-1, WPAD_BUTTON_A | WPAD_CLASSIC_BUTTON_A, PAD_BUTTON_A);
|
||||
GuiTrigger trigB;
|
||||
trigB.SetButtonOnlyTrigger(-1, WPAD_BUTTON_B | WPAD_CLASSIC_BUTTON_B, PAD_BUTTON_B);
|
||||
|
||||
else if ((!strcasecmp(bootDevice, "USB:"))&&(BrowseDevice(USB) > 0)) {
|
||||
curDivice = USB;
|
||||
goto main;
|
||||
} else if ((!strcasecmp(bootDevice, "SD:"))&&(BrowseDevice(SD) > 0)) {
|
||||
curDivice = SD;
|
||||
goto main;
|
||||
} else {
|
||||
WindowPrompt(tr("Error"),0,tr("OK"));
|
||||
return -1;
|
||||
}
|
||||
GuiSound btnSoundOver(button_over_pcm, button_over_pcm_size, SOUND_PCM, Settings.sfxvolume);
|
||||
GuiSound btnClick(button_click2_pcm, button_click2_pcm_size, SOUND_PCM, Settings.sfxvolume);
|
||||
|
||||
main:
|
||||
int menu = MENU_NONE;
|
||||
|
||||
/*
|
||||
GuiText titleTxt("Browse Files", 28, (GXColor){0, 0, 0, 230});
|
||||
titleTxt.SetAlignment(ALIGN_LEFT, ALIGN_TOP);
|
||||
titleTxt.SetPosition(70,20);
|
||||
*/
|
||||
GuiTrigger trigA;
|
||||
trigA.SetSimpleTrigger(-1, WPAD_BUTTON_A | WPAD_CLASSIC_BUTTON_A, PAD_BUTTON_A);
|
||||
GuiTrigger trigB;
|
||||
trigB.SetButtonOnlyTrigger(-1, WPAD_BUTTON_B | WPAD_CLASSIC_BUTTON_B, PAD_BUTTON_B);
|
||||
|
||||
GuiSound btnSoundOver(button_over_pcm, button_over_pcm_size, SOUND_PCM, Settings.sfxvolume);
|
||||
GuiSound btnClick(button_click2_pcm, button_click2_pcm_size, SOUND_PCM, Settings.sfxvolume);
|
||||
|
||||
GuiImageData folderImgData(icon_folder_png);
|
||||
GuiImage folderImg(&folderImgData);
|
||||
GuiButton folderBtn(folderImg.GetWidth(), folderImg.GetHeight());
|
||||
folderBtn.SetAlignment(ALIGN_CENTRE, ALIGN_MIDDLE);
|
||||
folderBtn.SetPosition(-210, -145);
|
||||
folderBtn.SetImage(&folderImg);
|
||||
folderBtn.SetTrigger(&trigA);
|
||||
folderBtn.SetEffectGrow();
|
||||
GuiImageData folderImgData(icon_folder_png);
|
||||
GuiImage folderImg(&folderImgData);
|
||||
GuiButton folderBtn(folderImg.GetWidth(), folderImg.GetHeight());
|
||||
folderBtn.SetAlignment(ALIGN_CENTRE, ALIGN_MIDDLE);
|
||||
folderBtn.SetPosition(-210, -145);
|
||||
folderBtn.SetImage(&folderImg);
|
||||
folderBtn.SetTrigger(&trigA);
|
||||
folderBtn.SetEffectGrow();
|
||||
|
||||
char imgPath[100];
|
||||
snprintf(imgPath, sizeof(imgPath), "%sbutton_dialogue_box.png", CFG.theme_path);
|
||||
GuiImageData btnOutline(imgPath, button_dialogue_box_png);
|
||||
GuiText ExitBtnTxt(tr("Cancel"), 24, (GXColor) {0, 0, 0, 255});
|
||||
GuiImage ExitBtnImg(&btnOutline);
|
||||
if (Settings.wsprompt == yes) {
|
||||
ExitBtnTxt.SetWidescreen(CFG.widescreen);
|
||||
ExitBtnImg.SetWidescreen(CFG.widescreen);
|
||||
}
|
||||
GuiButton ExitBtn(btnOutline.GetWidth(), btnOutline.GetHeight());
|
||||
ExitBtn.SetAlignment(ALIGN_RIGHT, ALIGN_BOTTOM);
|
||||
ExitBtn.SetPosition(-40, -35);
|
||||
ExitBtn.SetLabel(&ExitBtnTxt);
|
||||
ExitBtn.SetImage(&ExitBtnImg);
|
||||
ExitBtn.SetTrigger(&trigA);
|
||||
ExitBtn.SetTrigger(&trigB);
|
||||
ExitBtn.SetEffectGrow();
|
||||
snprintf(imgPath, sizeof(imgPath), "%sbutton_dialogue_box.png", CFG.theme_path);
|
||||
GuiImageData btnOutline(imgPath, button_dialogue_box_png);
|
||||
GuiText ExitBtnTxt(tr("Cancel"), 24, (GXColor) {0, 0, 0, 255});
|
||||
GuiImage ExitBtnImg(&btnOutline);
|
||||
if (Settings.wsprompt == yes) {
|
||||
ExitBtnTxt.SetWidescreen(CFG.widescreen);
|
||||
ExitBtnImg.SetWidescreen(CFG.widescreen);
|
||||
}
|
||||
GuiButton ExitBtn(btnOutline.GetWidth(), btnOutline.GetHeight());
|
||||
ExitBtn.SetAlignment(ALIGN_RIGHT, ALIGN_BOTTOM);
|
||||
ExitBtn.SetPosition(-40, -35);
|
||||
ExitBtn.SetLabel(&ExitBtnTxt);
|
||||
ExitBtn.SetImage(&ExitBtnImg);
|
||||
ExitBtn.SetTrigger(&trigA);
|
||||
ExitBtn.SetTrigger(&trigB);
|
||||
ExitBtn.SetEffectGrow();
|
||||
|
||||
GuiText usbBtnTxt((curDivice==SD?"USB":"SD"), 24, (GXColor) {0, 0, 0, 255});
|
||||
GuiImage usbBtnImg(&btnOutline);
|
||||
if (Settings.wsprompt == yes) {
|
||||
usbBtnTxt.SetWidescreen(CFG.widescreen);
|
||||
usbBtnImg.SetWidescreen(CFG.widescreen);
|
||||
}
|
||||
GuiButton usbBtn(btnOutline.GetWidth(), btnOutline.GetHeight());
|
||||
usbBtn.SetAlignment(ALIGN_CENTRE, ALIGN_BOTTOM);
|
||||
usbBtn.SetPosition(0, -35);
|
||||
usbBtn.SetLabel(&usbBtnTxt);
|
||||
usbBtn.SetImage(&usbBtnImg);
|
||||
usbBtn.SetTrigger(&trigA);
|
||||
usbBtn.SetEffectGrow();
|
||||
GuiText usbBtnTxt(browsers[(curDevice+1)%browsers.size()].rootdir, 24, (GXColor) {0, 0, 0, 255});
|
||||
GuiImage usbBtnImg(&btnOutline);
|
||||
if (Settings.wsprompt == yes) {
|
||||
usbBtnTxt.SetWidescreen(CFG.widescreen);
|
||||
usbBtnImg.SetWidescreen(CFG.widescreen);
|
||||
}
|
||||
GuiButton usbBtn(btnOutline.GetWidth(), btnOutline.GetHeight());
|
||||
usbBtn.SetAlignment(ALIGN_CENTRE, ALIGN_BOTTOM);
|
||||
usbBtn.SetPosition(0, -35);
|
||||
usbBtn.SetLabel(&usbBtnTxt);
|
||||
usbBtn.SetImage(&usbBtnImg);
|
||||
usbBtn.SetTrigger(&trigA);
|
||||
usbBtn.SetEffectGrow();
|
||||
|
||||
GuiText okBtnTxt(tr("OK"), 22, THEME.prompttext);
|
||||
GuiImage okBtnImg(&btnOutline);
|
||||
if (Settings.wsprompt == yes) {
|
||||
okBtnTxt.SetWidescreen(CFG.widescreen);
|
||||
okBtnImg.SetWidescreen(CFG.widescreen);
|
||||
}
|
||||
GuiButton okBtn(&okBtnImg,&okBtnImg, 0, 4, 40, -35, &trigA, &btnSoundOver, &btnClick,1);
|
||||
okBtn.SetLabel(&okBtnTxt);
|
||||
GuiText okBtnTxt(tr("Ok"), 22, THEME.prompttext);
|
||||
GuiImage okBtnImg(&btnOutline);
|
||||
if (Settings.wsprompt == yes) {
|
||||
okBtnTxt.SetWidescreen(CFG.widescreen);
|
||||
okBtnImg.SetWidescreen(CFG.widescreen);
|
||||
}
|
||||
GuiButton okBtn(&okBtnImg,&okBtnImg, 0, 4, 40, -35, &trigA, &btnSoundOver, &btnClick,1);
|
||||
okBtn.SetLabel(&okBtnTxt);
|
||||
|
||||
GuiFileBrowser fileBrowser(396, 248);
|
||||
fileBrowser.SetAlignment(ALIGN_CENTRE, ALIGN_TOP);
|
||||
fileBrowser.SetPosition(0, 120);
|
||||
GuiFileBrowser fileBrowser(396, 248);
|
||||
fileBrowser.SetAlignment(ALIGN_CENTRE, ALIGN_TOP);
|
||||
fileBrowser.SetPosition(0, 120);
|
||||
|
||||
GuiImageData Address(addressbar_textbox_png);
|
||||
snprintf(currentdir, sizeof(currentdir), "%s%s", browser.rootdir, browser.dir);
|
||||
GuiText AdressText(currentdir, 20, (GXColor) { 0, 0, 0, 255});
|
||||
AdressText.SetAlignment(ALIGN_LEFT, ALIGN_MIDDLE);
|
||||
AdressText.SetPosition(20, 0);
|
||||
AdressText.SetMaxWidth(Address.GetWidth()-40, GuiText::SCROLL);
|
||||
GuiImage AdressbarImg(&Address);
|
||||
GuiButton Adressbar(Address.GetWidth(), Address.GetHeight());
|
||||
Adressbar.SetAlignment(ALIGN_CENTRE, ALIGN_TOP);
|
||||
Adressbar.SetPosition(0, fileBrowser.GetTop()-45);
|
||||
Adressbar.SetImage(&AdressbarImg);
|
||||
Adressbar.SetLabel(&AdressText);
|
||||
GuiImageData Address(addressbar_textbox_png);
|
||||
GuiText AdressText(NULL, 20, (GXColor) { 0, 0, 0, 255});
|
||||
AdressText.SetTextf("%s%s", browser->rootdir, browser->dir);
|
||||
AdressText.SetAlignment(ALIGN_LEFT, ALIGN_MIDDLE);
|
||||
AdressText.SetPosition(20, 0);
|
||||
AdressText.SetMaxWidth(Address.GetWidth()-40, GuiText::SCROLL);
|
||||
GuiImage AdressbarImg(&Address);
|
||||
GuiButton Adressbar(Address.GetWidth(), Address.GetHeight());
|
||||
Adressbar.SetAlignment(ALIGN_CENTRE, ALIGN_TOP);
|
||||
Adressbar.SetPosition(0, fileBrowser.GetTop()-45);
|
||||
Adressbar.SetImage(&AdressbarImg);
|
||||
Adressbar.SetLabel(&AdressText);
|
||||
|
||||
//save var in case they cancel and return it to them
|
||||
snprintf(currentdir, sizeof(currentdir), "%s", var);
|
||||
sprintf(var,"%s", browser.rootdir);
|
||||
|
||||
HaltGui();
|
||||
GuiWindow w(screenwidth, screenheight);
|
||||
w.Append(&ExitBtn);
|
||||
HaltGui();
|
||||
GuiWindow w(screenwidth, screenheight);
|
||||
w.Append(&ExitBtn);
|
||||
// w.Append(&titleTxt);
|
||||
w.Append(&fileBrowser);
|
||||
w.Append(&Adressbar);
|
||||
w.Append(&okBtn);
|
||||
w.Append(&folderBtn);
|
||||
w.Append(&usbBtn);
|
||||
mainWindow->Append(&w);
|
||||
ResumeGui();
|
||||
w.Append(&fileBrowser);
|
||||
w.Append(&Adressbar);
|
||||
w.Append(&okBtn);
|
||||
if(!(Flags & FB_NOFOLDER_BTN))
|
||||
w.Append(&folderBtn);
|
||||
if(browsers.size()>1 && !(Flags & FB_NODEVICE_BTN))
|
||||
w.Append(&usbBtn);
|
||||
mainWindow->Append(&w);
|
||||
ResumeGui();
|
||||
int clickedIndex = -1;
|
||||
while (menu == MENU_NONE) {
|
||||
VIDEO_WaitVSync();
|
||||
|
||||
while (menu == MENU_NONE) {
|
||||
VIDEO_WaitVSync();
|
||||
if (shutdown == 1)
|
||||
Sys_Shutdown();
|
||||
|
||||
if (shutdown == 1)
|
||||
Sys_Shutdown();
|
||||
if (reset == 1)
|
||||
Sys_Reboot();
|
||||
|
||||
if (reset == 1)
|
||||
Sys_Reboot();
|
||||
for (i=0; i<FILEBROWSERSIZE; i++) {
|
||||
if (fileBrowser.fileList[i]->GetState() == STATE_CLICKED) {
|
||||
fileBrowser.fileList[i]->ResetState();
|
||||
|
||||
for (i=0; i<PAGESIZE; i++) {
|
||||
if (fileBrowser.fileList[i]->GetState() == STATE_CLICKED) {
|
||||
fileBrowser.fileList[i]->ResetState();
|
||||
// check corresponding browser entry
|
||||
if (browserList[browser.selIndex].isdir) {
|
||||
if (BrowserChangeFolder()) {
|
||||
fileBrowser.ResetState();
|
||||
fileBrowser.fileList[0]->SetState(STATE_SELECTED);
|
||||
fileBrowser.TriggerUpdate();
|
||||
sprintf(var,"%s", browser.rootdir);
|
||||
int len=strlen(browser.rootdir);
|
||||
for (unsigned int i=len;i<strlen(browser.rootdir)+strlen(browser.dir);i++) {
|
||||
var[i]=browser.dir[i-(len-1)];
|
||||
}
|
||||
AdressText.SetTextf("%s", var);
|
||||
} else {
|
||||
menu = MENU_DISCLIST;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
mainWindow->SetState(STATE_DISABLED);
|
||||
mainWindow->SetState(STATE_DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ExitBtn.GetState() == STATE_CLICKED) {
|
||||
snprintf(var,sizeof(currentdir),"%s", currentdir);
|
||||
break;
|
||||
}
|
||||
if (okBtn.GetState() == STATE_CLICKED) {
|
||||
result = 1;
|
||||
break;
|
||||
} else if (usbBtn.GetState() == STATE_CLICKED) {
|
||||
HaltGui();
|
||||
mainWindow->Remove(&w);
|
||||
ResumeGui();
|
||||
result = BrowseDevice(var, (curDivice==SD?USB:SD));
|
||||
break;
|
||||
} else if (folderBtn.GetState() == STATE_CLICKED) {
|
||||
HaltGui();
|
||||
mainWindow->Remove(&w);
|
||||
ResumeGui();
|
||||
char newfolder[100];
|
||||
char oldfolder[100];
|
||||
sprintf(newfolder,"%s/",var);
|
||||
strcpy(oldfolder,newfolder);
|
||||
|
||||
int result = OnScreenKeyboard(newfolder,100,0);
|
||||
|
||||
if ( result == 1 ) {
|
||||
int len = (strlen(newfolder)-1);
|
||||
if (newfolder[len] !='/')
|
||||
strncat (newfolder, "/", 1);
|
||||
char* pos = newfolder;
|
||||
char root[6];
|
||||
sprintf(root,"%s/",browser.rootdir);
|
||||
if (len > 0 && strcmp(oldfolder,newfolder)!=0 && strstr(newfolder,root) == pos && strstr(newfolder,"//") == NULL) {
|
||||
struct stat st;
|
||||
if (stat(newfolder, &st) != 0) {
|
||||
if (subfoldercreate(newfolder) != 1) {
|
||||
WindowPrompt(tr("Error !"),tr("Can't create directory"),tr("OK"));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
clickedIndex = browser->pageIndex + i;
|
||||
bool pathCanged = false;
|
||||
// check corresponding browser entry
|
||||
if (browser->browserList[clickedIndex].isdir)
|
||||
{
|
||||
/* go up to parent directory */
|
||||
if (strcmp(browser->browserList[clickedIndex].filename,"..") == 0)
|
||||
{
|
||||
/* remove last subdirectory name */
|
||||
int len = strlen(browser->dir);
|
||||
while(browser->dir[0] && browser->dir[len-1] == '/')
|
||||
browser->dir[--len] = '\0'; // remove all trailing '/'
|
||||
char *cptr = strrchr(browser->dir, '/');
|
||||
if(cptr) *++cptr = 0; else browser->dir[0] = '\0'; // remove trailing dir
|
||||
pathCanged = true;
|
||||
}
|
||||
/* Open a directory */
|
||||
/* current directory doesn't change */
|
||||
else if (strcmp(browser->browserList[clickedIndex].filename,"."))
|
||||
{
|
||||
/* test new directory namelength */
|
||||
if ((strlen(browser->dir) + strlen(browser->browserList[clickedIndex].filename)
|
||||
+ 1/*'/'*/) < MAXPATHLEN)
|
||||
{
|
||||
/* update current directory name */
|
||||
sprintf(browser->dir, "%s%s/",browser->dir,
|
||||
browser->browserList[clickedIndex].filename);
|
||||
pathCanged = true;
|
||||
}
|
||||
}
|
||||
if (pathCanged)
|
||||
{
|
||||
LOCK(&fileBrowser);
|
||||
ParseDirectory((char*)NULL, Flags, Filter);
|
||||
fileBrowser.ResetState();
|
||||
fileBrowser.TriggerUpdate();
|
||||
AdressText.SetTextf("%s%s", browser->rootdir, browser->dir);
|
||||
}
|
||||
clickedIndex = -1;
|
||||
}
|
||||
}
|
||||
result = BrowseDevice(var, (curDivice==SD?SD:USB));
|
||||
break;
|
||||
}
|
||||
else /* isFile */
|
||||
{
|
||||
AdressText.SetTextf("%s%s%s", browser->rootdir, browser->dir, browser->browserList[clickedIndex].filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
HaltGui();
|
||||
mainWindow->Remove(&w);
|
||||
ResumeGui();
|
||||
if (ExitBtn.GetState() == STATE_CLICKED) {
|
||||
break;
|
||||
}
|
||||
else if (okBtn.GetState() == STATE_CLICKED) {
|
||||
if(clickedIndex>=0)
|
||||
snprintf(Path, Path_size, "%s%s%s", browser->rootdir, browser->dir,browser->browserList[clickedIndex].filename);
|
||||
else
|
||||
snprintf(Path, Path_size, "%s%s", browser->rootdir, browser->dir);
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
else if (usbBtn.GetState() == STATE_CLICKED) {
|
||||
usbBtn.ResetState();
|
||||
for(u32 i=1; i<browsers.size(); i++)
|
||||
{
|
||||
LOCK(&fileBrowser);
|
||||
if(ParseDirectory((curDevice+i) % browsers.size(), Flags, Filter)==0)
|
||||
{
|
||||
fileBrowser.ResetState();
|
||||
fileBrowser.TriggerUpdate();
|
||||
AdressText.SetTextf("%s%s", browser->rootdir, browser->dir);
|
||||
usbBtnTxt.SetText(browsers[(curDevice+1)%browsers.size()].rootdir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (folderBtn.GetState() == STATE_CLICKED) {
|
||||
folderBtn.ResetState();
|
||||
|
||||
//}
|
||||
HaltGui();
|
||||
mainWindow->Remove(&w);
|
||||
ResumeGui();
|
||||
char newfolder[100];
|
||||
char oldfolder[100];
|
||||
snprintf(newfolder, sizeof(newfolder), "%s%s", browser->rootdir, browser->dir);
|
||||
strcpy(oldfolder,newfolder);
|
||||
|
||||
int result = OnScreenKeyboard(newfolder, sizeof(newfolder), strlen(browser->rootdir));
|
||||
if ( result == 1 ) {
|
||||
unsigned int len = strlen(newfolder);
|
||||
if (len>0 && len+1 < sizeof(newfolder) && newfolder[len-1] !='/')
|
||||
{
|
||||
newfolder[len] = '/';
|
||||
newfolder[len+1] = '\0';
|
||||
}
|
||||
|
||||
return result;
|
||||
struct stat st;
|
||||
if (stat(newfolder, &st) != 0) {
|
||||
if(WindowPrompt(tr("Directory does not exist!"),tr("The entered directory does not exist. Would you like to create it?") ,tr("OK"), tr("Cancel")) == 1)
|
||||
if (subfoldercreate(newfolder) == false)
|
||||
WindowPrompt(tr("Error !"),tr("Can't create directory"),tr("OK"));
|
||||
}
|
||||
if(ParseDirectory(newfolder, Flags, Filter)==0)
|
||||
{
|
||||
fileBrowser.ResetState();
|
||||
fileBrowser.TriggerUpdate();
|
||||
AdressText.SetTextf("%s%s", browser->rootdir, browser->dir);
|
||||
usbBtnTxt.SetText(browsers[(curDevice+1)%browsers.size()].rootdir);
|
||||
}
|
||||
}
|
||||
HaltGui();
|
||||
mainWindow->Append(&w);
|
||||
ResumeGui();
|
||||
}
|
||||
|
||||
}
|
||||
HaltGui();
|
||||
mainWindow->Remove(&w);
|
||||
ResumeGui();
|
||||
|
||||
//}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int BrowseDevice(char * Path, int Path_size, int Flags, FILEFILTERCALLBACK Filter, void *FilterArgs)
|
||||
{
|
||||
if(Filter)
|
||||
{
|
||||
FILTERCASCADE filter = {Filter, FilterArgs, NULL};
|
||||
return BrowseDevice(Path, Path_size, Flags, &filter);
|
||||
}
|
||||
return BrowseDevice(Path, Path_size, Flags);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
/****************************************************************************
|
||||
* libwiigui Template
|
||||
* Tantric 2009
|
||||
* Tantric 2009
|
||||
*
|
||||
* modified by dimok
|
||||
* modified by dimok and ardi
|
||||
*
|
||||
* filebrowser.h
|
||||
*
|
||||
@ -17,21 +17,9 @@
|
||||
|
||||
#define MAXJOLIET 255
|
||||
#define MAXDISPLAY MAXPATHLEN
|
||||
#define ROOTDIRLEN 10
|
||||
|
||||
|
||||
enum {
|
||||
SD,
|
||||
USB
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char dir[MAXPATHLEN]; // directory path of browserList
|
||||
char rootdir[10]; // directory path of browserList
|
||||
int numEntries; // # of entries in browserList
|
||||
int selIndex; // currently selected index of browserList
|
||||
int pageIndex; // starting index of browserList page display
|
||||
} BROWSERINFO;
|
||||
|
||||
typedef struct {
|
||||
u64 offset; // DVD offset
|
||||
u64 length; // file length in 64 bytes for sizes higher than 4GB
|
||||
@ -40,15 +28,52 @@ typedef struct {
|
||||
char displayname[MAXDISPLAY + 1]; // name for browser display
|
||||
} BROWSERENTRY;
|
||||
|
||||
extern BROWSERINFO browser;
|
||||
extern BROWSERENTRY * browserList;
|
||||
typedef struct {
|
||||
char dir[MAXPATHLEN]; // directory path of browserList
|
||||
char rootdir[ROOTDIRLEN];// directory path of browserList
|
||||
int pageIndex; // starting index of browserList page display
|
||||
std::vector<BROWSERENTRY> browserList;
|
||||
} BROWSERINFO;
|
||||
extern BROWSERINFO *browser;
|
||||
|
||||
int UpdateDirName();
|
||||
int FileSortCallback(const void *f1, const void *f2);
|
||||
void ResetBrowser();
|
||||
int ParseDirectory();
|
||||
int BrowserChangeFolder();
|
||||
int BrowseDevice(int device);
|
||||
int BrowseDevice(char * var, int force =-1);
|
||||
|
||||
#define FB_NOFOLDER_BTN 0x0001
|
||||
#define FB_NODEVICE_BTN 0x0002
|
||||
#define FB_TRYROOTDIR 0x0004
|
||||
#define FB_TRYSTDDEV 0x0008
|
||||
#define FB_DEFAULT (FB_TRYROOTDIR | FB_TRYSTDDEV)
|
||||
|
||||
typedef int (*FILEFILTERCALLBACK)(BROWSERENTRY *Entry, void* Args);
|
||||
int noDIRS(BROWSERENTRY *Entry, void* Args);
|
||||
int noFILES(BROWSERENTRY *Entry, void* Args);
|
||||
int noEXT(BROWSERENTRY *Entry, void* Args);
|
||||
|
||||
typedef struct _FILTERCASCADE{
|
||||
FILEFILTERCALLBACK filter;
|
||||
void *filter_args;
|
||||
_FILTERCASCADE *next;
|
||||
} FILTERCASCADE;
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* BrowseDevice
|
||||
* Displays a list of files on the selected path
|
||||
* Path returns the selectet Path/File
|
||||
* Path_size is the space of the Path-array
|
||||
* Ret: 0 ok / -1 Error
|
||||
***************************************************************************/
|
||||
/***************************************************************************
|
||||
* Example:
|
||||
* FILTERKASKADE filter2 = {noEXT, NULL, NULL};
|
||||
* FILTERKASKADE filter1 = {noDirs, NULL, &filter2};
|
||||
* char Path[MAXPATHLEN] = "SD:/";
|
||||
* BrowseDevice(Path, MAXPATHLEN, FB_DEFAULT, &filter1);
|
||||
*
|
||||
*
|
||||
***************************************************************************/
|
||||
int BrowseDevice(char * Path, int Path_size, int Flags/*=FB_DEFAULT*/, FILTERCASCADE *Filter=NULL) ;
|
||||
int BrowseDevice(char * Path, int Path_size, int Flags, FILEFILTERCALLBACK Filter, void *FilterArgs=NULL);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -1259,7 +1259,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
strlcpy(entered, Settings.covers_path, sizeof(entered));
|
||||
titleTxt.SetText(tr("3D Cover Path"));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
titleTxt.SetText(tr("Custom Paths"));
|
||||
w.Append(&optionBrowser2);
|
||||
w.Append(&backBtn);
|
||||
@ -1285,7 +1285,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
strlcpy(entered, Settings.covers2d_path, sizeof(entered));
|
||||
titleTxt.SetText(tr("2D Cover Path"));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
titleTxt.SetText(tr("Custom Paths"));
|
||||
w.Append(&optionBrowser2);
|
||||
w.Append(&backBtn);
|
||||
@ -1311,7 +1311,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
strlcpy(entered, Settings.disc_path, sizeof(entered));
|
||||
titleTxt.SetText(tr("Disc Artwork Path"));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
titleTxt.SetText(tr("Custom Paths"));
|
||||
w.Append(&optionBrowser2);
|
||||
w.Append(&backBtn);
|
||||
@ -1337,7 +1337,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
titleTxt.SetText(tr("Theme Path"));
|
||||
strlcpy(entered, CFG.theme_path, sizeof(entered));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
HaltGui();
|
||||
w.RemoveAll();
|
||||
if ( result == 1 ) {
|
||||
@ -1394,7 +1394,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
titleTxt.SetText(tr("WiiTDB Path"));
|
||||
strlcpy(entered, Settings.titlestxt_path, sizeof(entered));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
w.Append(&optionBrowser2);
|
||||
titleTxt.SetText(tr("Custom Paths"));
|
||||
w.Append(&backBtn);
|
||||
@ -1425,7 +1425,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
strlcpy(entered, Settings.update_path, sizeof(entered));
|
||||
titleTxt.SetText(tr("Update Path"));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
titleTxt.SetText(tr("Custom Paths"));
|
||||
w.Append(&optionBrowser2);
|
||||
w.Append(&backBtn);
|
||||
@ -1446,7 +1446,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
strlcpy(entered, Settings.Cheatcodespath, sizeof(entered));
|
||||
titleTxt.SetText(tr("GCT Cheatcodes Path"));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
titleTxt.SetText(tr("Custom Paths"));
|
||||
w.Append(&optionBrowser2);
|
||||
w.Append(&backBtn);
|
||||
@ -1467,7 +1467,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
strlcpy(entered, Settings.TxtCheatcodespath, sizeof(entered));
|
||||
titleTxt.SetText(tr("TXT Cheatcodes Path"));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
titleTxt.SetText(tr("Custom Paths"));
|
||||
w.Append(&optionBrowser2);
|
||||
w.Append(&backBtn);
|
||||
@ -1488,7 +1488,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
strlcpy(entered, Settings.dolpath, sizeof(entered));
|
||||
titleTxt.SetText(tr("DOL Path"));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
titleTxt.SetText(tr("Custom Paths"));
|
||||
w.Append(&optionBrowser2);
|
||||
w.Append(&backBtn);
|
||||
@ -1514,7 +1514,7 @@ int MenuSettings() {
|
||||
char entered[43] = "";
|
||||
strlcpy(entered, Settings.homebrewapps_path, sizeof(entered));
|
||||
titleTxt.SetText(tr("Homebrew Apps Path"));
|
||||
int result = BrowseDevice(entered);
|
||||
int result = BrowseDevice(entered, sizeof(entered), FB_DEFAULT, noFILES);
|
||||
titleTxt.SetText(tr("Custom Paths"));
|
||||
w.Append(&optionBrowser2);
|
||||
w.Append(&backBtn);
|
||||
|
Loading…
Reference in New Issue
Block a user