mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-22 20:11:44 +00:00
00885ffd27
Add support for DP_SV_CLIENTCAMERA (primarily to work around an rmq bug). Now pointing the engine at the shadowy updates.triptohell.info domain (with a self-signed cert), so that we can give fte.triptohell.info a proper cert that browsers will trust. Still need to make a stable release some time before that can happen. Make sure q2 gamecode updates can work, so win64/linux64 can actually run q2 now (using yamagi's). git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5467 fc73d0e0-1445-4013-8a0c-d673dee63da5
2153 lines
No EOL
60 KiB
C++
2153 lines
No EOL
60 KiB
C++
/*Copyright Spoike, license is GPLv2+ */
|
|
|
|
/*Todo (in no particular order):
|
|
tooltips for inspecting variables/types.
|
|
variables/watch list.
|
|
calltips for argument info
|
|
autocompletion calltips, for people who can't remember function names
|
|
initial open project prompt
|
|
shpuld's styling
|
|
decompiler output saving
|
|
right-click popup
|
|
goto-def
|
|
grep-for
|
|
toggle-breakpoint
|
|
set-next
|
|
bracket/brace highlights
|
|
autoindentation on enter, etc
|
|
different displays for non-text files?
|
|
utf-16? mneh, who gives a shit
|
|
give focus back to the engine on resume
|
|
*/
|
|
|
|
|
|
#ifdef __PIC__
|
|
#undef __PIE__ //QT is being annoying.
|
|
#endif
|
|
#include <QtWidgets>
|
|
#include <Qsci/qsciscintilla.h>
|
|
#include <Qsci/qscilexercpp.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
|
|
extern "C"
|
|
{
|
|
#include "qcc.h"
|
|
#include "gui.h"
|
|
|
|
extern pbool fl_nondfltopts;
|
|
extern pbool fl_hexen2;
|
|
extern pbool fl_ftetarg;
|
|
extern pbool fl_compileonstart;
|
|
extern pbool fl_showall;
|
|
extern pbool fl_log;
|
|
extern pbool fl_extramargins;
|
|
extern int fl_tabsize;
|
|
|
|
extern char enginebinary[MAX_OSPATH];
|
|
extern char enginebasedir[MAX_OSPATH];
|
|
extern char enginecommandline[8192];
|
|
};
|
|
static char *cmdlineargs;
|
|
|
|
#undef NULL
|
|
#define NULL nullptr
|
|
|
|
#undef Sys_Error
|
|
|
|
//c++ sucks and just pisses me off with its lack of support for void*
|
|
template<class T> inline T cpprealloc(T p, size_t s) {return static_cast<T>(realloc(static_cast<void*>(p),s));};
|
|
#define STRINGIFY2(s) #s
|
|
#define STRINGIFY(s) STRINGIFY2(s)
|
|
|
|
|
|
static void DebuggerStop(void);
|
|
static bool DebuggerSendCommand(const char *msg, ...);
|
|
static void DebuggerStart(void);
|
|
|
|
|
|
void Sys_Error(const char *text, ...)
|
|
{
|
|
va_list argptr;
|
|
static char msg[2048];
|
|
|
|
va_start (argptr,text);
|
|
QC_vsnprintf (msg,sizeof(msg)-1, text,argptr);
|
|
va_end (argptr);
|
|
|
|
QCC_Error(ERR_INTERNAL, "%s", msg);
|
|
}
|
|
|
|
void RunCompiler(const char *args, pbool quick);
|
|
static void *QCC_ReadFile(const char *fname, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size)
|
|
{
|
|
size_t len;
|
|
unsigned char *buffer;
|
|
|
|
vfile_t *v = QCC_FindVFile(fname);
|
|
if (v)
|
|
{
|
|
len = v->size;
|
|
if (buf_get)
|
|
buffer = buf_get(buf_ctx, len+1);
|
|
else
|
|
buffer = static_cast<unsigned char *>(malloc(len+1));
|
|
if (!buffer)
|
|
return NULL;
|
|
buffer[len] = 0;
|
|
if (len > v->size)
|
|
len = v->size;
|
|
memcpy(buffer, v->file, len);
|
|
if (out_size)
|
|
*out_size = len;
|
|
return buffer;
|
|
}
|
|
|
|
auto f = fopen(fname, "rb");
|
|
if (!f)
|
|
{
|
|
if (out_size)
|
|
*out_size = 0;
|
|
return nullptr;
|
|
}
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
len = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
if (buf_get)
|
|
buffer = buf_get(buf_ctx, len+1);
|
|
else
|
|
buffer = static_cast<unsigned char *>(malloc(len+1));
|
|
buffer[len] = 0;
|
|
if (len != fread(buffer, 1, len, f))
|
|
{
|
|
if (!buf_get)
|
|
free(buffer);
|
|
buffer = nullptr;
|
|
}
|
|
fclose(f);
|
|
|
|
if (out_size)
|
|
*out_size = len;
|
|
return buffer;
|
|
}
|
|
static int PDECL QCC_FileSize (const char *fname)
|
|
{
|
|
vfile_t *v = QCC_FindVFile(fname);
|
|
if (v)
|
|
return v->size;
|
|
|
|
long length;
|
|
auto f = fopen(fname, "rb");
|
|
if (!f)
|
|
return -1;
|
|
fseek(f, 0, SEEK_END);
|
|
length = ftell(f);
|
|
fclose(f);
|
|
return length;
|
|
}
|
|
static int PDECL QCC_PopFileSize (const char *fname)
|
|
{ //populates the file list, as well as returning the size
|
|
extern int qcc_compileactive;
|
|
int len = QCC_FileSize(fname);
|
|
if (len >= 0 && qcc_compileactive)
|
|
{
|
|
AddSourceFile(compilingrootfile, fname);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static int PDECL QCC_StatFile (const char *fname, struct stat *sbuf)
|
|
{
|
|
vfile_t *v = QCC_FindVFile(fname);
|
|
if (v)
|
|
{
|
|
memset(sbuf, 0, sizeof(*sbuf));
|
|
sbuf->st_size = v->size;
|
|
return 0;
|
|
}
|
|
return stat(fname, sbuf);
|
|
}
|
|
|
|
pbool PDECL QCC_WriteFile (const char *name, void *data, int len)
|
|
{
|
|
long length;
|
|
FILE *f;
|
|
|
|
auto *ext = strrchr(name, '.');
|
|
if (ext && !stricmp(ext, ".gz"))
|
|
{
|
|
#ifdef AVAIL_ZLIB
|
|
pbool okay = true;
|
|
char out[1024*8];
|
|
|
|
z_stream strm = {
|
|
data,
|
|
len,
|
|
0,
|
|
|
|
out,
|
|
sizeof(out),
|
|
0,
|
|
|
|
NULL,
|
|
NULL,
|
|
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
|
|
Z_BINARY,
|
|
0,
|
|
0
|
|
};
|
|
|
|
f = fopen(name, "wb");
|
|
if (!f)
|
|
return false;
|
|
deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, MAX_WBITS|16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
|
|
while(okay && deflate(&strm, Z_FINISH) == Z_OK)
|
|
{
|
|
if (sizeof(out) - strm.avail_out != fwrite(out, 1, sizeof(out) - strm.avail_out, f))
|
|
okay = false;
|
|
strm.next_out = out;
|
|
strm.avail_out = sizeof(out);
|
|
}
|
|
if (sizeof(out) - strm.avail_out != fwrite(out, 1, sizeof(out) - strm.avail_out, f))
|
|
okay = false;
|
|
deflateEnd(&strm);
|
|
fclose(f);
|
|
if (!okay)
|
|
unlink(name);
|
|
return okay;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
if (QCC_FindVFile(name))
|
|
return !!QCC_AddVFile(name, data, len);
|
|
|
|
f = fopen(name, "wb");
|
|
if (!f)
|
|
return false;
|
|
length = fwrite(data, 1, len, f);
|
|
fclose(f);
|
|
|
|
if (length != len)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//for the project's treeview to work, we need a subclass to provide the info to be displayed
|
|
class filelist : public QAbstractItemModel
|
|
{
|
|
public:
|
|
struct filenode_s
|
|
{
|
|
filenode_s *parent = nullptr;
|
|
int numchildren;
|
|
filenode_s **children;
|
|
|
|
char *name;
|
|
|
|
~filenode_s()
|
|
{
|
|
while(numchildren)
|
|
{
|
|
delete(children[--numchildren]);
|
|
}
|
|
free(children);
|
|
free(name);
|
|
}
|
|
} *root;
|
|
|
|
filenode_s *getItem(const QModelIndex &idx) const
|
|
{
|
|
if (idx.isValid())
|
|
return static_cast<filenode_s*>(idx.internalPointer());
|
|
return root;
|
|
}
|
|
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
|
|
{
|
|
const filenode_s *n = getItem(parent);
|
|
return n->numchildren;
|
|
}
|
|
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const
|
|
{
|
|
return 1;
|
|
}
|
|
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
|
|
{
|
|
const filenode_s *n = getItem(index);
|
|
switch(role)
|
|
{
|
|
case Qt::DisplayRole:
|
|
return QVariant(n->name);
|
|
}
|
|
return QVariant();
|
|
}
|
|
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
filenode_s *n;
|
|
if (column)
|
|
return QModelIndex();
|
|
n = getItem(parent);
|
|
if (row >= 0 && row < n->numchildren)
|
|
return createIndex(row, column, n->children[row]);
|
|
return QModelIndex();
|
|
}
|
|
virtual QModelIndex parent(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid())
|
|
return QModelIndex();
|
|
|
|
filenode_s *n = getItem(index);
|
|
filenode_s *p = n->parent;
|
|
|
|
if (p == root)
|
|
return QModelIndex();
|
|
else
|
|
{
|
|
int parentrow = 0;
|
|
if (n->parent)
|
|
while (n != n->parent->children[parentrow])
|
|
parentrow++;
|
|
return createIndex(parentrow, 0, p);
|
|
}
|
|
}
|
|
|
|
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
|
|
{
|
|
switch(role)
|
|
{
|
|
case Qt::DisplayRole:
|
|
return QVariant("Files");
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
public:
|
|
filelist()
|
|
{
|
|
root = new filenode_s();
|
|
}
|
|
|
|
void GrepAll(const char *search, filenode_s *n = nullptr)
|
|
{
|
|
if (!n)
|
|
{
|
|
GUIprintf("");
|
|
GUIprintf("Grep for %s\n", search);
|
|
n = root;
|
|
}
|
|
|
|
Grep(n->name, search);
|
|
for (int i = 0; i < n->numchildren; i++)
|
|
{
|
|
auto c = n->children[i];
|
|
GrepAll(search, c);
|
|
}
|
|
}
|
|
|
|
filenode_s *FindChild(filenode_s *p, const char *filename)
|
|
{
|
|
for (int i = 0; i < p->numchildren; i++)
|
|
{
|
|
auto c = p->children[i];
|
|
if (!strcasecmp(c->name, filename))
|
|
return c;
|
|
}
|
|
return nullptr;
|
|
}
|
|
void AddFile(const char *parentpath, const char *filename)
|
|
{
|
|
filenode_s *p = root, *c;
|
|
|
|
if (!filename)
|
|
{
|
|
delete root;
|
|
root = new filenode_s();
|
|
return;
|
|
}
|
|
|
|
while (parentpath && *parentpath)
|
|
{
|
|
auto sl = strchr(parentpath, '/');
|
|
|
|
c = FindChild(p, parentpath);
|
|
if (!c)
|
|
break;
|
|
p = c;
|
|
|
|
if (sl)
|
|
sl++;
|
|
parentpath = sl;
|
|
}
|
|
|
|
while(!strncmp(filename, "./", 2))
|
|
filename+=2;
|
|
|
|
if (p->parent && FindChild(p->parent, filename) == p)
|
|
return; //matches its parent. probably the .src file itself
|
|
c = FindChild(p, filename);
|
|
if (c)
|
|
return; //already in there.
|
|
|
|
beginResetModel();
|
|
c = new filenode_s();
|
|
c->name = strdup(filename);
|
|
c->parent = p;
|
|
|
|
p->children = cpprealloc(p->children, sizeof(*p->children)*(p->numchildren+1));
|
|
p->children[p->numchildren] = c;
|
|
p->numchildren++;
|
|
endResetModel();
|
|
}
|
|
};
|
|
|
|
class documentlist : public QAbstractListModel
|
|
{
|
|
QsciScintilla *s; //this is the widget that we load our documents into
|
|
|
|
int numdocuments;
|
|
struct document_s
|
|
{ //these are swapped in/out of the scintilla widget
|
|
const char *fname;
|
|
const char *shortname;
|
|
time_t filemodifiedtime;
|
|
bool modified;
|
|
int cursorline;
|
|
int savefmt; //encoding to save as
|
|
QsciDocument doc;
|
|
QsciLexer *l;
|
|
} **docs, *curdoc = nullptr;
|
|
|
|
class docstacklock
|
|
{
|
|
struct document_s *oldval;
|
|
documentlist &dl;
|
|
public:
|
|
docstacklock(documentlist *ptr_, struct document_s *newval) : dl(*ptr_)
|
|
{ //pick new stuff
|
|
oldval = dl.curdoc;
|
|
dl.curdoc = newval;
|
|
dl.s->setDocument(dl.curdoc->doc);
|
|
}
|
|
~docstacklock()
|
|
{ //restore state to how it used to be
|
|
if (!oldval)
|
|
return;
|
|
dl.curdoc = oldval;
|
|
dl.s->setDocument(dl.curdoc->doc);
|
|
}
|
|
};
|
|
|
|
document_s *getItem(const QModelIndex &idx) const
|
|
{
|
|
if (idx.isValid())
|
|
return docs[idx.row()];
|
|
return nullptr;
|
|
}
|
|
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
|
|
{
|
|
return numdocuments;
|
|
}
|
|
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
|
|
{
|
|
const document_s *n = getItem(index);
|
|
if(n)
|
|
switch(role)
|
|
{
|
|
case Qt::DisplayRole:
|
|
if (n->modified)
|
|
return QVariant(QString::asprintf("%s*", n->fname));
|
|
else
|
|
return QVariant(n->fname);
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
|
|
{
|
|
switch(role)
|
|
{
|
|
case Qt::DisplayRole:
|
|
return QVariant("Files");
|
|
}
|
|
return QVariant();
|
|
}
|
|
void UpdateTitle(void);
|
|
public:
|
|
documentlist(QsciScintilla *editor)
|
|
{
|
|
s = editor;
|
|
numdocuments = 0;
|
|
docs = nullptr;
|
|
|
|
connect(s, &QsciScintilla::cursorPositionChanged,
|
|
[=](int line, int index)
|
|
{
|
|
if (curdoc)
|
|
{
|
|
curdoc->cursorline = line+1;
|
|
UpdateTitle();
|
|
}
|
|
});
|
|
|
|
connect(s, &QsciScintilla::modificationChanged,
|
|
[=](bool m)
|
|
{
|
|
if (curdoc)
|
|
{
|
|
curdoc->modified = m;
|
|
|
|
for(int row = 0; row < numdocuments; row++)
|
|
{
|
|
if(docs[row] == curdoc)
|
|
{
|
|
auto i = index(row);
|
|
this->dataChanged(i, i);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void SetupScintilla(document_s *ed)
|
|
{
|
|
// ed->l = new QsciLexerCPP (s);
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLERESETDEFAULT);
|
|
// s->SendScintilla(QsciScintillaBase::SCI_STYLESETFONT, QsciScintillaBase::STYLE_DEFAULT, "Consolas");
|
|
s->setFont(QFont(QString("Consolas"), 8));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLECLEARALL);
|
|
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETCODEPAGE, QsciScintillaBase::SC_CP_UTF8);
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETLEXER, QsciScintillaBase::SCLEX_CPP);
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Default, QColor(0x00, 0x00, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLECLEARALL);
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Comment, QColor(0x00, 0x80, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentLine, QColor(0x00, 0x80, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentDoc, QColor(0x00, 0x80, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Number, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Keyword, QColor(0x00, 0x00, 0xFF));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::DoubleQuotedString, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::SingleQuotedString, QColor(0xA0, 0x10, 0x10));
|
|
// s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::UUID, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::PreProcessor, QColor(0x00, 0x00, 0xFF));
|
|
// s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Operator, QColor(0x00, 0x00, 0x00));
|
|
// s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Identifier, QColor(0x00, 0x00, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::UnclosedString, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::VerbatimString, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Regex, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentLineDoc, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::KeywordSet2, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentDocKeyword, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentDocKeywordError, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::GlobalClass, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::RawString, QColor(0xA0, 0x00, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::TripleQuotedVerbatimString, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::HashQuotedString, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::PreProcessorComment, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::PreProcessorCommentLineDoc, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::UserLiteral, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::TaskMarker, QColor(0xA0, 0x10, 0x10));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::EscapeSequence, QColor(0xA0, 0x10, 0x10));
|
|
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciScintillaBase::STYLE_BRACELIGHT, QColor(0x00, 0x00, 0x3F));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETBACK, QsciScintillaBase::STYLE_BRACELIGHT, QColor(0xef, 0xaf, 0xaf));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETBOLD, QsciScintillaBase::STYLE_BRACELIGHT, true);
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciScintillaBase::STYLE_BRACEBAD, QColor(0x3F, 0x00, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_STYLESETBACK, QsciScintillaBase::STYLE_BRACEBAD, QColor(0xff, 0xaf, 0xaf));
|
|
|
|
//SCE_C_WORD
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 0ul,
|
|
"if else for do not while asm break case const continue "
|
|
"default enum enumflags extern "
|
|
"float goto __in __out __inout noref "
|
|
"nosave shared __state optional string "
|
|
"struct switch thinktime until loop "
|
|
"typedef union var "
|
|
"accessor get set inline "
|
|
"virtual nonvirtual class static nonstatic local return "
|
|
"string float vector void int integer __variant entity"
|
|
);
|
|
|
|
//SCE_C_WORD2
|
|
{
|
|
char buffer[65536];
|
|
GenBuiltinsList(buffer, sizeof(buffer));
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 1ul, buffer);
|
|
}
|
|
//SCE_C_COMMENTDOCKEYWORDERROR
|
|
//SCE_C_GLOBALCLASS
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 3ul,
|
|
""
|
|
);
|
|
//preprocessor listing
|
|
{
|
|
char *deflist = QCC_PR_GetDefinesList();
|
|
if (!deflist)
|
|
deflist = strdup("");
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 4ul, deflist);
|
|
free(deflist);
|
|
}
|
|
//task markers (in comments only)
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 5ul,
|
|
"TODO FIXME BUG"
|
|
);
|
|
|
|
s->SendScintilla(QsciScintillaBase::SCI_USEPOPUP, QsciScintillaBase::SC_POPUP_NEVER); //so we can do right-click menus ourselves.
|
|
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETMOUSEDWELLTIME, 1000);
|
|
s->SendScintilla(QsciScintillaBase::SCI_AUTOCSETORDER, QsciScintillaBase::SC_ORDER_PERFORMSORT);
|
|
s->SendScintilla(QsciScintillaBase::SCI_AUTOCSETFILLUPS, nullptr, ".,[<>(*/+-=\t\n");
|
|
|
|
//Set up gui options.
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETMARGINWIDTHN, 0, fl_extramargins?40:0); //line numbers+folding
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETTABWIDTH, fl_tabsize); //tab size
|
|
|
|
//add margin for breakpoints
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETMARGINMASKN, 1, ~QsciScintillaBase::SC_MASK_FOLDERS);
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETMARGINWIDTHN, 1, 16);
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETMARGINSENSITIVEN, 1, true);
|
|
//give breakpoints a nice red circle.
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, 0, QsciScintillaBase::SC_MARK_CIRCLE);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, 0, QColor(0x7F, 0x00, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, 0, QColor(0xFF, 0x00, 0x00));
|
|
//give current line a yellow arrow
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, 1, QsciScintillaBase::SC_MARK_SHORTARROW);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, 1, QColor(0xFF, 0xFF, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, 1, QColor(0x7F, 0x7F, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, 2, QsciScintillaBase::SC_MARK_BACKGROUND);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, 2, QColor(0x00, 0x00, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, 2, QColor(0xFF, 0xFF, 0x00));
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETALPHA, 2, 0x40);
|
|
|
|
//add margin for folding
|
|
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETPROPERTY, "fold", "1");
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETMARGINWIDTHN, 2, fl_extramargins?16:0);
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETMARGINMASKN, 2, QsciScintillaBase::SC_MASK_FOLDERS);
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETMARGINSENSITIVEN, 2, true);
|
|
//stop the images from being stupid
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDEROPEN, QsciScintillaBase::SC_MARK_BOXMINUS);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDER, QsciScintillaBase::SC_MARK_BOXPLUS);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDERSUB, QsciScintillaBase::SC_MARK_VLINE);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDERTAIL, QsciScintillaBase::SC_MARK_LCORNERCURVE);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDEREND, QsciScintillaBase::SC_MARK_BOXPLUSCONNECTED);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDEROPENMID, QsciScintillaBase::SC_MARK_BOXMINUSCONNECTED);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDERMIDTAIL, QsciScintillaBase::SC_MARK_TCORNERCURVE);
|
|
//and fuck with colours so that its visible.
|
|
#define FOLDBACK QColor(0x50, 0x50, 0x50)
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, QsciScintillaBase::SC_MARKNUM_FOLDER, QColor(0xFF, 0xFF, 0xFF));
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDER, FOLDBACK);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, QsciScintillaBase::SC_MARKNUM_FOLDEROPEN, QColor(0xFF, 0xFF, 0xFF));
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDEROPEN, FOLDBACK);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, QsciScintillaBase::SC_MARKNUM_FOLDEROPENMID, QColor(0xFF, 0xFF, 0xFF));
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDEROPENMID, FOLDBACK);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDERSUB, FOLDBACK);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, QsciScintillaBase::SC_MARKNUM_FOLDEREND, QColor(0xFF, 0xFF, 0xFF));
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDEREND, FOLDBACK);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDERTAIL, FOLDBACK);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDERMIDTAIL, FOLDBACK);
|
|
|
|
//disable preprocessor tracking, because QC preprocessor is not specific to an individual file, and even if it was, includes would be messy.
|
|
// s->SendScintilla(QsciScintillaBase::SCI_SETPROPERTY, (WPARAM)"lexer.cpp.track.preprocessor", (LPARAM)"0");
|
|
|
|
for (int i = 0; i < 0x100; i++)
|
|
{
|
|
const char *lowtab[32] = {"QNUL",NULL,NULL,NULL,NULL,".",NULL,NULL,NULL,NULL,NULL,"#",NULL,">",".",".",
|
|
"[","]","0","1","2","3","4","5","6","7","8","9",".","<-","-","->"};
|
|
const char *hightab[32] = {"(=","=","=)","=#=","White",".","Green","Red","Yellow","Blue",NULL,"Purple",NULL,">",".",".",
|
|
"[","]","0","1","2","3","4","5","6","7","8","9",".","<-","-","->"};
|
|
char foo[4];
|
|
char bar[4];
|
|
unsigned char c = i&0xff;
|
|
foo[0] = i; //these are invalid encodings or control chars.
|
|
foo[1] = 0;
|
|
|
|
if (c < 32)
|
|
{
|
|
if (lowtab[c])
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, lowtab[c]);
|
|
}
|
|
else if (c >= (128|0) && c < (128|32))
|
|
{
|
|
if (hightab[c-128])
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, hightab[c-128]);
|
|
}
|
|
else if (c < 128)
|
|
continue; //don't do anything weird for ascii (other than control chars)
|
|
else
|
|
{
|
|
int b = 0;
|
|
bar[b++] = c&0x7f;
|
|
bar[b++] = 0;
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, bar);
|
|
}
|
|
}
|
|
|
|
for (int i = 0xe000; i < 0xe100; i++)
|
|
{
|
|
const char *lowtab[32] = {"QNUL",NULL,NULL,NULL,NULL,".",NULL,NULL,NULL,NULL,NULL,"#",NULL,">",".",".",
|
|
"[","]","0","1","2","3","4","5","6","7","8","9",".","<-","-","->"};
|
|
const char *hightab[32] = {"(=","=","=)","=#=","White",".","Green","Red","Yellow","Blue",NULL,"Purple",NULL,">",".",".",
|
|
"[","]","^0","^1","^2","^3","^4","^5","^6","^7","^8","^9",".","^<-","^-","^->"};
|
|
char foo[4];
|
|
char bar[4];
|
|
unsigned char c = i&0xff;
|
|
foo[0] = ((i>>12) & 0xf) | 0xe0;
|
|
foo[1] = ((i>>6) & 0x3f) | 0x80;
|
|
foo[2] = ((i>>0) & 0x3f) | 0x80;
|
|
foo[3] = 0;
|
|
|
|
if (c < 32)
|
|
{
|
|
if (lowtab[c])
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, lowtab[c]);
|
|
}
|
|
else if (c >= (128|0) && c < (128|32))
|
|
{
|
|
if (hightab[c-128])
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, hightab[c-128]);
|
|
}
|
|
else
|
|
{
|
|
int b = 0;
|
|
if (c >= 128)
|
|
bar[b++] = '^';
|
|
bar[b++] = c&0x7f;
|
|
bar[b++] = 0;
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, bar);
|
|
}
|
|
}
|
|
|
|
/* auto f = fopen("scintilla.cfg", "rt");
|
|
if (f)
|
|
{
|
|
char buf[256];
|
|
while(fgets(buf, sizeof(buf)-1, f))
|
|
{
|
|
int msg;
|
|
long lparam;
|
|
long wparam;
|
|
char *c;
|
|
buf[sizeof(buf)-1] = 0;
|
|
c = buf;
|
|
while(*c == ' ' || *c == '\t')
|
|
c++;
|
|
if (c[0] == '#')
|
|
continue;
|
|
if (c[0] == '/' && c[1] == '/')
|
|
continue;
|
|
if (c[0] == '\r' || c[0] == '\n' || !c[0])
|
|
continue;
|
|
msg = strtoul(c, &c, 0);
|
|
while(*c == ' ' || *c == '\t')
|
|
c++;
|
|
if (*c == '\"')
|
|
{
|
|
c++;
|
|
wparam = c;
|
|
c = strrchr(c, '\"');
|
|
if (c)
|
|
*c++ = 0;
|
|
}
|
|
else
|
|
wparam = strtoul(c, &c, 0);
|
|
while(*c == ' ' || *c == '\t')
|
|
c++;
|
|
if (*c == '\"')
|
|
{
|
|
c++;
|
|
lparam = c;
|
|
c = strrchr(c, '\"');
|
|
if (c)
|
|
*c++ = 0;
|
|
}
|
|
else
|
|
lparam = strtoul(c, &c, 0);
|
|
|
|
s->SendScintilla(QsciScintillaBase::msg, wparam, lparam);
|
|
}
|
|
if (!ftell(f))
|
|
{
|
|
fclose(f);
|
|
f = fopen("scintilla.cfg", "wt");
|
|
if (f)
|
|
{
|
|
int i;
|
|
int val;
|
|
for (i = 0; i < STYLE_LASTPREDEFINED; i++)
|
|
{
|
|
val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETFORE, i, 0);
|
|
fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETFORE, i, val);
|
|
val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETBACK, i, 0);
|
|
fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETBACK, i, val);
|
|
val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETBOLD, i, 0);
|
|
fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETBOLD, i, val);
|
|
val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETITALIC, i, 0);
|
|
fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETITALIC, i, val);
|
|
val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETSIZE, i, 0);
|
|
fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETSIZE, i, val);
|
|
val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETFONT, i, (LPARAM)buf);
|
|
fprintf(f, "%i\t%i\t\"%s\"\n", SCI_STYLESETFONT, i, buf);
|
|
val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETUNDERLINE, i, 0);
|
|
fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETUNDERLINE, i, val);
|
|
val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETCASE, i, 0);
|
|
fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETCASE, i, val);
|
|
}
|
|
fclose(f);
|
|
}
|
|
}
|
|
else
|
|
fclose(f);
|
|
}
|
|
*/ }
|
|
|
|
bool CreateDocument(document_s *ed)
|
|
{
|
|
size_t flensz;
|
|
auto rawfile = QCC_ReadFile(ed->fname, nullptr, nullptr, &flensz);
|
|
if (!rawfile)
|
|
return false;
|
|
auto flen = flensz;
|
|
pbool dofree;
|
|
auto file = QCC_SanitizeCharSet(static_cast<char*>(rawfile), &flen, &dofree, &ed->savefmt);
|
|
struct stat sbuf;
|
|
QCC_StatFile(ed->fname, &sbuf);
|
|
ed->filemodifiedtime = sbuf.st_mtime;
|
|
|
|
int endings = 0;
|
|
char *e, *stop;
|
|
for (e = file, stop=file+flen; e < stop; )
|
|
{
|
|
if (*e == '\r')
|
|
{
|
|
e++;
|
|
if (*e == '\n')
|
|
{
|
|
e++;
|
|
endings |= 4;
|
|
}
|
|
else
|
|
endings |= 2;
|
|
}
|
|
else if (*e == '\n')
|
|
{
|
|
e++;
|
|
endings |= 1;
|
|
}
|
|
else
|
|
e++;
|
|
}
|
|
|
|
curdoc = ed;
|
|
s->setDocument(ed->doc);
|
|
|
|
switch(endings)
|
|
{
|
|
case 0: //new file with no endings, default to windows on windows.
|
|
case 4: //windows
|
|
s->setEolMode(QsciScintilla::EolMode::EolWindows);
|
|
s->setEolVisibility(false);
|
|
break;
|
|
case 1: //unix
|
|
s->setEolMode(QsciScintilla::EolMode::EolUnix);
|
|
s->setEolVisibility(false);
|
|
break;
|
|
case 2: //mac. traditionally qccs have never supported this. one of the mission packs has a \r in the middle of some single-line comment.
|
|
s->setEolMode(QsciScintilla::EolMode::EolMac);
|
|
s->setEolVisibility(false);
|
|
break;
|
|
default: //panic! everyone panic!
|
|
s->setEolMode(QsciScintilla::EolMode::EolUnix);
|
|
s->setEolVisibility(true);
|
|
break;
|
|
}
|
|
|
|
s->setUtf8(ed->savefmt != UTF_ANSI);
|
|
s->setText(QString(file));
|
|
|
|
SetupScintilla(ed);
|
|
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETSAVEPOINT);
|
|
ed->modified = false;
|
|
return true;
|
|
}
|
|
|
|
void SwitchToDocument(document_s *ed)
|
|
{
|
|
struct stat sbuf;
|
|
QCC_StatFile(ed->fname, &sbuf);
|
|
if (ed->filemodifiedtime < sbuf.st_mtime)
|
|
{
|
|
CreateDocument(ed);
|
|
return;
|
|
}
|
|
|
|
curdoc = ed;
|
|
s->setDocument(ed->doc);
|
|
}
|
|
|
|
document_s *FindFile(const char *filename)
|
|
{
|
|
if (!filename)
|
|
return curdoc;
|
|
for (int i = 0; i < numdocuments; i++)
|
|
{
|
|
if (!strcasecmp(filename, docs[i]->fname))
|
|
return docs[i];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void *getFileData(document_s *d, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size)
|
|
{
|
|
docstacklock lock(this, d);
|
|
unsigned char *ret = NULL;
|
|
auto text = s->text().toUtf8();
|
|
*out_size = text.length();
|
|
if (buf_get)
|
|
ret = buf_get(buf_ctx, *out_size+1);
|
|
else
|
|
ret = static_cast<unsigned char*>(malloc(*out_size+1));
|
|
memcpy(ret, text.data(), *out_size);
|
|
ret[*out_size] = 0;
|
|
return ret;
|
|
}
|
|
int getFileSize(document_s *d)
|
|
{
|
|
docstacklock lock(this,d);
|
|
int ret = -1;
|
|
auto text = s->text().toUtf8();
|
|
ret = text.length();
|
|
return ret;
|
|
}
|
|
|
|
bool annotate(const char *line)
|
|
{
|
|
auto filename = line+6;
|
|
auto filenameend = strchr(filename, ':');
|
|
if (!filenameend) return false;
|
|
auto linenum = atoi(filenameend+1);
|
|
line = strchr(filenameend+1, ':')+1;
|
|
if (!line) return false;
|
|
if (strncmp(curdoc->fname, filename, filenameend-filename) || curdoc->fname[filenameend-filename])
|
|
{
|
|
auto d = FindFile(filename);
|
|
if (d)
|
|
{
|
|
curdoc = d;
|
|
s->setDocument(d->doc);
|
|
}
|
|
else
|
|
return false; //some other file that we're not interested in
|
|
}
|
|
|
|
s->annotate(linenum-1, s->annotation(linenum) + line + "\n", 0);
|
|
return true;
|
|
}
|
|
|
|
bool saveDocument(document_s *d)
|
|
{
|
|
struct stat sbuf;
|
|
bool saved = false;
|
|
|
|
//wordpad will corrupt any embedded quake chars if we force a bom, because it'll re-save using the wrong char encoding by default.
|
|
int bomlen = 0;
|
|
const char *bom = "";
|
|
if (!d)
|
|
d = curdoc;
|
|
if (!d)
|
|
return false;
|
|
|
|
if (d->savefmt == UTF32BE || d->savefmt == UTF32LE || d->savefmt == UTF16BE)
|
|
d->savefmt = UTF16LE;
|
|
|
|
if (d->savefmt == UTF8_BOM)
|
|
{
|
|
bomlen = 3;
|
|
bom = "\xEF\xBB\xBF";
|
|
}
|
|
else if (d->savefmt == UTF16BE)
|
|
{
|
|
bomlen = 2;
|
|
bom = "\xFE\xFF";
|
|
}
|
|
else if (d->savefmt == UTF16LE)
|
|
{
|
|
bomlen = 2;
|
|
bom = "\xFF\xFE";
|
|
}
|
|
else if (d->savefmt == UTF32BE)
|
|
{
|
|
bomlen = 4;
|
|
bom = "\x00\x00\xFE\xFF";
|
|
}
|
|
else if (d->savefmt == UTF32LE)
|
|
{
|
|
bomlen = 4;
|
|
bom = "\xFF\xFE\x00\x00";
|
|
}
|
|
|
|
docstacklock lock(this, d);
|
|
|
|
auto text = s->text().toUtf8();
|
|
text.prepend(bom, bomlen);
|
|
|
|
//because wordpad saves in ansi by default instead of the format the file was originally saved in, we HAVE to use ansi without
|
|
if (d->savefmt != UTF8_BOM && d->savefmt != UTF8_RAW)
|
|
{
|
|
/*int mchars;
|
|
char *mc;
|
|
int wchars = MultiByteToWideChar(CP_UTF8, 0, text.data(), text.length(), NULL, 0);
|
|
if (wchars)
|
|
{
|
|
wchar_t *wc = malloc(wchars * sizeof(wchar_t));
|
|
MultiByteToWideChar(CP_UTF8, 0, text.data(), text.length(), wc, wchars);
|
|
|
|
if (d->savefmt == UTF_ANSI)
|
|
{
|
|
mchars = WideCharToMultiByte(CP_ACP, 0, wc, wchars, NULL, 0, "", &failed);
|
|
if (mchars)
|
|
{
|
|
mc = malloc(mchars);
|
|
WideCharToMultiByte(CP_ACP, 0, wc, wchars, mc, mchars, "", &failed);
|
|
if (!failed)
|
|
{
|
|
saved = QCC_WriteFile(d->filename, mc, mchars))
|
|
}
|
|
free(mc);
|
|
}
|
|
}
|
|
else
|
|
saved = QCC_WriteFile(d->filename, wc, wchars);
|
|
free(wc);
|
|
}*/
|
|
}
|
|
else
|
|
saved = QCC_WriteFile(d->fname, text.data(), bomlen+text.length());
|
|
if (!saved)
|
|
{
|
|
QMessageBox::critical(NULL, "Failure", "Save failed\nCheck path and ReadOnly flags");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
s->SendScintilla(QsciScintillaBase::SCI_SETSAVEPOINT);
|
|
|
|
/*now whatever is on disk should have the current time*/
|
|
//d->modified = false;
|
|
QCC_StatFile(d->fname, &sbuf);
|
|
d->filemodifiedtime = sbuf.st_mtime;
|
|
|
|
//remove the * in a silly way.
|
|
//d->oldline=~0;
|
|
//UpdateEditorTitle(d);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool saveAll(void)
|
|
{
|
|
document_s *d;
|
|
struct stat sbuf;
|
|
for (int i = 0; i < numdocuments; i++)
|
|
{
|
|
d = docs[i];
|
|
QCC_StatFile(d->fname, &sbuf);
|
|
if (d->modified)
|
|
{
|
|
if (d->filemodifiedtime != sbuf.st_mtime)
|
|
{
|
|
switch(QMessageBox::question(nullptr, "Modification conflict", QString::asprintf("%s is modified in both memory and on disk. Overwrite external modification? (saying no will reload from disk)", d->fname), QMessageBox::Save|QMessageBox::Reset|QMessageBox::Ignore, QMessageBox::Ignore))
|
|
{
|
|
case QMessageBox::Save:
|
|
if (!saveDocument(d))
|
|
QMessageBox::critical(nullptr, "Error", QString::asprintf("Unable to write %s, file was not saved", d->fname));
|
|
break;
|
|
case QMessageBox::Reset:
|
|
CreateDocument(d);
|
|
break;
|
|
default:
|
|
case QMessageBox::Ignore:
|
|
break; /*compiling will use whatever is in memory*/
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*not modified on disk, but modified in memory? try and save it, cos we might as well*/
|
|
if (!saveDocument(d))
|
|
QMessageBox::critical(nullptr, "Error", QString::asprintf("Unable to write %s, file was not saved", d->fname));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*modified on disk but not in memory? just reload it off disk*/
|
|
if (d->filemodifiedtime != sbuf.st_mtime)
|
|
CreateDocument(d);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void reapplyAllBreakpoints(void)
|
|
{
|
|
for (int i = 0; i < numdocuments; i++)
|
|
{
|
|
document_s *d = docs[i];
|
|
int line = -1;
|
|
s->setDocument(d->doc);
|
|
for (;;)
|
|
{
|
|
line = s->SendScintilla(QsciScintillaBase::SCI_MARKERNEXT, line, 1);
|
|
if (line == -1)
|
|
break; //no more.
|
|
line++;
|
|
|
|
DebuggerSendCommand("qcbreakpoint 1 \"%s\" %i\n", d->fname, line);
|
|
}
|
|
}
|
|
if (curdoc)
|
|
s->setDocument(curdoc->doc);
|
|
}
|
|
void toggleBreak(void)
|
|
{
|
|
if (!curdoc)
|
|
return;
|
|
int mode = !(s->SendScintilla(QsciScintillaBase::SCI_MARKERGET, curdoc->cursorline-1) & 1);
|
|
s->SendScintilla(mode?QsciScintillaBase::SCI_MARKERADD:QsciScintillaBase::SCI_MARKERDELETE, curdoc->cursorline-1);
|
|
|
|
DebuggerSendCommand("qcbreakpoint %i \"%s\" %i\n", mode, curdoc->fname, curdoc->cursorline);
|
|
}
|
|
void setNext(void)
|
|
{
|
|
if (!curdoc)
|
|
return;
|
|
DebuggerSendCommand("qcjump \"%s\" %i\n", curdoc->fname, curdoc->cursorline);
|
|
}
|
|
|
|
void EditFile(document_s *doc, const char *filename, int linenum=-1, bool setcontrol=false);
|
|
void EditFile(const char *filename, int linenum=-1, bool setcontrol=false)
|
|
{
|
|
EditFile(NULL, filename, linenum, setcontrol);
|
|
}
|
|
void EditFile(QModelIndex idx, int linenum=-1, bool setcontrol=false)
|
|
{
|
|
auto d = getItem(idx);
|
|
if (d)
|
|
EditFile(d, d->fname, linenum, setcontrol);
|
|
}
|
|
};
|
|
|
|
template<class T>
|
|
class keyEnterReceiver : public QObject
|
|
{
|
|
void(*cb)(T &ctx);
|
|
T ctx;
|
|
public:
|
|
keyEnterReceiver(void(*cb_)(T &ctx), T &ctx_) : cb(cb_), ctx(T(ctx_))
|
|
{
|
|
}
|
|
protected:
|
|
bool eventFilter(QObject* obj, QEvent* event)
|
|
{
|
|
if (event->type()==QEvent::KeyPress)
|
|
{
|
|
QKeyEvent* key = static_cast<QKeyEvent*>(event);
|
|
if ( (key->key()==Qt::Key_Enter) || (key->key()==Qt::Key_Return) )
|
|
cb(ctx);
|
|
else
|
|
return QObject::eventFilter(obj, event);
|
|
return true;
|
|
}
|
|
else
|
|
return QObject::eventFilter(obj, event);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class optionswindow : public QDialog
|
|
{
|
|
QWidget *newlineedit(const char *initial, void(*changed)(const QString&))
|
|
{
|
|
auto w = new QLineEdit(initial);
|
|
connect(w, &QLineEdit::textEdited, changed);
|
|
return w;
|
|
}
|
|
void SetOptsEnabled(QGridLayout *layout, int lev = -1)
|
|
{
|
|
for(int i = 0; i < layout->count(); i++)
|
|
{
|
|
auto w = static_cast<QCheckBox*>(layout->itemAt(i)->widget());
|
|
if (!w)
|
|
break;
|
|
auto id = w->property("optnum").toInt();
|
|
if (lev >= 0 && lev <= 3)
|
|
w->setCheckState((lev >= optimisations[id].optimisationlevel)?Qt::Checked:Qt::Unchecked);
|
|
else if (lev == 4)
|
|
{
|
|
if (optimisations[id].flags & FLAG_KILLSDEBUGGERS)
|
|
w->setCheckState(Qt::Unchecked);
|
|
}
|
|
else if (lev == 5)
|
|
w->setCheckState((optimisations[id].flags&FLAG_ASDEFAULT)?Qt::Checked:Qt::Unchecked);
|
|
w->setEnabled(fl_nondfltopts); //FIXME: should use tristate on a per-setting basis
|
|
}
|
|
}
|
|
|
|
public:
|
|
optionswindow()
|
|
{
|
|
setModal(true);
|
|
|
|
auto toplayout = new QHBoxLayout;
|
|
auto leftlayout = new QVBoxLayout;
|
|
auto rightlayout = new QVBoxLayout;
|
|
|
|
{
|
|
auto layout = new QGridLayout;
|
|
for (int i = 0,n=0; optimisations[i].fullname; i++)
|
|
{
|
|
if (optimisations[i].flags & FLAG_HIDDENINGUI)
|
|
continue;
|
|
auto w = new QCheckBox(optimisations[i].fullname);
|
|
w->setProperty("optnum", i);
|
|
w->setCheckState((optimisations[i].flags & FLAG_SETINGUI)?Qt::Checked:Qt::Unchecked);
|
|
w->setEnabled(!fl_nondfltopts);
|
|
connect(w, &QCheckBox::stateChanged, [=](int v){if (v)optimisations[i].flags |= FLAG_SETINGUI;else optimisations[i].flags &= ~FLAG_SETINGUI;});
|
|
layout->addWidget(w, n>>1, n&1);
|
|
n++;
|
|
}
|
|
SetOptsEnabled(layout, -1);
|
|
auto gb = new QGroupBox("Optimisations");
|
|
auto opts = new QVBoxLayout;
|
|
opts->addLayout(layout);
|
|
auto optlevels = new QHBoxLayout;
|
|
for (int i=0; i<6; i++)
|
|
{
|
|
const char *buttons[] = {"O0", "O1", "O2", "O3", "Debug", "Default"};
|
|
auto w = new QPushButton(buttons[i]);
|
|
if (i == 5)
|
|
{
|
|
w->setCheckable(true);
|
|
w->setChecked(fl_nondfltopts);
|
|
}
|
|
connect(w, &QPushButton::clicked, [=](bool checked)
|
|
{
|
|
if (i == 5)
|
|
fl_nondfltopts = !checked;
|
|
else
|
|
fl_nondfltopts = true;
|
|
SetOptsEnabled(layout, i);
|
|
});
|
|
optlevels->addWidget(w);
|
|
}
|
|
opts->addLayout(optlevels);
|
|
gb->setLayout(opts);
|
|
leftlayout->addWidget(gb);
|
|
}
|
|
{
|
|
auto layout = new QFormLayout;
|
|
layout->addRow("Engine:", newlineedit(enginebinary, [](const QString&str){QC_strlcpy(enginebinary, str.toUtf8().data(), sizeof(enginebinary));}));
|
|
layout->addRow("Basedir:", newlineedit(enginebasedir, [](const QString&str){QC_strlcpy(enginebasedir, str.toUtf8().data(), sizeof(enginebasedir));}));
|
|
layout->addRow("Cmdline:", newlineedit(enginecommandline, [](const QString&str){QC_strlcpy(enginecommandline, str.toUtf8().data(), sizeof(enginecommandline));}));
|
|
leftlayout->addLayout(layout);
|
|
}
|
|
|
|
{
|
|
auto layout = new QGridLayout;
|
|
int n = 0;
|
|
|
|
auto w = new QCheckBox("HexenC");
|
|
w->setStatusTip("Compile for hexen2.\nThis changes the opcodes slightly, the progs crc, and enables some additional keywords.");
|
|
w->setCheckState((fl_hexen2)?Qt::Checked:Qt::Unchecked);
|
|
connect(w, &QCheckBox::stateChanged, [=](int v){fl_hexen2=!!v;});
|
|
layout->addWidget(w, n/3, n%3);
|
|
n++;
|
|
|
|
w = new QCheckBox("Extended Instructions");
|
|
w->setStatusTip("Enables the use of additional opcodes, which only FTE supports at this time.\nThis gives both smaller and faster code, as well as allowing pointers, ints, and other extensions not possible with the vanilla QCVM");
|
|
w->setCheckState((fl_ftetarg)?Qt::Checked:Qt::Unchecked);
|
|
connect(w, &QCheckBox::stateChanged, [=](int v){fl_ftetarg=!!v;});
|
|
layout->addWidget(w, n/3, n%3);
|
|
n++;
|
|
|
|
for (int i = 0; compiler_flag[i].fullname; i++)
|
|
{
|
|
if (compiler_flag[i].flags & FLAG_HIDDENINGUI)
|
|
continue;
|
|
auto w = new QCheckBox(compiler_flag[i].fullname);
|
|
w->setCheckState((compiler_flag[i].flags & FLAG_SETINGUI)?Qt::Checked:Qt::Unchecked);
|
|
connect(w, &QCheckBox::stateChanged, [=](int v){if (v)compiler_flag[i].flags |= FLAG_SETINGUI;else compiler_flag[i].flags &= ~FLAG_SETINGUI;});
|
|
layout->addWidget(w, n/3, n%3);
|
|
n++;
|
|
}
|
|
|
|
auto gb = new QGroupBox("Compiler Flags");
|
|
gb->setLayout(layout);
|
|
rightlayout->addWidget(gb);
|
|
|
|
rightlayout->addWidget(new QTextEdit());
|
|
}
|
|
toplayout->addLayout(leftlayout);
|
|
toplayout->addLayout(rightlayout);
|
|
|
|
setLayout(toplayout);
|
|
}
|
|
};
|
|
|
|
static class guimainwindow : public QMainWindow
|
|
{
|
|
public:
|
|
QsciScintilla s;
|
|
QSplitter leftrightsplit;
|
|
QSplitter logsplit;
|
|
QSplitter leftsplit;
|
|
QTreeView files_w;
|
|
QListView docs_w;
|
|
|
|
QTextEdit log;
|
|
filelist files;
|
|
documentlist docs;
|
|
|
|
private:
|
|
void CreateMenus(void)
|
|
{
|
|
auto fileMenu = menuBar()->addMenu(tr("&File"));
|
|
|
|
//editor UI things
|
|
auto fileopen = new QAction(tr("Open"), this);
|
|
fileMenu->addAction(fileopen);
|
|
fileopen->setShortcuts(QKeySequence::listFromString("Ctrl+O"));
|
|
connect(fileopen, &QAction::triggered, [=]()
|
|
{
|
|
GUIprintf("Ctrl+O hit\n");
|
|
});
|
|
auto filesave = new QAction(tr("Save"), this);
|
|
fileMenu->addAction(filesave);
|
|
filesave->setShortcuts(QKeySequence::listFromString("Ctrl+S"));
|
|
connect(filesave, &QAction::triggered, [=]()
|
|
{
|
|
if (!docs.saveDocument(NULL))
|
|
QMessageBox::critical(nullptr, "Error", QString::asprintf("Unable to save"));
|
|
});
|
|
|
|
auto prefs = new QAction(tr("&Preferences"), this);
|
|
fileMenu->addAction(prefs);
|
|
prefs->setShortcuts(QKeySequence::Preferences);
|
|
prefs->setStatusTip(tr("Reconfigure stuff"));
|
|
connect(prefs, &QAction::triggered, [](){(new optionswindow())->show();});
|
|
|
|
auto goline = new QAction(tr("Go to line"), this);
|
|
fileMenu->addAction(goline);
|
|
goline->setShortcuts(QKeySequence::listFromString("Ctrl+G"));
|
|
goline->setStatusTip(tr("Jump to line"));
|
|
connect(goline, &QAction::triggered, [=]()
|
|
{
|
|
struct grepargs_s
|
|
{
|
|
guimainwindow *mw;
|
|
QDialog *d;
|
|
QLineEdit *t;
|
|
} args = {this, new QDialog()};
|
|
args.t = new QLineEdit(QString(""));
|
|
auto l = new QVBoxLayout;
|
|
l->addWidget(args.t);
|
|
args.d->setLayout(l);
|
|
args.d->setWindowTitle("FTEQCC Go To Line");
|
|
args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
|
|
{
|
|
ctx.mw->docs.EditFile(NULL, atoi(ctx.t->text().toUtf8().data()));
|
|
ctx.d->done(0);
|
|
}, args));
|
|
args.d->show();
|
|
});
|
|
|
|
auto godef = new QAction(tr("Go To Definition"), this);
|
|
fileMenu->addAction(godef);
|
|
godef->setShortcuts(QKeySequence::listFromString("F12"));
|
|
connect(godef, &QAction::triggered, [=]()
|
|
{
|
|
struct grepargs_s
|
|
{
|
|
guimainwindow *mw;
|
|
QDialog *d;
|
|
QLineEdit *t;
|
|
} args = {this, new QDialog()};
|
|
args.t = new QLineEdit(this->s.selectedText());
|
|
auto l = new QVBoxLayout;
|
|
l->addWidget(args.t);
|
|
args.d->setLayout(l);
|
|
args.d->setWindowTitle("Go To Definition");
|
|
args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
|
|
{
|
|
GoToDefinition(ctx.t->text().toUtf8().data());
|
|
ctx.d->done(0);
|
|
}, args));
|
|
args.d->show();
|
|
});
|
|
|
|
auto find = new QAction(tr("Find"), this);
|
|
fileMenu->addAction(find);
|
|
find->setShortcuts(QKeySequence::listFromString("Ctrl+F"));
|
|
find->setStatusTip(tr("Search through all project files"));
|
|
connect(find, &QAction::triggered, [=]()
|
|
{
|
|
struct grepargs_s
|
|
{
|
|
guimainwindow *mw;
|
|
QDialog *d;
|
|
QLineEdit *t;
|
|
} args = {this, new QDialog()};
|
|
args.t = new QLineEdit(this->s.selectedText());
|
|
auto l = new QVBoxLayout;
|
|
l->addWidget(args.t);
|
|
args.d->setLayout(l);
|
|
args.d->setWindowTitle("FTEQCC Find");
|
|
args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
|
|
{
|
|
ctx.mw->s.findFirst(ctx.t->text(), false, false, false, true);
|
|
ctx.d->done(0);
|
|
}, args));
|
|
args.d->show();
|
|
});
|
|
connect(new QShortcut(QKeySequence(tr("F3", "File|FindNext")), this), &QShortcut::activated,
|
|
[=]()
|
|
{
|
|
s.findNext();
|
|
});
|
|
|
|
auto grep = new QAction(tr("Grep"), this);
|
|
fileMenu->addAction(grep);
|
|
grep->setShortcuts(QKeySequence::listFromString("Ctrl+Shift+G"));
|
|
grep->setStatusTip(tr("Search through all project files"));
|
|
connect(grep, &QAction::triggered, [=]()
|
|
{
|
|
struct grepargs_s
|
|
{
|
|
guimainwindow *mw;
|
|
QDialog *d;
|
|
QLineEdit *t;
|
|
} args = {this, new QDialog()};
|
|
args.t = new QLineEdit(this->s.selectedText());
|
|
auto l = new QVBoxLayout;
|
|
l->addWidget(args.t);
|
|
args.d->setLayout(l);
|
|
args.d->setWindowTitle("FTEQCC Grep Project Files");
|
|
args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
|
|
{
|
|
ctx.mw->files.GrepAll(ctx.t->text().toUtf8().data());
|
|
ctx.d->done(0);
|
|
}, args));
|
|
args.d->show();
|
|
});
|
|
|
|
//convert to utf-8 chars
|
|
//convert to Quake chars
|
|
|
|
auto convertunix = new QAction(tr("Convert to Unix Endings"), this);
|
|
fileMenu->addAction(convertunix);
|
|
connect(convertunix, &QAction::triggered, [=]()
|
|
{
|
|
s.convertEols(QsciScintilla::EolUnix);
|
|
s.setEolVisibility(false);
|
|
});
|
|
auto convertdos = new QAction(tr("Convert to DOS Endings"), this);
|
|
fileMenu->addAction(convertdos);
|
|
connect(convertdos, &QAction::triggered, [=]()
|
|
{
|
|
s.convertEols(QsciScintilla::EolWindows);
|
|
s.setEolVisibility(false);
|
|
});
|
|
|
|
auto quit = new QAction(tr("Quit"), this);
|
|
fileMenu->addAction(quit);
|
|
connect(quit, &QAction::triggered, [=]()
|
|
{
|
|
this->close();
|
|
});
|
|
|
|
|
|
auto debugMenu = menuBar()->addMenu(tr("&Debug"));
|
|
auto debugrebuild = new QAction(tr("Rebuild"), this);
|
|
debugMenu->addAction(debugrebuild);
|
|
debugrebuild->setShortcuts(QKeySequence::listFromString("F7"));
|
|
connect(debugrebuild, &QAction::triggered, [=]()
|
|
{
|
|
RunCompiler("", false);
|
|
});
|
|
auto debugsetnext = new QAction(tr("Set Next Statement"), this);
|
|
debugMenu->addAction(debugsetnext);
|
|
debugsetnext->setShortcuts(QKeySequence::listFromString("F8"));
|
|
connect(debugsetnext, &QAction::triggered, [=]()
|
|
{
|
|
//FIXME
|
|
});
|
|
auto debugresume = new QAction(tr("Resume"), this);
|
|
debugMenu->addAction(debugresume);
|
|
debugresume->setShortcuts(QKeySequence::listFromString("F5"));
|
|
connect(debugresume, &QAction::triggered, [=]()
|
|
{
|
|
if (!DebuggerSendCommand("qcresume\n"))
|
|
DebuggerStart(); //unable to send? assume its not running.
|
|
});
|
|
auto debugover = new QAction(tr("Step Over"), this);
|
|
debugMenu->addAction(debugover);
|
|
debugover->setShortcuts(QKeySequence::listFromString("F10"));
|
|
connect(debugover, &QAction::triggered, [=]()
|
|
{
|
|
if (!DebuggerSendCommand("qcstep over\n"))
|
|
DebuggerStart();
|
|
});
|
|
auto debuginto = new QAction(tr("Step Into"), this);
|
|
debugMenu->addAction(debuginto);
|
|
debuginto->setShortcuts(QKeySequence::listFromString("F11"));
|
|
connect(debuginto, &QAction::triggered, [=]()
|
|
{
|
|
if (!DebuggerSendCommand("qcstep into\n"))
|
|
DebuggerStart();
|
|
});
|
|
auto debugout = new QAction(tr("Step Out"), this);
|
|
debugMenu->addAction(debugout);
|
|
debugout->setShortcuts(QKeySequence::listFromString("Shift+F11"));
|
|
connect(debugout, &QAction::triggered, [=]()
|
|
{
|
|
if (!DebuggerSendCommand("qcstep out\n"))
|
|
DebuggerStart();
|
|
});
|
|
auto debugbreak = new QAction(tr("Toggle Breakpoint"), this);
|
|
debugMenu->addAction(debugbreak);
|
|
debugbreak->setShortcuts(QKeySequence::listFromString("F9"));
|
|
connect(debugbreak, &QAction::triggered, [=]()
|
|
{
|
|
docs.toggleBreak();
|
|
});
|
|
}
|
|
|
|
public:
|
|
~guimainwindow()
|
|
{ //if we're dying, make sure there's no engine waiting for us
|
|
DebuggerStop();
|
|
}
|
|
guimainwindow() : s(this), leftrightsplit(Qt::Horizontal, this), logsplit(Qt::Vertical, this), leftsplit(Qt::Vertical, this), files_w(this), docs_w(this), docs(&s)
|
|
{
|
|
setWindowTitle(QString("FTEQCC Gui"));
|
|
|
|
s.setReadOnly(true);
|
|
|
|
files_w.setModel(&files);
|
|
connect(&files_w, &QTreeView::clicked,
|
|
[=](const QModelIndex &index)
|
|
{
|
|
docs.EditFile(files.getItem(index)->name);
|
|
});
|
|
|
|
leftrightsplit.addWidget(&leftsplit);
|
|
leftrightsplit.addWidget(&logsplit);
|
|
leftsplit.addWidget(&files_w);
|
|
leftsplit.addWidget(&docs_w);
|
|
docs_w.setModel(&docs);
|
|
logsplit.addWidget(&s);
|
|
logsplit.addWidget(&log);
|
|
QList<int> sizes;
|
|
sizes.append(64);
|
|
sizes.append(256);
|
|
leftrightsplit.setSizes(sizes);
|
|
QList<int> sizes2;
|
|
sizes.append(1);
|
|
sizes.append(0);
|
|
logsplit.setSizes(sizes2);
|
|
setCentralWidget(&leftrightsplit);
|
|
|
|
log.setReadOnly(true);
|
|
log.clear();
|
|
|
|
connect(&docs_w, &QAbstractItemView::clicked,
|
|
[=](const QModelIndex &index)
|
|
{
|
|
docs.EditFile(index);
|
|
});
|
|
|
|
connect(&log, &QTextEdit::selectionChanged,
|
|
[=]()
|
|
{
|
|
auto foo = log.textCursor();
|
|
foo.select(QTextCursor::LineUnderCursor);
|
|
auto txt = foo.selectedText();
|
|
auto colon = txt.indexOf(':');
|
|
if (colon>0)
|
|
{
|
|
auto colon2 = txt.indexOf(':', colon+1);
|
|
if (colon2>0)
|
|
{
|
|
auto line = txt.mid(colon+1, colon2-colon-1).toInt();
|
|
EditFile(txt.mid(0, colon).toUtf8().data(), line, true);
|
|
}
|
|
else
|
|
EditFile(txt.mid(0, colon).toUtf8().data(), -1, true);
|
|
}
|
|
});
|
|
|
|
CreateMenus();
|
|
}
|
|
} *mainwnd;
|
|
|
|
//called when progssrcname has changed.
|
|
//progssrcname should already have been set.
|
|
void UpdateFileList(void)
|
|
{
|
|
char *buffer;
|
|
|
|
AddSourceFile(nullptr, progssrcname);
|
|
|
|
size_t size;
|
|
buffer = static_cast<char*>(QCC_ReadFile(progssrcname, nullptr, nullptr, &size));
|
|
|
|
pr_file_p = QCC_COM_Parse(buffer);
|
|
if (*qcc_token == '#')
|
|
{
|
|
//aaaahhh! newstyle!
|
|
}
|
|
else
|
|
{
|
|
pr_file_p = QCC_COM_Parse(pr_file_p); //we dont care about the produced progs.dat
|
|
while(pr_file_p)
|
|
{
|
|
if (*qcc_token == '#') //panic if there's preprocessor in there.
|
|
break;
|
|
|
|
AddSourceFile(progssrcname, qcc_token);
|
|
pr_file_p = QCC_COM_Parse(pr_file_p); //we dont care about the produced progs.dat
|
|
}
|
|
}
|
|
free(buffer);
|
|
|
|
//handle any #includes in there
|
|
RunCompiler(parameters, true);
|
|
|
|
//expand everything, so the user doesn't get annoyed.
|
|
mainwnd->files_w.expandAll();
|
|
}
|
|
void AddSourceFile(const char *parentpath, const char *filename)
|
|
{
|
|
mainwnd->files.AddFile(parentpath, filename);
|
|
}
|
|
|
|
static int Dummyprintf(const char *msg, ...){return 0;}
|
|
|
|
void RunCompiler(const char *args, pbool quick)
|
|
{
|
|
static FILE *logfile;
|
|
const char *argv[128];
|
|
int argc;
|
|
progexterns_t ext;
|
|
progfuncs_t funcs;
|
|
|
|
mainwnd->docs.saveAll();
|
|
|
|
memset(&funcs, 0, sizeof(funcs));
|
|
funcs.funcs.parms = &ext;
|
|
memset(&ext, 0, sizeof(ext));
|
|
ext.ReadFile = GUIReadFile;
|
|
ext.FileSize = GUIFileSize;
|
|
ext.WriteFile = QCC_WriteFile;
|
|
ext.Sys_Error = Sys_Error;
|
|
|
|
if (quick)
|
|
ext.Printf = Dummyprintf;
|
|
else
|
|
{
|
|
ext.Printf = GUIprintf;
|
|
GUIprintf("");
|
|
}
|
|
ext.DPrintf = ext.Printf;
|
|
|
|
if (logfile)
|
|
fclose(logfile);
|
|
if (fl_log && !quick)
|
|
logfile = fopen("fteqcc.log", "wb");
|
|
else
|
|
logfile = NULL;
|
|
|
|
argc = GUI_BuildParms(args, argv, quick);
|
|
|
|
if (CompileParams(&funcs, NULL, argc, argv))
|
|
{
|
|
if (!quick)
|
|
{
|
|
//DebuggerGiveFocus();
|
|
DebuggerSendCommand("qcresume\nqcreload\n");
|
|
}
|
|
}
|
|
|
|
if (logfile)
|
|
{
|
|
fclose(logfile);
|
|
logfile = NULL;
|
|
}
|
|
}
|
|
|
|
void GUI_DoDecompile(void *buf, size_t size)
|
|
{
|
|
const char *c = ReadProgsCopyright((char*)buf, size);
|
|
if (!c || !*c)
|
|
c = "COPYRIGHT OWNER NOT KNOWN"; //all work is AUTOMATICALLY copyrighted under the terms of the Berne Convention in all major nations. It _IS_ copyrighted, even if there's no license etc included. Good luck guessing what rights you have.
|
|
if (QMessageBox::Open == QMessageBox::question(mainwnd, "Copyright", QString::asprintf("The copyright message from this progs is\n%s\n\nPlease respect the wishes and legal rights of the person who created this.", c), QMessageBox::Open|QMessageBox::Cancel, QMessageBox::Cancel))
|
|
{
|
|
extern pbool qcc_vfiles_changed;
|
|
extern vfile_t *qcc_vfiles;
|
|
|
|
GUIprintf("");
|
|
|
|
DecompileProgsDat(progssrcname, buf, size);
|
|
|
|
if (qcc_vfiles_changed)
|
|
{
|
|
switch (QMessageBox::question(mainwnd, "Decompile", "Save as archive?", QMessageBox::Yes|QMessageBox::SaveAll|QMessageBox::Ignore, QMessageBox::Ignore))
|
|
{
|
|
case QMessageBox::Yes:
|
|
{
|
|
QString fname = QFileDialog::getSaveFileName(mainwnd, "Output Archive", QString(), "Zips (*.zip)");
|
|
if (!fname.isNull())
|
|
{
|
|
int h = SafeOpenWrite(fname.toUtf8().data(), -1);
|
|
|
|
progfuncs_t funcs;
|
|
progexterns_t ext;
|
|
memset(&funcs, 0, sizeof(funcs));
|
|
funcs.funcs.parms = &ext;
|
|
memset(&ext, 0, sizeof(ext));
|
|
ext.ReadFile = GUIReadFile;
|
|
ext.FileSize = GUIFileSize;
|
|
ext.WriteFile = QCC_WriteFile;
|
|
ext.Sys_Error = Sys_Error;
|
|
ext.Printf = GUIprintf;
|
|
|
|
qccprogfuncs = &funcs;
|
|
WriteSourceFiles(qcc_vfiles, h, true, false);
|
|
qccprogfuncs = NULL;
|
|
|
|
SafeClose(h);
|
|
|
|
qcc_vfiles_changed = false;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case QMessageBox::SaveAll:
|
|
{
|
|
QString path = QFileDialog::getExistingDirectory(mainwnd, "Where do you want to save the decompiled code?", QString());
|
|
for (vfile_t *f = qcc_vfiles; f; f = f->next)
|
|
{
|
|
char nname[MAX_OSPATH];
|
|
int h;
|
|
QC_snprintfz(nname, sizeof(nname), "%s/%s", path.toUtf8().data(), f->filename);
|
|
h = SafeOpenWrite(f->filename, -1);
|
|
|
|
if (h >= 0)
|
|
{
|
|
SafeWrite(h, f->file, f->size);
|
|
SafeClose(h);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void QCC_EnumerateFilesResult(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize)
|
|
{
|
|
auto buffer = new char[plainsize];
|
|
if (QC_decode(nullptr, compsize, plainsize, method, compdata, buffer))
|
|
QCC_AddVFile(name, buffer, plainsize);
|
|
|
|
delete [] buffer;
|
|
}
|
|
|
|
static void SetMainSrcFile(const char *src)
|
|
{
|
|
//if its a path, chdir to it instead
|
|
const char *sl = strrchr(src, '/');
|
|
if (sl)
|
|
{
|
|
sl++;
|
|
auto gah = static_cast<char*>(malloc(sl-src+1));
|
|
memcpy(gah, src, sl-src);
|
|
gah[sl-src] = 0;
|
|
chdir(gah);
|
|
free(gah);
|
|
src = sl;
|
|
}
|
|
|
|
strcpy(progssrcname, src);
|
|
|
|
QCC_CloseAllVFiles();
|
|
|
|
GUI_SetDefaultOpts();
|
|
GUI_ParseCommandLine(cmdlineargs, true);
|
|
GUI_RevealOptions();
|
|
|
|
//if the project is a .dat or .zip then decompile it now (so we can access the 'source')
|
|
{
|
|
char *ext = strrchr(progssrcname, '.');
|
|
if (ext && (!QC_strcasecmp(ext, ".dat") || !QC_strcasecmp(ext, ".pak") || !QC_strcasecmp(ext, ".zip") || !QC_strcasecmp(ext, ".pk3")))
|
|
{
|
|
FILE *f = fopen(progssrcname, "rb");
|
|
if (f)
|
|
{
|
|
size_t size;
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
size = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
auto buf = new char[size];
|
|
fread(buf, 1, size, f);
|
|
fclose(f);
|
|
if (!QC_EnumerateFilesFromBlob(buf, size, QCC_EnumerateFilesResult) && !QC_strcasecmp(ext, ".dat"))
|
|
{ //its a .dat and contains no .src files
|
|
GUI_DoDecompile(buf, size);
|
|
}
|
|
else if (!QCC_FindVFile("progs.src"))
|
|
{
|
|
vfile_t *f;
|
|
char *archivename = progssrcname;
|
|
while(strchr(archivename, '\\'))
|
|
archivename = strchr(archivename, '\\')+1;
|
|
AddSourceFile(NULL, archivename);
|
|
// for (f = qcc_vfiles; f; f = f->next)
|
|
// AddSourceFile(archivename, f->filename);
|
|
|
|
f = QCC_FindVFile("progs.dat");
|
|
if (f)
|
|
GUI_DoDecompile(f->file, f->size);
|
|
}
|
|
delete [] buf;
|
|
strcpy(progssrcname, "progs.src");
|
|
}
|
|
else
|
|
strcpy(progssrcname, "progs.src");
|
|
|
|
for (int i = 0; ; i++)
|
|
{
|
|
if (!strcmp("embedsrc", compiler_flag[i].abbrev))
|
|
{
|
|
compiler_flag[i].flags |= FLAG_SETINGUI;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//then populate the file list.
|
|
UpdateFileList();
|
|
EditFile(progssrcname, -1, false);
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
//handle initial commandline args
|
|
GUI_SetDefaultOpts();
|
|
{
|
|
size_t argl = 1;
|
|
for (int i = 1; i < argc; i++)
|
|
argl += strlen(argv[i])+1;
|
|
cmdlineargs = (char*)malloc(argl);
|
|
argl = 0;
|
|
for (int i = 1; i < argc; i++)
|
|
{
|
|
memcpy(cmdlineargs+argl, argv[i], strlen(argv[i]));
|
|
argl += strlen(argv[i]);
|
|
cmdlineargs[argl++] = ' ';
|
|
}
|
|
cmdlineargs[argl] = 0;
|
|
int mode = GUI_ParseCommandLine(cmdlineargs, false);
|
|
if (mode == 1)
|
|
{ //compile-only
|
|
RunCompiler(cmdlineargs, false);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//start up the gui parts
|
|
QCoreApplication *app = new QApplication(argc, argv);
|
|
mainwnd = new guimainwindow();
|
|
mainwnd->show();
|
|
GUIprintf("Welcome to FTEQCC!\n(QT edition)\n");
|
|
#ifdef SVNREVISION
|
|
if (strcmp(STRINGIFY(SVNREVISION), "-"))
|
|
GUIprintf("FTE SVN Revision: %s\n",STRINGIFY(SVNREVISION));
|
|
#endif
|
|
|
|
//and now set up our project...
|
|
if (*progssrcname)
|
|
SetMainSrcFile(progssrcname);
|
|
else
|
|
SetMainSrcFile("progs.src");
|
|
|
|
//done, run the gui's main loop.
|
|
return app->exec();
|
|
}
|
|
void compilecb(void)
|
|
{
|
|
}
|
|
|
|
int GUIprintf(const char *msg, ...)
|
|
{
|
|
static QString l;
|
|
va_list va;
|
|
|
|
if (!*msg)
|
|
{ //starting a compile or something.
|
|
//clear the text and make sure the log is visible.
|
|
mainwnd->log.setText(QString(""));
|
|
if (!mainwnd->logsplit.sizes()[1])
|
|
{ //force it visible
|
|
QList<int> sizes;
|
|
sizes.append(2);
|
|
sizes.append(1);
|
|
mainwnd->logsplit.setSizes(sizes);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
va_start(va, msg);
|
|
l.append(QString::vasprintf(msg, va));
|
|
va_end(va);
|
|
for (;;)
|
|
{
|
|
auto idx = l.indexOf('\n');
|
|
if (idx >= 0)
|
|
{
|
|
QString s = l.mid(0, idx);
|
|
l = l.mid(idx+1);
|
|
|
|
if (!s.mid(0, 6).compare("code: "))
|
|
{
|
|
mainwnd->docs.annotate(s.toUtf8().data());
|
|
continue;
|
|
}
|
|
else if (s.contains(": error") || s.contains(": werror") || !s.mid(0,5).compare("error", Qt::CaseInsensitive))
|
|
mainwnd->log.setTextColor(QColor(255, 0, 0));
|
|
else if (s.contains(": warning"))
|
|
mainwnd->log.setTextColor(QColor(128, 128, 0));
|
|
else
|
|
mainwnd->log.setTextColor(QColor(0, 0, 0));
|
|
mainwnd->log.append(s);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
void documentlist::UpdateTitle(void)
|
|
{
|
|
if (curdoc)
|
|
mainwnd->setWindowTitle(QString::asprintf("%s%s:%i", curdoc->fname, curdoc->modified?"*":"", curdoc->cursorline));
|
|
else
|
|
mainwnd->setWindowTitle("FTEQCC");
|
|
}
|
|
void documentlist::EditFile(document_s *c, const char *filename, int linenum, bool setcontrol)
|
|
{
|
|
if (setcontrol)
|
|
{
|
|
for (int i = 0; i < numdocuments; i++)
|
|
{
|
|
docstacklock lock(this, docs[i]);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDELETEALL, 1);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERDELETEALL, 2);
|
|
}
|
|
}
|
|
|
|
if (!c)
|
|
c = FindFile(filename);
|
|
if (!c)
|
|
{
|
|
c = new document_s();
|
|
c->fname = strdup(filename);
|
|
|
|
docs = cpprealloc(docs, sizeof(*docs)*(numdocuments+1));
|
|
|
|
if (!CreateDocument(c))
|
|
{
|
|
delete(c);
|
|
return;
|
|
}
|
|
|
|
beginInsertRows(QModelIndex(), numdocuments, numdocuments);
|
|
docs[numdocuments] = c;
|
|
numdocuments++;
|
|
endInsertRows();
|
|
}
|
|
else
|
|
{
|
|
SwitchToDocument(c);
|
|
}
|
|
|
|
UpdateTitle();
|
|
|
|
if (linenum >= 1)
|
|
{
|
|
linenum--; //scintilla is 0-based, apparently.
|
|
s->ensureLineVisible(max(1, linenum - 3));
|
|
s->ensureLineVisible(linenum + 3);
|
|
s->setCursorPosition(linenum, 0);
|
|
s->ensureCursorVisible();
|
|
s->setFocus();
|
|
|
|
if (setcontrol)
|
|
{
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERADD, linenum, 1);
|
|
s->SendScintilla(QsciScintillaBase::SCI_MARKERADD, linenum, 2);
|
|
}
|
|
}
|
|
}
|
|
void EditFile(const char *name, int line, pbool setcontrol)
|
|
{
|
|
mainwnd->docs.EditFile(name, line, setcontrol);
|
|
}
|
|
void GUI_DialogPrint(const char *title, const char *text)
|
|
{
|
|
QMessageBox::information(mainwnd, title, text);
|
|
}
|
|
void *GUIReadFile(const char *fname, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size, pbool issourcefile)
|
|
{
|
|
auto d = mainwnd->docs.FindFile(fname);
|
|
if (d)
|
|
return mainwnd->docs.getFileData(d, buf_get, buf_ctx, out_size);
|
|
|
|
if (issourcefile)
|
|
AddSourceFile(compilingrootfile, fname);
|
|
|
|
return QCC_ReadFile(fname, buf_get, buf_ctx, out_size);
|
|
}
|
|
int GUIFileSize(const char *fname)
|
|
{
|
|
auto d = mainwnd->docs.FindFile(fname);
|
|
if (d)
|
|
return mainwnd->docs.getFileSize(d);
|
|
|
|
return QCC_PopFileSize(fname);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static QProcess *qcdebugger;
|
|
static void DebuggerStop(void)
|
|
{
|
|
if (qcdebugger)
|
|
{
|
|
//GUIprintf("Detatching from debuggee\n");
|
|
qcdebugger->closeWriteChannel();
|
|
qcdebugger->closeReadChannel(QProcess::StandardOutput);
|
|
qcdebugger->closeReadChannel(QProcess::StandardError);
|
|
qcdebugger->waitForFinished();
|
|
delete qcdebugger;
|
|
qcdebugger = NULL;
|
|
}
|
|
}
|
|
static bool DebuggerSendCommand(const char *msg, ...)
|
|
{
|
|
va_list va;
|
|
//qcresume
|
|
//qcstep out|over|into
|
|
//qcjump "file" line
|
|
//debuggerwnd windowid
|
|
//qcinspect "variable"
|
|
//qcreload
|
|
//qcbreakpoint off=0|on=1|toggle=2 "file" line
|
|
|
|
if (!qcdebugger || qcdebugger->state() == QProcess::NotRunning)
|
|
return false; //not running, can't send.
|
|
|
|
va_start(va, msg);
|
|
qcdebugger->write(QString::vasprintf(msg, va).toUtf8().data());
|
|
va_end(va);
|
|
return true;
|
|
}
|
|
static void DebuggerStart(void)
|
|
{
|
|
DebuggerStop();
|
|
|
|
const char *engine = enginebinary;
|
|
const char *cmdline = enginecommandline;
|
|
if (!*enginebinary)
|
|
{
|
|
engine = "fteqw";
|
|
if(!*cmdline)
|
|
cmdline = "-window";
|
|
}
|
|
qcdebugger = new QProcess(mainwnd);
|
|
qcdebugger->setProgram(engine);
|
|
qcdebugger->setWorkingDirectory(enginebasedir);
|
|
qcdebugger->setArguments(QStringList(cmdline));
|
|
|
|
QObject::connect(qcdebugger, static_cast<void(QProcess::*)(int)>(&QProcess::finished),
|
|
[=](int exitcode)
|
|
{
|
|
// GUIprintf("Debuggee finished\n");
|
|
// DebuggerStop(); //can't kill it here, there's still code running inside it
|
|
mainwnd->activateWindow(); //try and grab the user's attention
|
|
});
|
|
QObject::connect(qcdebugger, &QProcess::readyReadStandardOutput,
|
|
[=]()
|
|
{
|
|
while(qcdebugger->canReadLine())
|
|
{
|
|
auto l = qcdebugger->readLine();
|
|
const char *line = l.data();
|
|
if (!strncmp(line, "qcstep ", 7) || !strncmp(line, "qcfault ", 8))
|
|
{ //engine hit a breakpoint or some such.
|
|
//file, linenum, message
|
|
line = QCC_COM_Parse (line+7);
|
|
QString s(qcc_token);
|
|
if (*line == ':')
|
|
line++; //grr
|
|
line = QCC_COM_Parse (line);
|
|
EditFile(s.toUtf8().data(), atoi(qcc_token), true);
|
|
line = QCC_COM_Parse (line);
|
|
mainwnd->activateWindow();
|
|
if (*qcc_token)
|
|
QMessageBox::critical(mainwnd, "Debugger Fault", qcc_token);
|
|
}
|
|
else if (!strncmp(line, "qcreloaded ", 10))
|
|
{ //vmname, progsname
|
|
mainwnd->docs.reapplyAllBreakpoints(); //send all breakpoints...
|
|
DebuggerSendCommand("qcresume\n"); //and let it run
|
|
}
|
|
//status
|
|
//curserver
|
|
//qcstack
|
|
//qcvalue
|
|
//refocuswindow
|
|
else
|
|
GUIprintf(line);
|
|
}
|
|
});
|
|
|
|
qcdebugger->start(QProcess::ReadWrite|QProcess::Unbuffered);
|
|
qcdebugger->waitForStarted();
|
|
switch (qcdebugger->state())
|
|
{
|
|
case QProcess::NotRunning:
|
|
GUIprintf("Child process not running\n");
|
|
break;
|
|
case QProcess::Starting:
|
|
GUIprintf("Child starting up\n"); //still forking?
|
|
break;
|
|
case QProcess::Running:
|
|
// GUIprintf("Child is running\n");
|
|
break;
|
|
}
|
|
} |