mirror of
https://github.com/nzbget/nzbget.git
synced 2026-01-01 18:47:45 -05:00
1349 lines
32 KiB
C++
1349 lines
32 KiB
C++
/*
|
|
* This file is part of nzbget. See <http://nzbget.net>.
|
|
*
|
|
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
|
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include "nzbget.h"
|
|
|
|
#ifndef DISABLE_CURSES
|
|
|
|
// "ncurses.h" contains many global defines such as for "OK" or "clear" which we sure don't want
|
|
// everywhere in the project. For that reason we include "ncurses.h" directly here instead of
|
|
// putting it into global header file "nzbget.h".
|
|
|
|
#ifdef HAVE_NCURSES_H
|
|
#include <ncurses.h>
|
|
#endif
|
|
#ifdef HAVE_NCURSES_NCURSES_H
|
|
#include <ncurses/ncurses.h>
|
|
#endif
|
|
|
|
#include "NCursesFrontend.h"
|
|
#include "Options.h"
|
|
#include "Util.h"
|
|
|
|
#ifndef WIN32
|
|
// curses.h on Solaris declares "clear()" via DEFINE. That causes problems, because
|
|
// it also affects calls to deque's method "clear()", producing compiler errors.
|
|
// We use function "curses_clear()" to call macro "clear" of curses, then
|
|
// undefine macro "clear".
|
|
void curses_clear()
|
|
{
|
|
clear();
|
|
}
|
|
#undef clear
|
|
#endif
|
|
|
|
extern void ExitProc();
|
|
|
|
static const int NCURSES_COLORPAIR_TEXT = 1;
|
|
static const int NCURSES_COLORPAIR_INFO = 2;
|
|
static const int NCURSES_COLORPAIR_WARNING = 3;
|
|
static const int NCURSES_COLORPAIR_ERROR = 4;
|
|
static const int NCURSES_COLORPAIR_DEBUG = 5;
|
|
static const int NCURSES_COLORPAIR_DETAIL = 6;
|
|
static const int NCURSES_COLORPAIR_STATUS = 7;
|
|
static const int NCURSES_COLORPAIR_KEYBAR = 8;
|
|
static const int NCURSES_COLORPAIR_INFOLINE = 9;
|
|
static const int NCURSES_COLORPAIR_TEXTHIGHL = 10;
|
|
static const int NCURSES_COLORPAIR_CURSOR = 11;
|
|
static const int NCURSES_COLORPAIR_HINT = 12;
|
|
|
|
static const int MAX_SCREEN_WIDTH = 512;
|
|
|
|
#ifdef WIN32
|
|
static const int COLOR_BLACK = 0;
|
|
static const int COLOR_BLUE = FOREGROUND_BLUE;
|
|
static const int COLOR_RED = FOREGROUND_RED;
|
|
static const int COLOR_GREEN = FOREGROUND_GREEN;
|
|
static const int COLOR_WHITE = FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN;
|
|
static const int COLOR_MAGENTA = FOREGROUND_RED | FOREGROUND_BLUE;
|
|
static const int COLOR_CYAN = FOREGROUND_BLUE | FOREGROUND_GREEN;
|
|
static const int COLOR_YELLOW = FOREGROUND_RED | FOREGROUND_GREEN;
|
|
|
|
static const int READKEY_EMPTY = 0;
|
|
|
|
#define KEY_DOWN VK_DOWN
|
|
#define KEY_UP VK_UP
|
|
#define KEY_PPAGE VK_PRIOR
|
|
#define KEY_NPAGE VK_NEXT
|
|
#define KEY_END VK_END
|
|
#define KEY_HOME VK_HOME
|
|
#define KEY_BACKSPACE VK_BACK
|
|
|
|
#else
|
|
|
|
static const int READKEY_EMPTY = ERR;
|
|
|
|
#endif
|
|
|
|
NCursesFrontend::NCursesFrontend()
|
|
{
|
|
m_summary = true;
|
|
m_fileList = true;
|
|
|
|
m_showNzbname = g_Options->GetCursesNzbName();
|
|
m_showTimestamp = g_Options->GetCursesTime();
|
|
m_groupFiles = g_Options->GetCursesGroup();
|
|
|
|
// Setup curses
|
|
#ifdef WIN32
|
|
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
CONSOLE_CURSOR_INFO ConsoleCursorInfo;
|
|
GetConsoleCursorInfo(hConsole, &ConsoleCursorInfo);
|
|
ConsoleCursorInfo.bVisible = false;
|
|
SetConsoleCursorInfo(hConsole, &ConsoleCursorInfo);
|
|
if (IsRemoteMode())
|
|
{
|
|
SetConsoleTitle("NZBGet - remote mode");
|
|
}
|
|
else
|
|
{
|
|
SetConsoleTitle("NZBGet");
|
|
}
|
|
#else
|
|
m_window = initscr();
|
|
if (m_window == nullptr)
|
|
{
|
|
printf("ERROR: m_pWindow == nullptr\n");
|
|
exit(-1);
|
|
}
|
|
keypad(stdscr, true);
|
|
nodelay((WINDOW*)m_window, true);
|
|
noecho();
|
|
curs_set(0);
|
|
m_useColor = has_colors();
|
|
#endif
|
|
|
|
if (m_useColor)
|
|
{
|
|
#ifndef WIN32
|
|
start_color();
|
|
#endif
|
|
init_pair(0, COLOR_WHITE, COLOR_BLUE);
|
|
init_pair(NCURSES_COLORPAIR_TEXT, COLOR_WHITE, COLOR_BLACK);
|
|
init_pair(NCURSES_COLORPAIR_INFO, COLOR_GREEN, COLOR_BLACK);
|
|
init_pair(NCURSES_COLORPAIR_WARNING, COLOR_MAGENTA, COLOR_BLACK);
|
|
init_pair(NCURSES_COLORPAIR_ERROR, COLOR_RED, COLOR_BLACK);
|
|
init_pair(NCURSES_COLORPAIR_DEBUG, COLOR_WHITE, COLOR_BLACK);
|
|
init_pair(NCURSES_COLORPAIR_DETAIL, COLOR_GREEN, COLOR_BLACK);
|
|
init_pair(NCURSES_COLORPAIR_STATUS, COLOR_BLUE, COLOR_WHITE);
|
|
init_pair(NCURSES_COLORPAIR_KEYBAR, COLOR_WHITE, COLOR_BLUE);
|
|
init_pair(NCURSES_COLORPAIR_INFOLINE, COLOR_WHITE, COLOR_BLUE);
|
|
init_pair(NCURSES_COLORPAIR_TEXTHIGHL, COLOR_BLACK, COLOR_CYAN);
|
|
init_pair(NCURSES_COLORPAIR_CURSOR, COLOR_BLACK, COLOR_YELLOW);
|
|
init_pair(NCURSES_COLORPAIR_HINT, COLOR_WHITE, COLOR_RED);
|
|
}
|
|
}
|
|
|
|
NCursesFrontend::~NCursesFrontend()
|
|
{
|
|
#ifdef WIN32
|
|
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
CONSOLE_CURSOR_INFO ConsoleCursorInfo;
|
|
GetConsoleCursorInfo(hConsole, &ConsoleCursorInfo);
|
|
ConsoleCursorInfo.bVisible = true;
|
|
SetConsoleCursorInfo(hConsole, &ConsoleCursorInfo);
|
|
#else
|
|
keypad(stdscr, false);
|
|
echo();
|
|
curs_set(1);
|
|
endwin();
|
|
#endif
|
|
printf("\n");
|
|
SetHint(nullptr);
|
|
}
|
|
|
|
void NCursesFrontend::Run()
|
|
{
|
|
debug("Entering NCursesFrontend-loop");
|
|
|
|
m_dataUpdatePos = 0;
|
|
|
|
while (!IsStopped())
|
|
{
|
|
// The data (queue and log) is updated each m_iUpdateInterval msec,
|
|
// but the window is updated more often for better reaction on user's input
|
|
|
|
bool updateNow = false;
|
|
int key = ReadConsoleKey();
|
|
|
|
if (key != READKEY_EMPTY)
|
|
{
|
|
// Update now and next if a key is pressed.
|
|
updateNow = true;
|
|
m_updateNextTime = true;
|
|
}
|
|
else if (m_updateNextTime)
|
|
{
|
|
// Update due to key being pressed during previous call.
|
|
updateNow = true;
|
|
m_updateNextTime = false;
|
|
}
|
|
else if (m_dataUpdatePos <= 0)
|
|
{
|
|
updateNow = true;
|
|
m_updateNextTime = false;
|
|
}
|
|
|
|
if (updateNow)
|
|
{
|
|
Update(key);
|
|
}
|
|
|
|
if (m_dataUpdatePos <= 0)
|
|
{
|
|
m_dataUpdatePos = m_updateInterval;
|
|
}
|
|
|
|
// update more often (sleep shorter) if need faster reaction on user input
|
|
int sleepInterval = m_inputMode == normal ? 100 : 10;
|
|
Wait(sleepInterval);
|
|
m_dataUpdatePos -= sleepInterval;
|
|
}
|
|
|
|
FreeData();
|
|
|
|
debug("Exiting NCursesFrontend-loop");
|
|
}
|
|
|
|
void NCursesFrontend::NeedUpdateData()
|
|
{
|
|
m_dataUpdatePos = 10;
|
|
m_updateNextTime = true;
|
|
}
|
|
|
|
void NCursesFrontend::Update(int key)
|
|
{
|
|
// Figure out how big the screen is
|
|
CalcWindowSizes();
|
|
|
|
if (m_dataUpdatePos <= 0)
|
|
{
|
|
FreeData();
|
|
m_neededLogEntries = m_messagesWinClientHeight;
|
|
if (!PrepareData())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// recalculate frame sizes
|
|
CalcWindowSizes();
|
|
}
|
|
|
|
if (m_inputMode == editQueue)
|
|
{
|
|
int queueSize = CalcQueueSize();
|
|
if (queueSize == 0)
|
|
{
|
|
m_selectedQueueEntry = 0;
|
|
m_inputMode = normal;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------
|
|
// Print Current NZBInfoList
|
|
//------------------------------------------
|
|
if (m_queueWinHeight > 0)
|
|
{
|
|
PrintQueue();
|
|
}
|
|
|
|
//------------------------------------------
|
|
// Print Messages
|
|
//------------------------------------------
|
|
if (m_messagesWinHeight > 0)
|
|
{
|
|
PrintMessages();
|
|
}
|
|
|
|
PrintStatus();
|
|
|
|
PrintKeyInputBar();
|
|
|
|
UpdateInput(key);
|
|
|
|
RefreshScreen();
|
|
}
|
|
|
|
void NCursesFrontend::CalcWindowSizes()
|
|
{
|
|
int nrRows, nrColumns;
|
|
#ifdef WIN32
|
|
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
CONSOLE_SCREEN_BUFFER_INFO BufInfo;
|
|
GetConsoleScreenBufferInfo(hConsole, &BufInfo);
|
|
nrRows = BufInfo.srWindow.Bottom - BufInfo.srWindow.Top + 1;
|
|
nrColumns = BufInfo.srWindow.Right - BufInfo.srWindow.Left + 1;
|
|
#else
|
|
getmaxyx(stdscr, nrRows, nrColumns);
|
|
#endif
|
|
if (nrRows != m_screenHeight || nrColumns != m_screenWidth)
|
|
{
|
|
#ifdef WIN32
|
|
int screenAreaSize = nrRows * nrColumns;
|
|
m_screenBuffer.resize(screenAreaSize);
|
|
m_oldScreenBuffer.resize(screenAreaSize);
|
|
#else
|
|
curses_clear();
|
|
#endif
|
|
m_screenHeight = nrRows;
|
|
m_screenWidth = nrColumns;
|
|
}
|
|
|
|
int queueSize = CalcQueueSize();
|
|
|
|
m_queueWinTop = 0;
|
|
m_queueWinHeight = (m_screenHeight - 2) * m_queueWindowPercentage / 100;
|
|
if (m_queueWinHeight - 1 > queueSize)
|
|
{
|
|
m_queueWinHeight = queueSize > 0 ? queueSize + 1 : 1 + 1;
|
|
}
|
|
m_queueWinClientHeight = m_queueWinHeight - 1;
|
|
if (m_queueWinClientHeight < 0)
|
|
{
|
|
m_queueWinClientHeight = 0;
|
|
}
|
|
|
|
m_messagesWinTop = m_queueWinTop + m_queueWinHeight;
|
|
m_messagesWinHeight = m_screenHeight - m_queueWinHeight - 2;
|
|
m_messagesWinClientHeight = m_messagesWinHeight - 1;
|
|
if (m_messagesWinClientHeight < 0)
|
|
{
|
|
m_messagesWinClientHeight = 0;
|
|
}
|
|
}
|
|
|
|
int NCursesFrontend::CalcQueueSize()
|
|
{
|
|
int queueSize = 0;
|
|
if (m_groupFiles)
|
|
{
|
|
queueSize = DownloadQueue::Guard()->GetQueue()->size();
|
|
}
|
|
else
|
|
{
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
queueSize += nzbInfo->GetFileList()->size();
|
|
}
|
|
}
|
|
return queueSize;
|
|
}
|
|
|
|
void NCursesFrontend::PlotLine(const char * string, int row, int pos, int colorPair)
|
|
{
|
|
BString<1024> buffer("%-*s", m_screenWidth, string);
|
|
int len = buffer.Length();
|
|
if (len > m_screenWidth - pos && m_screenWidth - pos < MAX_SCREEN_WIDTH)
|
|
{
|
|
buffer[m_screenWidth - pos] = '\0';
|
|
}
|
|
|
|
PlotText(buffer, row, pos, colorPair, false);
|
|
}
|
|
|
|
void NCursesFrontend::PlotText(const char * string, int row, int pos, int colorPair, bool blink)
|
|
{
|
|
#ifdef WIN32
|
|
int bufPos = row * m_screenWidth + pos;
|
|
int len = strlen(string);
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
char c = string[i];
|
|
CharToOemBuff(&c, &c, 1);
|
|
m_screenBuffer[bufPos + i].Char.AsciiChar = c;
|
|
m_screenBuffer[bufPos + i].Attributes = m_colorAttr[colorPair];
|
|
}
|
|
#else
|
|
if( m_useColor )
|
|
{
|
|
attron(COLOR_PAIR(colorPair));
|
|
if (blink)
|
|
{
|
|
attron(A_BLINK);
|
|
}
|
|
}
|
|
mvaddstr(row, pos, (char*)string);
|
|
if( m_useColor )
|
|
{
|
|
attroff(COLOR_PAIR(colorPair));
|
|
if (blink)
|
|
{
|
|
attroff(A_BLINK);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void NCursesFrontend::RefreshScreen()
|
|
{
|
|
#ifdef WIN32
|
|
bool bufChanged = !std::equal(m_screenBuffer.begin(), m_screenBuffer.end(), m_oldScreenBuffer.begin(), m_oldScreenBuffer.end(),
|
|
[](CHAR_INFO& a, CHAR_INFO& b)
|
|
{
|
|
return a.Char.AsciiChar == b.Char.AsciiChar && a.Attributes == b.Attributes;
|
|
});
|
|
|
|
if (bufChanged)
|
|
{
|
|
COORD BufSize;
|
|
BufSize.X = m_screenWidth;
|
|
BufSize.Y = m_screenHeight;
|
|
|
|
COORD BufCoord;
|
|
BufCoord.X = 0;
|
|
BufCoord.Y = 0;
|
|
|
|
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
CONSOLE_SCREEN_BUFFER_INFO BufInfo;
|
|
GetConsoleScreenBufferInfo(hConsole, &BufInfo);
|
|
WriteConsoleOutput(hConsole, m_screenBuffer.data(), BufSize, BufCoord, &BufInfo.srWindow);
|
|
|
|
BufInfo.dwCursorPosition.X = BufInfo.srWindow.Right;
|
|
BufInfo.dwCursorPosition.Y = BufInfo.srWindow.Bottom;
|
|
SetConsoleCursorPosition(hConsole, BufInfo.dwCursorPosition);
|
|
|
|
m_oldScreenBuffer = m_screenBuffer;
|
|
}
|
|
#else
|
|
// Cursor placement
|
|
wmove((WINDOW*)m_window, m_screenHeight, m_screenWidth);
|
|
|
|
// NCurses refresh
|
|
refresh();
|
|
#endif
|
|
}
|
|
|
|
#ifdef WIN32
|
|
void NCursesFrontend::init_pair(int colorNumber, WORD wForeColor, WORD wBackColor)
|
|
{
|
|
m_colorAttr.resize(colorNumber + 1);
|
|
m_colorAttr[colorNumber] = wForeColor | (wBackColor << 4);
|
|
}
|
|
#endif
|
|
|
|
void NCursesFrontend::PrintMessages()
|
|
{
|
|
int lineNr = m_messagesWinTop;
|
|
|
|
BString<1024> buffer("%s Messages", m_useColor ? "" : "*** ");
|
|
PlotLine(buffer, lineNr++, 0, NCURSES_COLORPAIR_INFOLINE);
|
|
|
|
int line = lineNr + m_messagesWinClientHeight - 1;
|
|
int linesToPrint = m_messagesWinClientHeight;
|
|
|
|
GuardedMessageList messages = GuardMessages();
|
|
|
|
// print messages from bottom
|
|
for (int i = (int)messages->size() - 1; i >= 0 && linesToPrint > 0; i--)
|
|
{
|
|
int printedLines = PrintMessage(messages->at(i), line, linesToPrint);
|
|
line -= printedLines;
|
|
linesToPrint -= printedLines;
|
|
}
|
|
|
|
if (linesToPrint > 0)
|
|
{
|
|
// too few messages, print them again from top
|
|
line = lineNr + m_messagesWinClientHeight - 1;
|
|
while (linesToPrint-- > 0)
|
|
{
|
|
PlotLine("", line--, 0, NCURSES_COLORPAIR_TEXT);
|
|
}
|
|
int linesToPrint2 = m_messagesWinClientHeight;
|
|
for (int i = (int)messages->size() - 1; i >= 0 && linesToPrint2 > 0; i--)
|
|
{
|
|
int printedLines = PrintMessage(messages->at(i), line, linesToPrint2);
|
|
line -= printedLines;
|
|
linesToPrint2 -= printedLines;
|
|
}
|
|
}
|
|
}
|
|
|
|
int NCursesFrontend::PrintMessage(Message& msg, int row, int maxLines)
|
|
{
|
|
const char* messageType[] = { "INFO ", "WARNING ", "ERROR ", "DEBUG ", "DETAIL "};
|
|
const int messageTypeColor[] = { NCURSES_COLORPAIR_INFO, NCURSES_COLORPAIR_WARNING,
|
|
NCURSES_COLORPAIR_ERROR, NCURSES_COLORPAIR_DEBUG, NCURSES_COLORPAIR_DETAIL };
|
|
|
|
CString text;
|
|
|
|
if (m_showTimestamp)
|
|
{
|
|
time_t rawtime = msg.GetTime() + g_Options->GetTimeCorrection();
|
|
text.Format("%s - %s", *Util::FormatTime(rawtime), msg.GetText());
|
|
}
|
|
else
|
|
{
|
|
text = msg.GetText();
|
|
}
|
|
|
|
// replace some special characters with spaces
|
|
for (char* p = (char*)text; *p; p++)
|
|
{
|
|
if (*p == '\n' || *p == '\r' || *p == '\b')
|
|
{
|
|
*p = ' ';
|
|
}
|
|
}
|
|
|
|
int len = strlen(text);
|
|
int winWidth = m_screenWidth - 8;
|
|
int msgLines = len / winWidth;
|
|
if (len % winWidth > 0)
|
|
{
|
|
msgLines++;
|
|
}
|
|
|
|
int lines = 0;
|
|
for (int i = msgLines - 1; i >= 0 && lines < maxLines; i--)
|
|
{
|
|
int r = row - msgLines + i + 1;
|
|
PlotLine(text + winWidth * i, r, 8, NCURSES_COLORPAIR_TEXT);
|
|
if (i == 0)
|
|
{
|
|
PlotText(messageType[msg.GetKind()], r, 0, messageTypeColor[msg.GetKind()], false);
|
|
}
|
|
else
|
|
{
|
|
PlotText(" ", r, 0, messageTypeColor[msg.GetKind()], false);
|
|
}
|
|
lines++;
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
void NCursesFrontend::PrintStatus()
|
|
{
|
|
int statusRow = m_screenHeight - 2;
|
|
|
|
BString<100> timeString;
|
|
|
|
int currentDownloadSpeed = m_standBy ? 0 : m_currentDownloadSpeed;
|
|
if (currentDownloadSpeed > 0 && !m_pauseDownload)
|
|
{
|
|
int64 remain_sec = (int64)(m_remainingSize / currentDownloadSpeed);
|
|
int h = (int)(remain_sec / 3600);
|
|
int m = (int)((remain_sec % 3600) / 60);
|
|
int s = (int)(remain_sec % 60);
|
|
timeString.Format(" (~ %.2d:%.2d:%.2d)", h, m, s);
|
|
}
|
|
|
|
BString<100> downloadLimit;
|
|
if (m_downloadLimit > 0)
|
|
{
|
|
downloadLimit.Format(", Limit %i KB/s", m_downloadLimit / 1024);
|
|
}
|
|
|
|
BString<100> postStatus;
|
|
if (m_postJobCount > 0)
|
|
{
|
|
postStatus.Format(", %i post-job%s", m_postJobCount, m_postJobCount > 1 ? "s" : "");
|
|
}
|
|
|
|
int averageSpeed = (int)(m_dnTimeSec > 0 ? m_allBytes / m_dnTimeSec : 0);
|
|
|
|
BString<1024> status(" %d threads, %s, %s remaining%s%s%s%s, Avg. %s",
|
|
m_threadCount, *Util::FormatSpeed(currentDownloadSpeed),
|
|
*Util::FormatSize(m_remainingSize),
|
|
*timeString, *postStatus, m_pauseDownload ? (m_standBy ? ", Paused" : ", Pausing") : "",
|
|
*downloadLimit, *Util::FormatSpeed(averageSpeed));
|
|
PlotLine(status, statusRow, 0, NCURSES_COLORPAIR_STATUS);
|
|
}
|
|
|
|
void NCursesFrontend::PrintKeyInputBar()
|
|
{
|
|
int queueSize = CalcQueueSize();
|
|
int inputBarRow = m_screenHeight - 1;
|
|
|
|
if (!m_hint.Empty())
|
|
{
|
|
time_t time = Util::CurrentTime();
|
|
if (time - m_startHint < 5)
|
|
{
|
|
PlotLine(m_hint, inputBarRow, 0, NCURSES_COLORPAIR_HINT);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
SetHint(nullptr);
|
|
}
|
|
}
|
|
|
|
switch (m_inputMode)
|
|
{
|
|
case normal:
|
|
if (m_groupFiles)
|
|
{
|
|
PlotLine("(Q)uit | (E)dit | (P)ause | (R)ate | (W)indow | (G)roup | (T)ime", inputBarRow, 0, NCURSES_COLORPAIR_KEYBAR);
|
|
}
|
|
else
|
|
{
|
|
PlotLine("(Q)uit | (E)dit | (P)ause | (R)ate | (W)indow | (G)roup | (T)ime | n(Z)b", inputBarRow, 0, NCURSES_COLORPAIR_KEYBAR);
|
|
}
|
|
break;
|
|
case editQueue:
|
|
{
|
|
const char* status = nullptr;
|
|
if (m_selectedQueueEntry > 0 && queueSize > 1 && m_selectedQueueEntry == queueSize - 1)
|
|
{
|
|
status = "(Q)uit | (E)xit | (P)ause | (D)elete | (U)p/(T)op";
|
|
}
|
|
else if (queueSize > 1 && m_selectedQueueEntry == 0)
|
|
{
|
|
status = "(Q)uit | (E)xit | (P)ause | (D)elete | dow(N)/(B)ottom";
|
|
}
|
|
else if (queueSize > 1)
|
|
{
|
|
status = "(Q)uit | (E)xit | (P)ause | (D)elete | (U)p/dow(N)/(T)op/(B)ottom";
|
|
}
|
|
else
|
|
{
|
|
status = "(Q)uit | (E)xit | (P)ause | (D)elete";
|
|
}
|
|
|
|
PlotLine(status, inputBarRow, 0, NCURSES_COLORPAIR_KEYBAR);
|
|
break;
|
|
}
|
|
case downloadRate:
|
|
BString<100> hint("Download rate: %i", m_inputValue);
|
|
PlotLine(hint, inputBarRow, 0, NCURSES_COLORPAIR_KEYBAR);
|
|
// Print the cursor
|
|
PlotText(" ", inputBarRow, 15 + m_inputNumberIndex, NCURSES_COLORPAIR_CURSOR, true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void NCursesFrontend::SetHint(const char* hint)
|
|
{
|
|
m_hint = hint;
|
|
if (!m_hint.Empty())
|
|
{
|
|
m_startHint = Util::CurrentTime();
|
|
}
|
|
}
|
|
|
|
void NCursesFrontend::PrintQueue()
|
|
{
|
|
if (m_groupFiles)
|
|
{
|
|
PrintGroupQueue();
|
|
}
|
|
else
|
|
{
|
|
PrintFileQueue();
|
|
}
|
|
}
|
|
|
|
void NCursesFrontend::PrintFileQueue()
|
|
{
|
|
int lineNr = m_queueWinTop + 1;
|
|
int64 remaining = 0;
|
|
int64 paused = 0;
|
|
int pausedFiles = 0;
|
|
int fileNum = 0;
|
|
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
for (FileInfo* fileInfo : nzbInfo->GetFileList())
|
|
{
|
|
if (fileNum >= m_queueScrollOffset && fileNum < m_queueScrollOffset + m_queueWinHeight -1)
|
|
{
|
|
PrintFilename(fileInfo, lineNr++, fileNum == m_selectedQueueEntry);
|
|
}
|
|
fileNum++;
|
|
|
|
if (fileInfo->GetPaused())
|
|
{
|
|
pausedFiles++;
|
|
paused += fileInfo->GetRemainingSize();
|
|
}
|
|
remaining += fileInfo->GetRemainingSize();
|
|
}
|
|
}
|
|
|
|
if (fileNum > 0)
|
|
{
|
|
BString<1024> header(" %sFiles for downloading - %i / %i files in queue - %s / %s",
|
|
m_useColor ? "" : "*** ", fileNum,
|
|
fileNum - pausedFiles,
|
|
*Util::FormatSize(remaining), *Util::FormatSize(remaining - paused));
|
|
PrintTopHeader(header, m_queueWinTop, true);
|
|
}
|
|
else
|
|
{
|
|
lineNr--;
|
|
BString<1024> header("%s Files for downloading", m_useColor ? "" : "*** ");
|
|
PrintTopHeader(header, lineNr++, true);
|
|
PlotLine("Ready to receive nzb-job", lineNr++, 0, NCURSES_COLORPAIR_TEXT);
|
|
}
|
|
}
|
|
|
|
void NCursesFrontend::PrintFilename(FileInfo * fileInfo, int row, bool selected)
|
|
{
|
|
int color = 0;
|
|
const char* Brace1 = "[";
|
|
const char* Brace2 = "]";
|
|
if (m_inputMode == editQueue && selected)
|
|
{
|
|
color = NCURSES_COLORPAIR_TEXTHIGHL;
|
|
if (!m_useColor)
|
|
{
|
|
Brace1 = "<";
|
|
Brace2 = ">";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
color = NCURSES_COLORPAIR_TEXT;
|
|
}
|
|
|
|
const char* downloading = "";
|
|
if (fileInfo->GetActiveDownloads() > 0)
|
|
{
|
|
downloading = " *";
|
|
}
|
|
|
|
BString<100> priority;
|
|
if (fileInfo->GetNzbInfo()->GetPriority() != 0)
|
|
{
|
|
priority.Format(" [%+i]", fileInfo->GetNzbInfo()->GetPriority());
|
|
}
|
|
|
|
BString<100> completed;
|
|
if (fileInfo->GetRemainingSize() < fileInfo->GetSize())
|
|
{
|
|
completed.Format(", %i%%", (int)(100 - fileInfo->GetRemainingSize() * 100 / fileInfo->GetSize()));
|
|
}
|
|
|
|
BString<1024> nzbNiceName;
|
|
if (m_showNzbname)
|
|
{
|
|
nzbNiceName.Format("%s%c", fileInfo->GetNzbInfo()->GetName(), PATH_SEPARATOR);
|
|
}
|
|
|
|
BString<1024> text("%s%i%s%s%s %s%s (%s%s)%s", Brace1, fileInfo->GetId(),
|
|
Brace2, *priority, downloading, *nzbNiceName, fileInfo->GetFilename(),
|
|
*Util::FormatSize(fileInfo->GetSize()),
|
|
*completed, fileInfo->GetPaused() ? " (paused)" : "");
|
|
|
|
PlotLine(text, row, 0, color);
|
|
}
|
|
|
|
void NCursesFrontend::PrintTopHeader(char* header, int lineNr, bool upTime)
|
|
{
|
|
BString<1024> buffer("%-*s", m_screenWidth, header);
|
|
int headerLen = strlen(header);
|
|
int charsLeft = m_screenWidth - headerLen - 2;
|
|
|
|
int time = upTime ? m_upTimeSec : m_dnTimeSec;
|
|
int d = time / 3600 / 24;
|
|
int h = (time % (3600 * 24)) / 3600;
|
|
int m = (time % 3600) / 60;
|
|
int s = time % 60;
|
|
BString<100> timeStr;
|
|
|
|
if (d == 0)
|
|
{
|
|
timeStr.Format("%.2d:%.2d:%.2d", h, m, s);
|
|
if ((int)strlen(timeStr) > charsLeft)
|
|
{
|
|
timeStr.Format("%.2d:%.2d", h, m);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
timeStr.Format("%i %s %.2d:%.2d:%.2d", d, (d == 1 ? "day" : "days"), h, m, s);
|
|
if ((int)strlen(timeStr) > charsLeft)
|
|
{
|
|
timeStr.Format("%id %.2d:%.2d:%.2d", d, h, m, s);
|
|
}
|
|
if ((int)strlen(timeStr) > charsLeft)
|
|
{
|
|
timeStr.Format("%id %.2d:%.2d", d, h, m);
|
|
}
|
|
}
|
|
|
|
const char* shortCap = upTime ? " Up " : "Dn ";
|
|
const char* longCap = upTime ? " Uptime " : " Download-time ";
|
|
|
|
int timeLen = strlen(timeStr);
|
|
int shortCapLen = strlen(shortCap);
|
|
int longCapLen = strlen(longCap);
|
|
|
|
if (charsLeft - timeLen - longCapLen >= 0)
|
|
{
|
|
snprintf(buffer + m_screenWidth - timeLen - longCapLen, MAX_SCREEN_WIDTH - (m_screenWidth - timeLen - longCapLen), "%s%s", longCap, *timeStr);
|
|
}
|
|
else if (charsLeft - timeLen - shortCapLen >= 0)
|
|
{
|
|
snprintf(buffer + m_screenWidth - timeLen - shortCapLen, MAX_SCREEN_WIDTH - (m_screenWidth - timeLen - shortCapLen), "%s%s", shortCap, *timeStr);
|
|
}
|
|
else if (charsLeft - timeLen >= 0)
|
|
{
|
|
snprintf(buffer + m_screenWidth - timeLen, MAX_SCREEN_WIDTH - (m_screenWidth - timeLen), "%s", *timeStr);
|
|
}
|
|
|
|
PlotLine(buffer, lineNr, 0, NCURSES_COLORPAIR_INFOLINE);
|
|
}
|
|
|
|
void NCursesFrontend::PrintGroupQueue()
|
|
{
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
|
|
int lineNr = m_queueWinTop;
|
|
|
|
if (downloadQueue->GetQueue()->empty())
|
|
{
|
|
BString<1024> buffer("%s NZBs for downloading", m_useColor ? "" : "*** ");
|
|
PrintTopHeader(buffer, lineNr++, false);
|
|
PlotLine("Ready to receive nzb-job", lineNr++, 0, NCURSES_COLORPAIR_TEXT);
|
|
}
|
|
else
|
|
{
|
|
lineNr++;
|
|
|
|
ResetColWidths();
|
|
int calcLineNr = lineNr;
|
|
int i = 0;
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
if (i >= m_queueScrollOffset && i < m_queueScrollOffset + m_queueWinHeight -1)
|
|
{
|
|
PrintGroupname(nzbInfo, calcLineNr++, false, true);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
int64 remaining = 0;
|
|
int64 paused = 0;
|
|
i = 0;
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
if (i >= m_queueScrollOffset && i < m_queueScrollOffset + m_queueWinHeight -1)
|
|
{
|
|
PrintGroupname(nzbInfo, lineNr++, i == m_selectedQueueEntry, false);
|
|
}
|
|
i++;
|
|
remaining += nzbInfo->GetRemainingSize();
|
|
paused += nzbInfo->GetPausedSize();
|
|
}
|
|
|
|
BString<1024> buffer(" %sNZBs for downloading - %i NZBs in queue - %s / %s",
|
|
m_useColor ? "" : "*** ", (int)downloadQueue->GetQueue()->size(),
|
|
*Util::FormatSize(remaining), *Util::FormatSize(remaining - paused));
|
|
PrintTopHeader(buffer, m_queueWinTop, false);
|
|
}
|
|
}
|
|
|
|
void NCursesFrontend::ResetColWidths()
|
|
{
|
|
m_colWidthFiles = 0;
|
|
m_colWidthTotal = 0;
|
|
m_colWidthLeft = 0;
|
|
}
|
|
|
|
void NCursesFrontend::PrintGroupname(NzbInfo* nzbInfo, int row, bool selected, bool calcColWidth)
|
|
{
|
|
int color = NCURSES_COLORPAIR_TEXT;
|
|
char chBrace1 = '[';
|
|
char chBrace2 = ']';
|
|
if (m_inputMode == editQueue && selected)
|
|
{
|
|
color = NCURSES_COLORPAIR_TEXTHIGHL;
|
|
if (!m_useColor)
|
|
{
|
|
chBrace1 = '<';
|
|
chBrace2 = '>';
|
|
}
|
|
}
|
|
|
|
const char* downloading = "";
|
|
if (nzbInfo->GetActiveDownloads() > 0)
|
|
{
|
|
downloading = " *";
|
|
}
|
|
|
|
BString<100> priority;
|
|
if (nzbInfo->GetPriority() != 0)
|
|
{
|
|
priority.Format(" [%+i]", nzbInfo->GetPriority());
|
|
}
|
|
|
|
// Format:
|
|
// [id - id] Name Left-Files/Paused Total Left Time
|
|
// [1-2] Nzb-name 999/999 999.99 MB 999.99 MB 00:00:00
|
|
|
|
int nameLen = 0;
|
|
if (calcColWidth)
|
|
{
|
|
nameLen = m_screenWidth - 1 - 9 - 11 - 11 - 9;
|
|
}
|
|
else
|
|
{
|
|
nameLen = m_screenWidth - 1 - m_colWidthFiles - 2 - m_colWidthTotal - 2 - m_colWidthLeft - 2 - 9;
|
|
}
|
|
|
|
BString<1024> buffer;
|
|
|
|
bool printFormatted = nameLen > 20;
|
|
|
|
if (printFormatted)
|
|
{
|
|
BString<100> files("%i/%i", (int)nzbInfo->GetFileList()->size(), nzbInfo->GetPausedFileCount());
|
|
BString<1024> nameWithIds("%c%i%c%s%s %s", chBrace1, nzbInfo->GetId(), chBrace2,
|
|
*priority, downloading, nzbInfo->GetName());
|
|
|
|
int64 unpausedRemainingSize = nzbInfo->GetRemainingSize() - nzbInfo->GetPausedSize();
|
|
CString remaining = Util::FormatSize(unpausedRemainingSize);
|
|
CString total = Util::FormatSize(nzbInfo->GetSize());
|
|
|
|
BString<100> time;
|
|
int currentDownloadSpeed = m_standBy ? 0 : m_currentDownloadSpeed;
|
|
if (nzbInfo->GetPausedSize() > 0 && unpausedRemainingSize == 0)
|
|
{
|
|
time = "[paused]";
|
|
remaining = Util::FormatSize(nzbInfo->GetRemainingSize());
|
|
}
|
|
else if (currentDownloadSpeed > 0 && !m_pauseDownload)
|
|
{
|
|
int64 remain_sec = (int64)(unpausedRemainingSize / currentDownloadSpeed);
|
|
int h = (int)(remain_sec / 3600);
|
|
int m = (int)((remain_sec % 3600) / 60);
|
|
int s = (int)(remain_sec % 60);
|
|
if (h < 100)
|
|
{
|
|
time.Format("%.2d:%.2d:%.2d", h, m, s);
|
|
}
|
|
else
|
|
{
|
|
time.Format("99:99:99");
|
|
}
|
|
}
|
|
|
|
if (calcColWidth)
|
|
{
|
|
int colWidthFiles = strlen(files);
|
|
m_colWidthFiles = colWidthFiles > m_colWidthFiles ? colWidthFiles : m_colWidthFiles;
|
|
|
|
int colWidthTotal = strlen(total);
|
|
m_colWidthTotal = colWidthTotal > m_colWidthTotal ? colWidthTotal : m_colWidthTotal;
|
|
|
|
int colWidthLeft = strlen(remaining);
|
|
m_colWidthLeft = colWidthLeft > m_colWidthLeft ? colWidthLeft : m_colWidthLeft;
|
|
}
|
|
else
|
|
{
|
|
buffer.Format("%-*s %*s %*s %*s %8s", nameLen, *nameWithIds,
|
|
m_colWidthFiles, *files, m_colWidthTotal, *total, m_colWidthLeft, *remaining, *time);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buffer.Format("%c%i%c%s %s", chBrace1, nzbInfo->GetId(),
|
|
chBrace2, downloading, nzbInfo->GetName());
|
|
}
|
|
|
|
if (!calcColWidth)
|
|
{
|
|
PlotLine(buffer, row, 0, color);
|
|
}
|
|
}
|
|
|
|
bool NCursesFrontend::EditQueue(DownloadQueue::EEditAction action, int offset)
|
|
{
|
|
int ID = 0;
|
|
|
|
if (m_groupFiles)
|
|
{
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
if (m_selectedQueueEntry >= 0 && m_selectedQueueEntry < (int)downloadQueue->GetQueue()->size())
|
|
{
|
|
std::unique_ptr<NzbInfo>& nzbInfo = downloadQueue->GetQueue()->at(m_selectedQueueEntry);
|
|
ID = nzbInfo->GetId();
|
|
if (action == DownloadQueue::eaFilePause)
|
|
{
|
|
if (nzbInfo->GetRemainingSize() == nzbInfo->GetPausedSize())
|
|
{
|
|
action = DownloadQueue::eaFileResume;
|
|
}
|
|
else if (nzbInfo->GetPausedSize() == 0 && (nzbInfo->GetRemainingParCount() > 0) &&
|
|
!(m_lastPausePars && m_lastEditEntry == m_selectedQueueEntry))
|
|
{
|
|
action = DownloadQueue::eaFilePauseExtraPars;
|
|
m_lastPausePars = true;
|
|
}
|
|
else
|
|
{
|
|
action = DownloadQueue::eaFilePause;
|
|
m_lastPausePars = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// map file-edit-actions to group-edit-actions
|
|
DownloadQueue::EEditAction FileToGroupMap[] = {
|
|
(DownloadQueue::EEditAction)0,
|
|
DownloadQueue::eaGroupMoveOffset,
|
|
DownloadQueue::eaGroupMoveTop,
|
|
DownloadQueue::eaGroupMoveBottom,
|
|
DownloadQueue::eaGroupPause,
|
|
DownloadQueue::eaGroupResume,
|
|
DownloadQueue::eaGroupDelete,
|
|
DownloadQueue::eaGroupPauseAllPars,
|
|
DownloadQueue::eaGroupPauseExtraPars };
|
|
action = FileToGroupMap[action];
|
|
}
|
|
else
|
|
{
|
|
int fileNum = 0;
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
for (FileInfo* fileInfo : nzbInfo->GetFileList())
|
|
{
|
|
if (m_selectedQueueEntry == fileNum)
|
|
{
|
|
ID = fileInfo->GetId();
|
|
if (action == DownloadQueue::eaFilePause)
|
|
{
|
|
action = !fileInfo->GetPaused() ? DownloadQueue::eaFilePause : DownloadQueue::eaFileResume;
|
|
}
|
|
}
|
|
fileNum++;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_lastEditEntry = m_selectedQueueEntry;
|
|
|
|
NeedUpdateData();
|
|
|
|
if (ID != 0)
|
|
{
|
|
return ServerEditQueue(action, offset, ID);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void NCursesFrontend::SetCurrentQueueEntry(int entry)
|
|
{
|
|
int queueSize = CalcQueueSize();
|
|
|
|
if (entry < 0)
|
|
{
|
|
entry = 0;
|
|
}
|
|
else if (entry > queueSize - 1)
|
|
{
|
|
entry = queueSize - 1;
|
|
}
|
|
|
|
if (entry > m_queueScrollOffset + m_queueWinClientHeight ||
|
|
entry < m_queueScrollOffset - m_queueWinClientHeight)
|
|
{
|
|
m_queueScrollOffset = entry - m_queueWinClientHeight / 2;
|
|
}
|
|
else if (entry < m_queueScrollOffset)
|
|
{
|
|
m_queueScrollOffset -= m_queueWinClientHeight;
|
|
}
|
|
else if (entry >= m_queueScrollOffset + m_queueWinClientHeight)
|
|
{
|
|
m_queueScrollOffset += m_queueWinClientHeight;
|
|
}
|
|
|
|
if (m_queueScrollOffset > queueSize - m_queueWinClientHeight)
|
|
{
|
|
m_queueScrollOffset = queueSize - m_queueWinClientHeight;
|
|
}
|
|
if (m_queueScrollOffset < 0)
|
|
{
|
|
m_queueScrollOffset = 0;
|
|
}
|
|
|
|
m_selectedQueueEntry = entry;
|
|
}
|
|
|
|
|
|
/*
|
|
* Process keystrokes starting with the initialKey, which must not be
|
|
* READKEY_EMPTY but has alread been set via ReadConsoleKey.
|
|
*/
|
|
void NCursesFrontend::UpdateInput(int initialKey)
|
|
{
|
|
int key = initialKey;
|
|
while (key != READKEY_EMPTY)
|
|
{
|
|
int queueSize = CalcQueueSize();
|
|
|
|
// Normal or edit queue mode
|
|
if (m_inputMode == normal || m_inputMode == editQueue)
|
|
{
|
|
switch (key)
|
|
{
|
|
case 'q':
|
|
// Key 'q' for quit
|
|
ExitProc();
|
|
break;
|
|
case 'z':
|
|
// show/hide NZBFilename
|
|
m_showNzbname = !m_showNzbname;
|
|
break;
|
|
case 'w':
|
|
// swicth window sizes
|
|
if (m_queueWindowPercentage == 50)
|
|
{
|
|
m_queueWindowPercentage = 100;
|
|
}
|
|
else if (m_queueWindowPercentage == 100 && m_inputMode != editQueue)
|
|
{
|
|
m_queueWindowPercentage = 0;
|
|
}
|
|
else
|
|
{
|
|
m_queueWindowPercentage = 50;
|
|
}
|
|
CalcWindowSizes();
|
|
SetCurrentQueueEntry(m_selectedQueueEntry);
|
|
break;
|
|
case 'g':
|
|
// group/ungroup files
|
|
m_groupFiles = !m_groupFiles;
|
|
SetCurrentQueueEntry(m_selectedQueueEntry);
|
|
NeedUpdateData();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Normal mode
|
|
if (m_inputMode == normal)
|
|
{
|
|
switch (key)
|
|
{
|
|
case 'p':
|
|
// Key 'p' for pause
|
|
if (!IsRemoteMode())
|
|
{
|
|
info(m_pauseDownload ? "Unpausing download" : "Pausing download");
|
|
}
|
|
ServerPauseUnpause(!m_pauseDownload);
|
|
break;
|
|
case 'e':
|
|
case 10: // return
|
|
case 13: // enter
|
|
if (queueSize > 0)
|
|
{
|
|
m_inputMode = editQueue;
|
|
if (m_queueWindowPercentage == 0)
|
|
{
|
|
m_queueWindowPercentage = 50;
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
case 'r':
|
|
// Download rate
|
|
m_inputMode = downloadRate;
|
|
m_inputNumberIndex = 0;
|
|
m_inputValue = 0;
|
|
return;
|
|
case 't':
|
|
// show/hide Timestamps
|
|
m_showTimestamp = !m_showTimestamp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Edit Queue mode
|
|
if (m_inputMode == editQueue)
|
|
{
|
|
switch (key)
|
|
{
|
|
case 'e':
|
|
case 10: // return
|
|
case 13: // enter
|
|
m_inputMode = normal;
|
|
return;
|
|
case KEY_DOWN:
|
|
if (m_selectedQueueEntry < queueSize - 1)
|
|
{
|
|
SetCurrentQueueEntry(m_selectedQueueEntry + 1);
|
|
}
|
|
break;
|
|
case KEY_UP:
|
|
if (m_selectedQueueEntry > 0)
|
|
{
|
|
SetCurrentQueueEntry(m_selectedQueueEntry - 1);
|
|
}
|
|
break;
|
|
case KEY_PPAGE:
|
|
if (m_selectedQueueEntry > 0)
|
|
{
|
|
if (m_selectedQueueEntry == m_queueScrollOffset)
|
|
{
|
|
m_queueScrollOffset -= m_queueWinClientHeight;
|
|
SetCurrentQueueEntry(m_selectedQueueEntry - m_queueWinClientHeight);
|
|
}
|
|
else
|
|
{
|
|
SetCurrentQueueEntry(m_queueScrollOffset);
|
|
}
|
|
}
|
|
break;
|
|
case KEY_NPAGE:
|
|
if (m_selectedQueueEntry < queueSize - 1)
|
|
{
|
|
if (m_selectedQueueEntry == m_queueScrollOffset + m_queueWinClientHeight - 1)
|
|
{
|
|
m_queueScrollOffset += m_queueWinClientHeight;
|
|
SetCurrentQueueEntry(m_selectedQueueEntry + m_queueWinClientHeight);
|
|
}
|
|
else
|
|
{
|
|
SetCurrentQueueEntry(m_queueScrollOffset + m_queueWinClientHeight - 1);
|
|
}
|
|
}
|
|
break;
|
|
case KEY_HOME:
|
|
SetCurrentQueueEntry(0);
|
|
break;
|
|
case KEY_END:
|
|
SetCurrentQueueEntry(queueSize > 0 ? queueSize - 1 : 0);
|
|
break;
|
|
case 'p':
|
|
// Key 'p' for pause
|
|
EditQueue(DownloadQueue::eaFilePause, 0);
|
|
break;
|
|
case 'd':
|
|
SetHint(" Use Uppercase \"D\" for delete");
|
|
break;
|
|
case 'D':
|
|
// Delete entry
|
|
if (EditQueue(DownloadQueue::eaFileDelete, 0))
|
|
{
|
|
SetCurrentQueueEntry(m_selectedQueueEntry);
|
|
}
|
|
break;
|
|
case 'u':
|
|
if (EditQueue(DownloadQueue::eaFileMoveOffset, -1))
|
|
{
|
|
SetCurrentQueueEntry(m_selectedQueueEntry - 1);
|
|
}
|
|
break;
|
|
case 'n':
|
|
if (EditQueue(DownloadQueue::eaFileMoveOffset, +1))
|
|
{
|
|
SetCurrentQueueEntry(m_selectedQueueEntry + 1);
|
|
}
|
|
break;
|
|
case 't':
|
|
if (EditQueue(DownloadQueue::eaFileMoveTop, 0))
|
|
{
|
|
SetCurrentQueueEntry(0);
|
|
}
|
|
break;
|
|
case 'b':
|
|
if (EditQueue(DownloadQueue::eaFileMoveBottom, 0))
|
|
{
|
|
SetCurrentQueueEntry(queueSize > 0 ? queueSize - 1 : 0);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Edit download rate input mode
|
|
if (m_inputMode == downloadRate)
|
|
{
|
|
// Numbers
|
|
if (m_inputNumberIndex < 5 && key >= '0' && key <= '9')
|
|
{
|
|
m_inputValue = (m_inputValue * 10) + (key - '0');
|
|
m_inputNumberIndex++;
|
|
}
|
|
// Enter
|
|
else if (key == 10 || key == 13)
|
|
{
|
|
ServerSetDownloadRate(m_inputValue * 1024);
|
|
m_inputMode = normal;
|
|
return;
|
|
}
|
|
// Escape
|
|
else if (key == 27)
|
|
{
|
|
m_inputMode = normal;
|
|
return;
|
|
}
|
|
// Backspace
|
|
else if (m_inputNumberIndex > 0 && key == KEY_BACKSPACE)
|
|
{
|
|
int remain = m_inputValue % 10;
|
|
|
|
m_inputValue = (m_inputValue - remain) / 10;
|
|
m_inputNumberIndex--;
|
|
}
|
|
}
|
|
|
|
key = ReadConsoleKey();
|
|
}
|
|
}
|
|
|
|
int NCursesFrontend::ReadConsoleKey()
|
|
{
|
|
#ifdef WIN32
|
|
HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE);
|
|
DWORD NumberOfEvents;
|
|
BOOL ok = GetNumberOfConsoleInputEvents(hConsole, &NumberOfEvents);
|
|
if (ok && NumberOfEvents > 0)
|
|
{
|
|
while (NumberOfEvents--)
|
|
{
|
|
INPUT_RECORD InputRecord;
|
|
DWORD NumberOfEventsRead;
|
|
if (ReadConsoleInput(hConsole, &InputRecord, 1, &NumberOfEventsRead) &&
|
|
NumberOfEventsRead > 0 &&
|
|
InputRecord.EventType == KEY_EVENT &&
|
|
InputRecord.Event.KeyEvent.bKeyDown)
|
|
{
|
|
char c = tolower(InputRecord.Event.KeyEvent.wVirtualKeyCode);
|
|
if (bool(InputRecord.Event.KeyEvent.dwControlKeyState & CAPSLOCK_ON) ^
|
|
bool(InputRecord.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED))
|
|
{
|
|
c = toupper(c);
|
|
}
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
return READKEY_EMPTY;
|
|
#else
|
|
return getch();
|
|
#endif
|
|
}
|
|
|
|
#endif
|