diff --git a/.gitignore b/.gitignore index 37183b78..9cc65068 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ fpsconfig/gimx-fpsconfig core/gimx core/test/haptic/ff_lg_test loader/gimx-loader + +.vscode diff --git a/Makedefs b/Makedefs index 1030da3e..bb2673fd 100644 --- a/Makedefs +++ b/Makedefs @@ -26,6 +26,11 @@ CXXFLAGS += -pg LDFLAGS += -pg endif +#Library address sanitiser is not available for mingw64. This applies to msys2. +#Uncomment below lines, and comment above lines, for plain debug symbols. +#CFLAGS += -Wall -Wextra -Werror -O0 -g +#CXXFLAGS += -Wall -Wextra -Werror -O0 -g + CPPFLAGS += -I../shared GIMXFILE_LDFLAGS = $(GIMXLOG_LDFLAGS) -L../shared/gimxfile diff --git a/Makefile b/Makefile index 5a7d597d..32b13b91 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -DIRS = shared utils core config launcher fpsconfig loader +DIRS = shared utils core config launcher fpsconfig loader fetchconfig + ifneq ($(OS),Windows_NT) DIRS+= po @@ -19,13 +20,14 @@ build-core: build-shared build-config: build-shared build-launcher: build-shared build-fpsconfig: build-shared +build-fetchconfig: build-shared clean: $(CLEANDIRS) -$(CLEANDIRS): +$(CLEANDIRS): $(MAKE) -C $(@:clean-%=%) clean ifeq ($(OS),Windows_NT) -DLLS = $(shell ntldd -R {core,config,fpsconfig,launcher}/*.exe | grep mingw | sed "s/.*=> //g" | cut -d' ' -f 1 | sed 's/\\/\\\\/g' | xargs cygpath -u | sort | uniq)\ +DLLS = $(shell ntldd -R {core,config,fpsconfig,launcher,fetchconfig}/*.exe | grep mingw | sed "s/.*=> //g" | cut -d' ' -f 1 | sed 's/\\/\\\\/g' | xargs cygpath -u | sort | uniq)\ $(shell ntldd -R shared/*/*.dll | grep mingw | sed "s/.*=> //g" | cut -d' ' -f 1 | sed 's/\\/\\\\/g' | xargs cygpath -u | sort | uniq) install: all mkdir -p setup @@ -36,6 +38,7 @@ install: all cp -u -f core/gimx setup/gimx.exe cp -u -f config/gimx-config setup/gimx-config.exe cp -u -f launcher/gimx-launcher setup/gimx-launcher.exe + cp -u -f fetchconfig/gimx-fetchconfig setup/gimx-fetchconfig.exe cp -u -f fpsconfig/gimx-fpsconfig setup/gimx-fpsconfig.exe cp -u -f loader/gimx-loader setup/gimx-loader.exe cp -u -f shared/gimxinput/src/windows/gamecontrollerdb.txt setup diff --git a/core/macros.c b/core/macros.c index b98b5246..bbb470a2 100644 --- a/core/macros.c +++ b/core/macros.c @@ -304,7 +304,7 @@ static int get_macro(char tokens[MAX_LINE_TOKENS][LINE_MAX], int ntoks) if(ntoks < 2) { return -1; } - + pcurrent = NULL; if(ntoks > 2) @@ -460,19 +460,19 @@ static int get_event(char tokens[MAX_LINE_TOKENS][LINE_MAX], int ntoks) int rvalue; int delay_nb; int i; - + if(!pcurrent || ntoks < 2) { return -1; } - + int ret = 0; - + if (!strncmp(tokens[0], "KEYDOWN", strlen("KEYDOWN"))) { rbutton = ginput_key_id(tokens[1]); ALLOCATE_EVENT_OR_FAIL - + pcurrent->events[pcurrent->nb_events - 1].type = GE_KEYDOWN; pcurrent->events[pcurrent->nb_events - 1].key.keysym = rbutton; } @@ -490,7 +490,7 @@ static int get_event(char tokens[MAX_LINE_TOKENS][LINE_MAX], int ntoks) rbutton = ginput_key_id(tokens[1]); ALLOCATE_EVENT_OR_FAIL - + pcurrent->events[pcurrent->nb_events - 1].type = GE_KEYDOWN; pcurrent->events[pcurrent->nb_events - 1].key.keysym = rbutton; @@ -501,7 +501,7 @@ static int get_event(char tokens[MAX_LINE_TOKENS][LINE_MAX], int ntoks) } ALLOCATE_EVENT_OR_FAIL - + pcurrent->events[pcurrent->nb_events - 1].type = GE_KEYUP; pcurrent->events[pcurrent->nb_events - 1].key.keysym = rbutton; } @@ -510,7 +510,7 @@ static int get_event(char tokens[MAX_LINE_TOKENS][LINE_MAX], int ntoks) rbutton = ginput_mouse_button_id(tokens[1]); ALLOCATE_EVENT_OR_FAIL - + pcurrent->events[pcurrent->nb_events - 1].type = GE_MOUSEBUTTONDOWN; pcurrent->events[pcurrent->nb_events - 1].button.button = rbutton; } @@ -646,12 +646,12 @@ int get_trigger(char tokens[MAX_LINE_TOKENS][LINE_MAX], int ntoks) { int etype = -1; int rbutton; - + if(!pcurrent || ntoks < 2) { return -1; } - + if(ntoks > 2) { if(!strncmp(tokens[1], "KEYDOWN", strlen("KEYDOWN"))) @@ -1386,4 +1386,3 @@ unsigned int macro_process() } return running_macro_nb; } - diff --git a/fetchconfig/Makefile b/fetchconfig/Makefile new file mode 100644 index 00000000..1539b34c --- /dev/null +++ b/fetchconfig/Makefile @@ -0,0 +1,70 @@ +include ../Makedefs + + +ifneq ($(OS),Windows_NT) +prefix=$(DESTDIR)/usr +bindir=$(prefix)/bin +endif + + +NAME=$(shell basename "$(shell pwd)") + + +ifneq ($(OS),Windows_NT) +LDLIBS += -lstdc++ -lm `pkg-config --libs ncursesw` +else +LDLIBS += -lws2_32 -lstdc++ -lpdcursesw -lintl +endif + +CPPFLAGS += -Iinclude -std=c++14 + +LDFLAGS += -L../shared/gimxfetchconfig +LDLIBS += -lgimxfetchconfig +LDFLAGS += $(GIMXINPUT_LDFLAGS) $(GIMXUSB_LDFLAGS) $(GIMXPOLL_LDFLAGS) +LDLIBS += $(GIMXINPUT_LDLIBS) $(GIMXUSB_LDLIBS) $(GIMXPOLL_LDLIBS) +LDLIBS += $(GIMXUPDATER_LDLIBS) $(GIMXCONFIGUPDATER_LDLIBS) +LDFLAGS += $(GIMXUPDATER_LDFLAGS) $(GIMXCONFIGUPDATER_LDFLAGS) + + +OBJECTS := $(patsubst %.cpp,%.o,$(wildcard *.cpp)) + +OUT=gimx-$(NAME) + +ifneq ($(OS),Windows_NT) +BINS = $(OUT) +else +OBJECTS += $(NAME).rc.o +endif + + +all: $(OUT) + + +$(OUT): $(OBJECTS) + +ifeq ($(OS),Windows_NT) +$(NAME).rc.o: $(NAME).rc + WINDRES $^ -o $@ +endif + + +clean: + $(RM) $(OBJECTS) $(OUT) + +.PHONY: clean + + +ifneq ($(OS),Windows_NT) +install: all + mkdir -p $(prefix) + mkdir -p $(bindir) + for i in $(BINS); do cp $$i $(bindir)/; done + for i in $(BINS); do chmod ug+s $(bindir)/$$i; done + +uninstall: + -for i in $(BINS); do $(RM) $(bindir)/$$i; done + -rmdir $(bindir) + -rmdir $(prefix) + +really-clean: clean uninstall +endif diff --git a/fetchconfig/configDownload.cpp b/fetchconfig/configDownload.cpp new file mode 100644 index 00000000..64e46d15 --- /dev/null +++ b/fetchconfig/configDownload.cpp @@ -0,0 +1,289 @@ +/* + * configDownload.cpp + * + * Author: Zac + * Contact: codeohms@protonmail.com + * Created on: 10 Aug. 2018 + */ + +#include "include/configDownload.h" + + +bool printStat(CUStat status, WINDOW* s) +{ + if(status != Ok) + { + wprintw(s, "ConfigUpdater module failed. Return value %i.\n" \ + "Reason: ", status); + + switch (status) { + case DlFail: + wprintw(s, "download failed.", status); + break; + case InitFail: + wprintw(s, "initialisation failed.", status); + break; + case Cancelled: + wprintw(s, "download cancelled.", status); + break; + default: + break; + } + + wprintw(s, "\nPress any key to continue", status); + wrefresh(s); + wgetch(s); + return false; + } + + return true; +} + + +int progress_callback_configupdater_terminal(void *clientp, CUStat status, double progress, double total) +{ + return ((ConfigDownload *) clientp)->updateProgress(status, progress, total); +} + +int updateProgress_common(ttyProgressDialog* progressDialog, CUStat status, double progress, double total) +{ + std::string message; + switch(status) + { + case Pending: + message = "Connecting"; + break; + case Running: + message = "Progress: "; + message += Updater::getProgress(progress, total); + break; + default: + break; + } + + if (status >= 0) { + if(progressDialog->update(progress, message) == false) { + return 1; + } + } else { + return 1; + } + + return 0; +} + + +/* + * Return codes for both manual and auto config download classes + * -3 download failed, -2 ConfigUpdater module failed to init, + * -1 cancelled, 0 okay, 1 connection pending, 2 download in progress + * + */ + +ConfigDownload::ConfigDownload() : dlWinData(newWinData(stdscr)) +{ + { + char* res = gfile_homedir(); + if(res == NULL) + { + wprintw(stdscr, "Cannot access config directory. Press any key to continue"); + wrefresh(stdscr); + wgetch(stdscr); + } + + gimxConfigDir = std::string(res) + (GIMX_DIR CONFIG_DIR); + free(res); + } + + //Progress dialog + int height = dlWinData->height; + int width = dlWinData->width; + dlWinData->height = 7; + dlWinData->width = 30; + dlWinData->startX = (width /2) - (dlWinData->width /2); + dlWinData->startY = (height /2) - (dlWinData->height /2); + dlScreen = newwin(dlWinData->height, dlWinData->width, dlWinData->startY, dlWinData->startX); + dlWinData->win = dlScreen; + progressDialog = std::make_unique(dlWinData.get(), "Downloading"); +} + +void ConfigDownload::initDownload() +{ + werase(stdscr); + werase(dlScreen); + wrefresh(stdscr); + wrefresh(dlScreen); + progressDialog->dialog(); +} + +void ConfigDownload::cleanDownload() +{ + progressDialog->resetPBar(); + werase(stdscr); + werase(dlScreen); + wrefresh(stdscr); + wrefresh(dlScreen); +} + +CUStat ConfigDownload::grabConfigs(std::list& configs, WINDOW* screen) +{ + CUStat status = Ok; + if(!configs.empty()) + { + initDownload(); + for(std::list::iterator it = configs.begin(); it != configs.end(); ++it) + { + status = configupdater().getconfig(gimxConfigDir, *it, + progress_callback_configupdater_terminal, this); + if (status != Ok) + break; + cleanDownload(); + } + } + else + wprintw(screen, "No configs to download\n"); + + if(printStat(status, screen)) + { + wprintw(screen, "Completed\n"); + wprintw(screen, "Press any key to continue"); + } + + wrefresh(screen); + wgetch(screen); + + return status; +} + +int ConfigDownload::updateProgress(CUStat status, double progress, double total) +{ + return updateProgress_common(progressDialog.get(), status, progress, total); +} + + +ManualConfigDownload::ManualConfigDownload() : winData(newWinData(stdscr)) +{ + //Selection menu + winData->height -= 1; + winData->win = newwin(winData->height, winData->width, winData->startY, winData->startX); + + //Help dialog + helpText = "Press:\n\nESC to exit\nENTER to select\nArrow keys to change selection\n"\ + "Page up and down keys to change page"; +} + +bool ManualConfigDownload::help() +{ + BasicMenu helpMenu(helpText, winData.get(), "Help menu"); + /*Stylise the menu borders*/ + // borders => (bool, we, ns) + helpMenu.setDrawBorder(true, 0, 0); + flushinp(); + + mvwprintw(stdscr, winData->height, 0, "Press ESC to exit menu"); + wrefresh(stdscr); + + helpMenu.menuLoop(); + + wmove(stdscr, winData->height, 0); + wclrtoeol(stdscr); + mvwprintw(stdscr, winData->height, 0, "Press h for help"); + wrefresh(stdscr); + + return true; +} + +int ManualConfigDownload::chooseConfigs() +{ + CUStat status; + + /*Download config list*/ + initDownload(); + status = configupdater().getconfiglist(configList, + progress_callback_configupdater_terminal, this); + cleanDownload(); + + if(!printStat(status, winData->win)) + return status; + + /*Ensure the config list is not empty*/ + if(configList.empty()) + { + wprintw(winData->win, "Can't retrieve configs list!\nPress any key to continue"); + wrefresh(winData->win); + wgetch(winData->win); + return status; + } + + /*Add config names to options list*/ + std::vector chosen; + { + std::string options; + for(std::string configName : configList) + options += configName + "\n"; + + SelectionMenu selectionMenu(options, winData.get(), + "Select the files to download"); + + /*Stylise the menu borders*/ + // borders => (bool, we, ns) + selectionMenu.setDrawBorder(true, 0, 0); + + const char* help = "Press h for help"; + selectionMenu.setCustomAction(std::bind(&ManualConfigDownload::help, this)); + { + std::map& kB = selectionMenu.getKeyBindings(); + kB[104] = NavContent::custom; //104 => 'h' + selectionMenu.setKeyBindings(kB); + } + mvwprintw(stdscr, winData->height, 0, help); + wrefresh(stdscr); + + selectionMenu.menuLoop(); + selectionMenu.getResult(chosen); + } + + werase(winData->win); + + std::string sel; + std::string file; + /*Check if any chosen configs exist already*/ + int c; + for(int cIndex : chosen) + { + sel = *(std::next(configList.begin(), cIndex)); + file = gimxConfigDir + sel; + + if(gfile_isfile(gimxConfigDir.c_str(), sel.c_str()) != 0 ) + { + wprintw(winData->win, "Overwrite local file: %s?(y or n)\n", file.c_str()); + wrefresh(winData->win); + c = wgetch(winData->win); + + if( not (c == 121 || c == 89) ) //don't overwrite + continue; + } + selectedConfigs.push_back(sel); + } + werase(winData->win); + wrefresh(winData->win); + + status = static_cast(grabConfigs(selectedConfigs, winData->win)); + + wrefresh(winData->win); + return status; +} + + +int AutoConfigDownload::chooseConfigs() +{ + std::list download; + + autoConfigDownload([&download](std::string confName) -> void { + download.push_back(confName); } + ); + + CUStat res = grabConfigs(download, dlScreen); + + return res; +} diff --git a/fetchconfig/easyCurses.cpp b/fetchconfig/easyCurses.cpp new file mode 100755 index 00000000..589df185 --- /dev/null +++ b/fetchconfig/easyCurses.cpp @@ -0,0 +1,776 @@ +/* + * ncursesIO.cpp + * + * Author: Zac + * Contact: codeohms@protonmail.com + * Created on: 10 Aug. 2018 + */ + +#include "include/easyCurses.h" + +namespace EasyCurses +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Miscellaneous + + unsigned roundUp(float n) + { + //First, check if float is integer/round number + //Isolate decimal point + unsigned roundN = n; + //Subtract round number from float => 2.6 - 2 OR 3.0 - 3 + float decimal = n - roundN; + + //Check if decimal is .0 to see if n is an interger + if(decimal == .0f) + return roundN; + else + return roundN +1; + } + + unsigned centreText(unsigned windowWidth, unsigned textLength) + { + //Math works best with even numbers + if((windowWidth % 2) != 0) + windowWidth -= 1; + return (windowWidth - textLength) / 2; + } + + std::string fillString(unsigned textLength, char filler) + { + std::string temp; + for(unsigned i = 0; i < textLength; ++i) + temp += filler; + return temp; + } + + void clrToEolFrom(WINDOW* win, unsigned y, unsigned x) + { + wmove(win, y, x); + wclrtoeol(win); + } + void eraseChunk(WINDOW* win, unsigned y, unsigned x, unsigned amount) + { + mvwprintw(win, y, x, fillString(amount).c_str()); + wmove(win, y, x); + } + void eraseChunk(WINDOW* win, unsigned startY, unsigned endY, unsigned startX, unsigned endX) + { + for(unsigned currentLine = startY; currentLine <= endY; ++currentLine) + { + eraseChunk(win, currentLine, startX, endX - startX); + } + } + + unsigned maxLines(unsigned height, unsigned padding) { return height - (padding *2); } + unsigned maxChars(unsigned screenWidth, unsigned padding) { return screenWidth - (padding *2); } + + namespace TextFormat + { + void overflow(std::string text, unsigned maxLength, LineEnds& lineFormat, OverFlow& oFLayout, bool wrap) + { + //Each time this is run, information is recalculated. + lineFormat.clear(); + oFLayout.clear(); + + size_t numChars; + size_t start = 0; + size_t linePos = 0; + size_t currentLine = 0; + + LineEnds oFlow; + + while(linePos != text.length() && (linePos +1) != text.length()) + { //2nd case is when last char is new line + { + size_t endPoint; + + //Set end points + linePos = text.find("\n", linePos + (linePos == 0 ? 0 : 1)); + + if(linePos == std::string::npos) + linePos = text.length(); + + if(linePos > (maxLength + start)) + { + //Fill out overflow information + size_t virtStart = start + maxLength; + size_t virtEnd; + + auto commit = [&]() -> void { + if(wrap) + oFlow.push_back(std::make_pair(virtEnd, numChars)); + else + oFLayout.insert(std::make_pair( currentLine, + std::make_pair(virtEnd, numChars) )); + + }; + + + while(true) + { + virtEnd = virtStart + maxLength; + + if(virtEnd > linePos) + { + virtEnd = linePos; + numChars = linePos - virtStart; + + commit(); + break; + } + else + { + numChars = maxLength; + + commit(); + + if(virtEnd == linePos) + break; + } + + virtStart = virtEnd; + } + + numChars = maxLength +1; + endPoint = start + maxLength; + start = linePos +1; + } + else + { + numChars = linePos - start; + + start = linePos +1; + endPoint = linePos; + } + + lineFormat.push_back(std::make_pair(endPoint, numChars)); + if(wrap) + { + for(auto line : oFlow) + { + size_t ep = line.first; + unsigned chars = line.second; + lineFormat.push_back(std::make_pair(ep, chars)); + } + oFlow.clear(); + } + } + + ++currentLine; + } + } + + void overflow(std::string text, unsigned maxLength, LineEnds& lineFormat) + { + OverFlow placeHolder; + + overflow(text, maxLength, lineFormat, placeHolder, true); + } + } + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Widgets + + ProgressBar::ProgressBar(WINDOW* win, unsigned size, unsigned startY, + unsigned startX, std::string prefix, std::string suffix, char barChar, char point) + { + /*Setup values and generate progress bar*/ + this->prefix = prefix; + this->suffix = suffix; + this->barChar[0] = barChar; + this->point[0] = point; + this->barChar[1] = 0; + this->point[1] = 0; + prevAmount = 0; + setSize(size); + + window = win; + this->startY = startY; + this->startX = startX; + currentX = this->startX; + } + + void ProgressBar::reset() + { + prevAmount = 0; + currentX = this->startX; + } + + void ProgressBar::first() + { + //-1 so zero initialised like progress + mvwprintw(window, startY, startX, "%s%s%s%s", prefix.c_str(), point, fillString(barSize -1).c_str(), suffix.c_str()); + currentX += prefix.length(); + } + void ProgressBar::update(double progress) + { + /*Only print difference between the last and future render*/ + //Progress needs to be a fraction to convert progress % into % of progress bar + unsigned amount = (progress /100) * barSize; + unsigned diff = amount - prevAmount; + + mvwprintw(window, startY, currentX, fillString(diff, barChar[0]).c_str()); + + currentX += diff; + prevAmount = amount; + + if(diff != 0 && amount < barSize) + mvwprintw(window, startY, currentX, "%s", point); + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Menus + + WinData* newWinData(WINDOW* window, unsigned height, unsigned width, unsigned startY, unsigned startX, unsigned padY, unsigned padX, \ + bool drawBorder, int bordersWE, int bordersNS) + { + WinData* data = new WinData; + + data->win = window; + + data->drawBorder = drawBorder; + data->bordersWE = bordersWE; + data->bordersNS = bordersNS; + + data->paddingY = padY; + data->paddingX = padX; + data->startY = startY; + data->startX = startX; + if(height == 0) + data->height = getmaxy(data->win); + else + data->height = height; + if(width == 0) + data->width = getmaxx(data->win); + else + data->width = width; + + return data; + } + + + void Menus::drawTitle() + { + /*Create title and message*/ + unsigned padding = centreText(winData->width, title.length()); + mvwprintw(winData->win, 0, padding, title.c_str()); + wrefresh(winData->win); + } + + void Menus::drawFrame() + { + if(winData->drawBorder) + box(winData->win, winData->bordersWE, winData->bordersNS); + wrefresh(winData->win); + } + + + BasicMenu::BasicMenu(std::string lines, WinData* windowsData, std::string title) : Menus(windowsData, title) + { + page = 0; + text = lines; + //This sets up pageLayout and oFLayout + TF::overflow(lines, _maxChars(), pageLayout); + numLines = pageLayout.size(); + + changed = false; + + keypad(winData->win, true); + keyBindings = { + { KEY_NPAGE, NavContent::pageUp }, { KEY_PPAGE, NavContent::pageDown }, + { 27, NavContent::finish } + }; //27 => ESC + + this->cusAct = [](void) -> bool { + return false; + }; + } + + unsigned BasicMenu::getInput() + { + return wgetch(winData->win); + } + + NavContent BasicMenu::mapInput(unsigned rawInput) + { + auto res = keyBindings.find(rawInput); + if(res != keyBindings.end()) + return res->second; + return NavContent::null; + } + + void BasicMenu::calculatePage(NavContent seek) + { + unsigned startPage = page; + + switch(seek) + { + /* + * [pageUp] + * 1st case: turn over first page to last, 2nd case: page up, + * [pageDown] + * 3rd case: turn over last page to first, 4th case: page down + */ + case NavContent::pageUp: + if(page == 0) //1st + page = lastPage() -1; + else //2nd + --page; + break; + + case NavContent::pageDown: + if(page == lastPage() -1) //3rd + page = 0; + else //4th + ++page; + break; + + default: + break; + } + + if(startPage != page) + setUpdate(); + } + + void BasicMenu::inputHandling(NavContent& input) + { + input = mapInput(getInput()); + + if(input == NavContent::custom) + { + if(cusAct()) + update(); + } + } + + void BasicMenu::printStyle() + { + unsigned x, y; + x = winData->paddingX; + y = winData->paddingY; + + for(unsigned l = pageTop(); l <= pageBottom() && l < numLines; ++l) + { + auto info = pageLayout[l]; + unsigned numChars = (info.second == _maxChars() +1? _maxChars() : info.second); //-1 so zero-initialised + size_t lineStart = info.first - numChars; + + wmove(winData->win, y, x); + waddstr(winData->win, text.substr(lineStart, numChars).c_str()); + ++y; + } + } + + void BasicMenu::drawContent() + { + unsigned x, y; + + //Start postion of content in window + x = winData->paddingX; + y = winData->paddingY; + + eraseChunk(winData->win, y, _maxLines() +y, x, _maxChars() +x); + + printStyle(); + wrefresh(winData->win); + } + + void BasicMenu::drawPageNumber() + { + mvwprintw(winData->win, winData->height -1, 1, "pg %i / %i", page +1, lastPage()); + wrefresh(winData->win); + } + + bool BasicMenu::doUpdate() + { + bool old = changed; + changed = false; + return old; + } + + void BasicMenu::update() + { + drawContent(); + drawFrame(); + drawPageNumber(); + drawTitle(); + wrefresh(winData->win); + } + + void BasicMenu::menuLoop() + { + page = 0; + update(); + wrefresh(winData->win); + + NavContent input = NavContent::null; + do + { + inputHandling(input); + calculatePage(input); + if(doUpdate()) + update(); + } while(input != NavContent::finish); + } + + void BasicMenu::setDrawBorder(bool draw, int bordersWE, int bordersNS) + { + winData->drawBorder = draw; + winData->bordersWE = bordersWE; + winData->bordersNS = bordersNS; + } + + + SelectionMenu::SelectionMenu(std::string text, WinData* windowsData, std::string title) + : BasicMenu(text, windowsData, title) + { + this->text = text; + + TF::overflow(text, _maxChars(), pageLayout, oFLayout); + highlight = 0; + oFLine = 0; + numLines = pageLayout.size(); + + for(unsigned i = 0; i < numLines; ++i) + selected[i] = false; + + checkMark = "X"; + blankMark = "O"; + + keyBindings[KEY_RIGHT] = NavContent::right; + keyBindings[KEY_LEFT] = NavContent::left; + keyBindings[KEY_UP] = NavContent::lineUp; + keyBindings[KEY_DOWN] = NavContent::lineDown; + keyBindings[10] = NavContent::select; //Enter key + + keypad(winData->win, true); + } + + void SelectionMenu::getResult(std::vector& chosen) + { + for(auto option : selected) + { + if(option.second == true) + chosen.push_back(option.first); + } + } + + void SelectionMenu::updateLineTrackers(unsigned n) + { + highlight = n; + oFLine = 0; + } + + void SelectionMenu::drawCheckMark(unsigned index, unsigned y) + { + mvwprintw(winData->win, y, winData->width -1, (selected[index] ? checkMark : blankMark ).c_str()); + wrefresh(winData->win); + } + + void SelectionMenu::drawAllCheckMarks() + { + unsigned y = winData->paddingY; + for(unsigned cIndex = pageTop(); cIndex <= pageBottom() && cIndex < numLines; ++cIndex) + { + drawCheckMark(cIndex, y); + ++y; + } + } + + void SelectionMenu::calculatePage(NavContent seek) + { + //Deals with page up and down keys + BasicMenu::calculatePage(seek); + if(doUpdate()) + { + updateLineTrackers(pageTop()); + setUpdate(); + return; + } + + unsigned startPage = page; + + /* + * Check if we need to turn to next page: + * 1st case: turn over last page to first, 2nd case: page up/next, + * 3rd case: lineDown, + * 4th case: turn over first page to last, 5th case: page down/back, + * 6th case: lineUp + */ + switch(seek) + { + case NavContent::lineDown: + setUpdate(); + + if((highlight +1) == numLines) //1st + { + page = 0; + updateLineTrackers(0); + break; + } + else if((highlight +1) > pageBottom()) //2nd + { + ++page; + updateLineTrackers(pageTop()); + break; + } + else //3rd + updateLineTrackers(highlight +1); + break; + + case NavContent::lineUp: + setUpdate(); + + if(highlight == 0) //4th + { + page = lastPage() -1; + updateLineTrackers(pageBottom()); + } + else if((highlight -1) < pageTop()) //5th + { + --page; + updateLineTrackers(pageBottom()); + } + else //6th + updateLineTrackers(highlight -1); + break; + + default: + break; + } + + if(startPage != page) + setUpdate(); + } + + void SelectionMenu::navTrunc(NavContent input) + { + if(pageLayout[highlight].second != _maxChars() +1) + return; + + bool changed = false; + unsigned numChars = 0; + size_t lineStart = 0; + + unsigned first = 0; + unsigned last = oFLayout.count(highlight); + + auto set = [&]() -> void { + auto temp = oFLayout.lower_bound(highlight); + for(unsigned count = 1; count < oFLine; ++count) + { + ++temp; + } + numChars = (temp->second).second; + lineStart = ((temp->second).first) - numChars; + }; + + switch(input) + { + case NavContent::left: + if(oFLine != 0 && (oFLine -1) > first) + { + changed = true; + --oFLine; + set(); + } + else if((oFLine -1) < first) + break; + else // oFLine == 0 + { + changed = true; + oFLine = 0; + + auto info = pageLayout[highlight]; + numChars = (info.second == _maxChars() +1? _maxChars() : info.second); + lineStart = info.first - numChars; + } + break; + + case NavContent::right: + if(oFLine == first) + { + changed = true; + set(); + ++oFLine; + } + else if((oFLine +1) <= last) + { + changed = true; + ++oFLine; + set(); + } + break; + + default: + break; + } + + if(changed) + { + eraseChunk(winData->win, yCoord(), winData->paddingX, _maxChars()); + wattron(winData->win, A_REVERSE); + waddstr(winData->win, text.substr(lineStart, numChars).c_str()); + wattroff(winData->win, A_REVERSE); + } + + wrefresh(winData->win); + } + + void SelectionMenu::inputHandling(NavContent& input) + { + BasicMenu::inputHandling(input); + + + switch (input) + { + case NavContent::right: + navTrunc(input); + break; + + case NavContent::left: + navTrunc(input); + break; + + case NavContent::select: + if(selected[highlight] == true) + selected[highlight] = false; + else + selected[highlight] = true; + drawCheckMark(highlight, yCoord()); + break; + + case NavContent::finish: + //User done selecting + return; + + default: + break; + } + } + + void SelectionMenu::drawFrame() + { + BasicMenu::drawFrame(); + + drawAllCheckMarks(); + wrefresh(winData->win); + } + + void SelectionMenu::update() + { + BasicMenu::update(); + + drawAllCheckMarks(); + } + + void SelectionMenu::menuLoop(unsigned startChoice) + { + updateLineTrackers(startChoice); + + BasicMenu::menuLoop(); + } + + void SelectionMenu::printStyle() + { + unsigned x, y; + //Start postion of content in window + x = winData->paddingX; + y = winData->paddingY; + + for(unsigned l = pageTop(); l <= pageBottom() && l < numLines; ++l) + { + auto info = pageLayout[l]; + unsigned numChars = (info.second == _maxChars() +1? _maxChars() : info.second); + size_t lineStart = info.first - numChars; + + wmove(winData->win, y, x); + //Highlight the present choice + if(highlight == l) + { + wattron(winData->win, A_REVERSE); + waddstr(winData->win, text.substr(lineStart, numChars).c_str()); + wattroff(winData->win, A_REVERSE); + } + else + { + waddstr(winData->win, text.substr(lineStart, numChars).c_str()); + } + ++y; + } + } + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Dialogs + + ttyProgressDialog::ttyProgressDialog(WinData* windowsData, std::string title, std::string mssg)\ + : Menus(windowsData, title), pBar(windowsData->win, 0, 0, 0) + { + //+2 to have space between eta data and message + progInfoPosY = winData->paddingY +2; + progInfoPosX = winData->paddingX; + + pBarPosY = progInfoPosY +1; + pBarPosX = progInfoPosX; + + message = mssg; + + progInfo = ""; + } + + void ttyProgressDialog::setDrawBorder(bool draw, int bordersWE, int bordersNS) + { + winData->drawBorder = draw; + winData->bordersWE = bordersWE; + winData->bordersNS = bordersNS; + } + + bool ttyProgressDialog::update(double progress, std::string mssg) + { + if(!mssg.empty()) + { + int diff = message.length() - mssg.length(); + if(diff > 0) + eraseChunk(winData->win, winData->paddingY, winData->paddingX +mssg.length(), diff); + + message = mssg; + mvwprintw(winData->win, winData->paddingY, winData->paddingX, message.c_str()); + } + + /*Update ETA*/ + //TODO add ETA + mvwprintw(winData->win, progInfoPosY, progInfoPosX, "%*.2f%%", 6, progress); + + /*Update progress bar*/ + pBar.update(progress); + + wrefresh(winData->win); + return true; + } + void ttyProgressDialog::dialog() + { + drawFrame(); + drawTitle(); + mvwprintw(winData->win, winData->paddingY, winData->paddingX, message.c_str()); + + /*Create ETA*/ + //This will be on the 4th line => index 3 + //TODO add ETA + mvwprintw(winData->win, progInfoPosY, progInfoPosX, progInfo.c_str()); + + /*Create space for progress bar*/ + //This will be on the 5th line => index 4 + pBar.setStartCoords(pBarPosY, pBarPosX); + pBar.setSize(winData->width - (winData->paddingX *2)); + pBar.first(); + + wrefresh(winData->win); + } + +} diff --git a/fetchconfig/gimx-fetchconfig.cpp b/fetchconfig/gimx-fetchconfig.cpp new file mode 100644 index 00000000..bba08704 --- /dev/null +++ b/fetchconfig/gimx-fetchconfig.cpp @@ -0,0 +1,141 @@ +/* + * gimx-fetchconfig + * + * Author: Zac + * Contact: codeohms@protonmail.com + * Created on: 8 Aug. 2018 + */ +#include //for smart pointers +#include +#include + +#include +#include + +#include "parseArgs.h" +#include "easyCurses.h" +#include "configDownload.h" + + +void help() +{ + std::cout << "Usage: gimxFileDownloader