After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,315 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
|
||||||
|
|
||||||
|
A thread-safe logging console with a line editor for windows.
|
||||||
|
|
||||||
|
Based on linenoise win32 port,
|
||||||
|
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
|
||||||
|
All rights reserved.
|
||||||
|
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
|
||||||
|
The original linenoise can be found at: http://github.com/antirez/linenoise
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <istream>
|
||||||
|
#include <string>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <md5wrapper.h>
|
||||||
|
|
||||||
|
using std::cout;
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
typedef unsigned char patch_byte;
|
||||||
|
|
||||||
|
struct BinaryPatch {
|
||||||
|
struct Byte {
|
||||||
|
unsigned offset;
|
||||||
|
patch_byte old_val, new_val;
|
||||||
|
};
|
||||||
|
enum State {
|
||||||
|
Conflict = 0,
|
||||||
|
Unapplied = 1,
|
||||||
|
Applied = 2,
|
||||||
|
Partial = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Byte> entries;
|
||||||
|
|
||||||
|
bool loadDIF(std::string name);
|
||||||
|
State checkState(const patch_byte *ptr, size_t len);
|
||||||
|
|
||||||
|
void apply(patch_byte *ptr, size_t len, bool newv);
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool is_hex(char c)
|
||||||
|
{
|
||||||
|
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BinaryPatch::loadDIF(std::string name)
|
||||||
|
{
|
||||||
|
entries.clear();
|
||||||
|
|
||||||
|
std::ifstream infile(name);
|
||||||
|
if(infile.bad())
|
||||||
|
{
|
||||||
|
cerr << "Cannot open file: " << name << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string s;
|
||||||
|
while(std::getline(infile, s))
|
||||||
|
{
|
||||||
|
// Parse lines that begin with "[0-9a-f]+:"
|
||||||
|
size_t idx = s.find(':');
|
||||||
|
if (idx == std::string::npos || idx == 0 || idx > 8)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
for (size_t i = 0; i < idx; i++)
|
||||||
|
if (!is_hex(s[i]))
|
||||||
|
ok = false;
|
||||||
|
if (!ok)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
unsigned off, oval, nval;
|
||||||
|
int nchar = 0;
|
||||||
|
int cnt = sscanf(s.c_str(), "%x: %x %x%n", &off, &oval, &nval, &nchar);
|
||||||
|
|
||||||
|
if (cnt < 3)
|
||||||
|
{
|
||||||
|
cerr << "Could not parse: " << s << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = nchar; i < s.size(); i++)
|
||||||
|
{
|
||||||
|
if (!isspace(s[i]))
|
||||||
|
{
|
||||||
|
cerr << "Garbage at end of line: " << s << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oval >= 256 || nval >= 256)
|
||||||
|
{
|
||||||
|
cerr << "Invalid byte values: " << s << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Byte bv = { off, patch_byte(oval), patch_byte(nval) };
|
||||||
|
entries.push_back(bv);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.empty())
|
||||||
|
{
|
||||||
|
cerr << "No lines recognized." << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len)
|
||||||
|
{
|
||||||
|
int state = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < entries.size(); i++)
|
||||||
|
{
|
||||||
|
Byte &bv = entries[i];
|
||||||
|
|
||||||
|
if (bv.offset >= len)
|
||||||
|
{
|
||||||
|
cerr << "Offset out of range: 0x" << std::hex << bv.offset << std::dec << endl;
|
||||||
|
return Conflict;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch_byte cv = ptr[bv.offset];
|
||||||
|
if (bv.old_val == cv)
|
||||||
|
state |= Unapplied;
|
||||||
|
else if (bv.new_val == cv)
|
||||||
|
state |= Applied;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cerr << std::hex << bv.offset << ": "
|
||||||
|
<< unsigned(bv.old_val) << " " << unsigned(bv.new_val)
|
||||||
|
<< ", but currently " << unsigned(cv) << std::dec << endl;
|
||||||
|
return Conflict;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return State(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BinaryPatch::apply(patch_byte *ptr, size_t len, bool newv)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < entries.size(); i++)
|
||||||
|
{
|
||||||
|
Byte &bv = entries[i];
|
||||||
|
assert (bv.offset < len);
|
||||||
|
|
||||||
|
ptr[bv.offset] = (newv ? bv.new_val : bv.old_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_file(std::vector<patch_byte> *pvec, std::string fname)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(fname.c_str(), "rb");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
cerr << "Cannot open file: " << fname << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
pvec->resize(ftell(f));
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
size_t cnt = fread(pvec->data(), 1, pvec->size(), f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
return cnt == pvec->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool save_file(const std::vector<patch_byte> &pvec, std::string fname)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(fname.c_str(), "wb");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
cerr << "Cannot open file: " << fname << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cnt = fwrite(pvec.data(), 1, pvec.size(), f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
return cnt == pvec.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string compute_hash(const std::vector<patch_byte> &pvec)
|
||||||
|
{
|
||||||
|
md5wrapper md5;
|
||||||
|
return md5.getHashFromBytes(pvec.data(), pvec.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc <= 3)
|
||||||
|
{
|
||||||
|
cerr << "Usage: binpatch check|apply|remove <exe> <patch>" << endl;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cmd = argv[1];
|
||||||
|
|
||||||
|
if (cmd != "check" && cmd != "apply" && cmd != "remove")
|
||||||
|
{
|
||||||
|
cerr << "Invalid command: " << cmd << endl;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string exe_file = argv[2];
|
||||||
|
std::vector<patch_byte> bindata;
|
||||||
|
if (!load_file(&bindata, exe_file))
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
BinaryPatch patch;
|
||||||
|
if (!patch.loadDIF(argv[3]))
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
BinaryPatch::State state = patch.checkState(bindata.data(), bindata.size());
|
||||||
|
if (state == BinaryPatch::Conflict)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (cmd == "check")
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case BinaryPatch::Unapplied:
|
||||||
|
cout << "Currently not applied." << endl;
|
||||||
|
break;
|
||||||
|
case BinaryPatch::Applied:
|
||||||
|
cout << "Currently applied." << endl;
|
||||||
|
break;
|
||||||
|
case BinaryPatch::Partial:
|
||||||
|
cout << "Currently partially applied." << endl;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (cmd == "apply")
|
||||||
|
{
|
||||||
|
if (state == BinaryPatch::Applied)
|
||||||
|
{
|
||||||
|
cout << "Already applied." << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch.apply(bindata.data(), bindata.size(), true);
|
||||||
|
}
|
||||||
|
else if (cmd == "remove")
|
||||||
|
{
|
||||||
|
if (state == BinaryPatch::Unapplied)
|
||||||
|
{
|
||||||
|
cout << "Already removed." << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch.apply(bindata.data(), bindata.size(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save_file(bindata, exe_file + ".bak"))
|
||||||
|
{
|
||||||
|
cerr << "Could not create backup." << endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save_file(bindata, exe_file))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
cout << "Patched " << patch.entries.size()
|
||||||
|
<< " bytes, new hash: " << compute_hash(bindata) << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
https://github.com/peterix/dfhack
|
|
||||||
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any
|
|
||||||
damages arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any
|
|
||||||
purpose, including commercial applications, and to alter it and
|
|
||||||
redistribute it freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must
|
|
||||||
not claim that you wrote the original software. If you use this
|
|
||||||
software in a product, an acknowledgment in the product documentation
|
|
||||||
would be appreciated but is not required.
|
|
||||||
|
|
||||||
2. Altered source versions must be plainly marked as such, and
|
|
||||||
must not be misrepresented as being the original software.
|
|
||||||
|
|
||||||
3. This notice may not be removed or altered from any source
|
|
||||||
distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#ifndef CL_MOD_VEGETATION
|
|
||||||
#define CL_MOD_VEGETATION
|
|
||||||
/**
|
|
||||||
* \defgroup grp_vegetation Vegetation : stuff that grows and gets cut down or trampled by dwarves
|
|
||||||
* @ingroup grp_modules
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "Export.h"
|
|
||||||
#include "DataDefs.h"
|
|
||||||
#include "df/plant.h"
|
|
||||||
|
|
||||||
namespace DFHack
|
|
||||||
{
|
|
||||||
namespace Vegetation
|
|
||||||
{
|
|
||||||
const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years
|
|
||||||
|
|
||||||
// "Simplified" copy of plant
|
|
||||||
struct t_plant {
|
|
||||||
df::language_name name;
|
|
||||||
df::plant_flags flags;
|
|
||||||
int16_t material;
|
|
||||||
df::coord pos;
|
|
||||||
int32_t grow_counter;
|
|
||||||
uint16_t temperature_1;
|
|
||||||
uint16_t temperature_2;
|
|
||||||
int32_t is_burning;
|
|
||||||
int32_t hitpoints;
|
|
||||||
int16_t update_order;
|
|
||||||
//std::vector<void *> unk1;
|
|
||||||
//int32_t unk2;
|
|
||||||
//uint16_t temperature_3;
|
|
||||||
//uint16_t temperature_4;
|
|
||||||
//uint16_t temperature_5;
|
|
||||||
// Pointer to original object, in case you want to modify it
|
|
||||||
df::plant *origin;
|
|
||||||
};
|
|
||||||
|
|
||||||
DFHACK_EXPORT bool isValid();
|
|
||||||
DFHACK_EXPORT uint32_t getCount();
|
|
||||||
DFHACK_EXPORT df::plant * getPlant(const int32_t index);
|
|
||||||
DFHACK_EXPORT bool copyPlant (const int32_t index, t_plant &out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
@ -0,0 +1,121 @@
|
|||||||
|
-- Simple binary patch with IDA dif file support.
|
||||||
|
|
||||||
|
local function load_patch(name)
|
||||||
|
local filename = name
|
||||||
|
if not string.match(filename, '[./\\]') then
|
||||||
|
filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif'
|
||||||
|
end
|
||||||
|
|
||||||
|
local file, err = io.open(filename, 'r')
|
||||||
|
if not file then
|
||||||
|
if string.match(err, ': No such file or directory') then
|
||||||
|
return nil, 'patch not found'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local old_bytes = {}
|
||||||
|
local new_bytes = {}
|
||||||
|
|
||||||
|
for line in file:lines() do
|
||||||
|
if string.match(line, '^%x+:') then
|
||||||
|
local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$')
|
||||||
|
if not offset then
|
||||||
|
file:close()
|
||||||
|
return nil, 'could not parse: '..line
|
||||||
|
end
|
||||||
|
|
||||||
|
offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16)
|
||||||
|
if oldv > 255 or newv > 255 then
|
||||||
|
file:close()
|
||||||
|
return nil, 'invalid byte values: '..line
|
||||||
|
end
|
||||||
|
|
||||||
|
old_bytes[offset] = oldv
|
||||||
|
new_bytes[offset] = newv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rebase_table(input)
|
||||||
|
local output = {}
|
||||||
|
local base = dfhack.internal.getImageBase()
|
||||||
|
for k,v in pairs(input) do
|
||||||
|
local offset = dfhack.internal.adjustOffset(k)
|
||||||
|
if not offset then
|
||||||
|
return nil, string.format('invalid offset: %x', k)
|
||||||
|
end
|
||||||
|
output[base + offset] = v
|
||||||
|
end
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rebase_patch(patch)
|
||||||
|
local nold, err = rebase_table(patch.old_bytes)
|
||||||
|
if not nold then return nil, err end
|
||||||
|
local nnew, err = rebase_table(patch.new_bytes)
|
||||||
|
if not nnew then return nil, err end
|
||||||
|
return { name = patch.name, old_bytes = nold, new_bytes = nnew }
|
||||||
|
end
|
||||||
|
|
||||||
|
BinaryPatch = defclass(BinaryPatch)
|
||||||
|
|
||||||
|
BinaryPatch.ATTRS {
|
||||||
|
name = DEFAULT_NIL,
|
||||||
|
old_bytes = DEFAULT_NIL,
|
||||||
|
new_bytes = DEFAULT_NIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_dif_file(name)
|
||||||
|
local patch, err = load_patch(name)
|
||||||
|
if not patch then return nil, err end
|
||||||
|
|
||||||
|
local rpatch, err = rebase_patch(patch)
|
||||||
|
if not rpatch then return nil, err end
|
||||||
|
|
||||||
|
return BinaryPatch(rpatch)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BinaryPatch:status()
|
||||||
|
local old_ok, err, addr = dfhack.internal.patchBytes({}, self.old_bytes)
|
||||||
|
if old_ok then
|
||||||
|
return 'removed'
|
||||||
|
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
|
||||||
|
return 'applied'
|
||||||
|
else
|
||||||
|
return 'conflict', addr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BinaryPatch:isApplied()
|
||||||
|
return dfhack.internal.patchBytes({}, self.new_bytes)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BinaryPatch:apply()
|
||||||
|
local ok, err, addr = dfhack.internal.patchBytes(self.new_bytes, self.old_bytes)
|
||||||
|
if ok then
|
||||||
|
return true, 'applied the patch'
|
||||||
|
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
|
||||||
|
return true, 'patch is already applied'
|
||||||
|
else
|
||||||
|
return false, string.format('conflict at address %x', addr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BinaryPatch:isRemoved()
|
||||||
|
return dfhack.internal.patchBytes({}, self.old_bytes)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BinaryPatch:remove()
|
||||||
|
local ok, err, addr = dfhack.internal.patchBytes(self.old_bytes, self.new_bytes)
|
||||||
|
if ok then
|
||||||
|
return true, 'removed the patch'
|
||||||
|
elseif dfhack.internal.patchBytes({}, self.old_bytes) then
|
||||||
|
return true, 'patch is already removed'
|
||||||
|
else
|
||||||
|
return false, string.format('conflict at address %x', addr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV
|
@ -0,0 +1,337 @@
|
|||||||
|
-- Stock dialog for selecting materials
|
||||||
|
|
||||||
|
local _ENV = mkmodule('gui.materials')
|
||||||
|
|
||||||
|
local gui = require('gui')
|
||||||
|
local widgets = require('gui.widgets')
|
||||||
|
local dlg = require('gui.dialogs')
|
||||||
|
local utils = require('utils')
|
||||||
|
|
||||||
|
ARROW = string.char(26)
|
||||||
|
|
||||||
|
CREATURE_BASE = 19
|
||||||
|
PLANT_BASE = 419
|
||||||
|
|
||||||
|
MaterialDialog = defclass(MaterialDialog, gui.FramedScreen)
|
||||||
|
|
||||||
|
MaterialDialog.focus_path = 'MaterialDialog'
|
||||||
|
|
||||||
|
MaterialDialog.ATTRS{
|
||||||
|
prompt = 'Type or select a material from this list',
|
||||||
|
frame_style = gui.GREY_LINE_FRAME,
|
||||||
|
frame_inset = 1,
|
||||||
|
frame_title = 'Select Material',
|
||||||
|
-- new attrs
|
||||||
|
none_caption = 'none',
|
||||||
|
hide_none = false,
|
||||||
|
use_inorganic = true,
|
||||||
|
use_creature = true,
|
||||||
|
use_plant = true,
|
||||||
|
mat_filter = DEFAULT_NIL,
|
||||||
|
on_select = DEFAULT_NIL,
|
||||||
|
on_cancel = DEFAULT_NIL,
|
||||||
|
on_close = DEFAULT_NIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
function MaterialDialog:init(info)
|
||||||
|
self:addviews{
|
||||||
|
widgets.Label{
|
||||||
|
text = {
|
||||||
|
self.prompt, '\n\n',
|
||||||
|
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
|
||||||
|
},
|
||||||
|
text_pen = COLOR_WHITE,
|
||||||
|
frame = { l = 0, t = 0 },
|
||||||
|
},
|
||||||
|
widgets.Label{
|
||||||
|
view_id = 'back',
|
||||||
|
visible = false,
|
||||||
|
text = { { key = 'LEAVESCREEN', text = ': Back' } },
|
||||||
|
frame = { r = 0, b = 0 },
|
||||||
|
auto_width = true,
|
||||||
|
},
|
||||||
|
widgets.FilteredList{
|
||||||
|
view_id = 'list',
|
||||||
|
not_found_label = 'No matching materials',
|
||||||
|
frame = { l = 0, r = 0, t = 4, b = 2 },
|
||||||
|
icon_width = 2,
|
||||||
|
on_submit = self:callback('onSubmitItem'),
|
||||||
|
},
|
||||||
|
widgets.Label{
|
||||||
|
text = { {
|
||||||
|
key = 'SELECT', text = ': Select',
|
||||||
|
disabled = function() return not self.subviews.list:canSubmit() end
|
||||||
|
} },
|
||||||
|
frame = { l = 0, b = 0 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self:initBuiltinMode()
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:getWantedFrameSize(rect)
|
||||||
|
return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:onDestroy()
|
||||||
|
if self.on_close then
|
||||||
|
self.on_close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:initBuiltinMode()
|
||||||
|
local choices = {}
|
||||||
|
if not self.hide_none then
|
||||||
|
table.insert(choices, { text = self.none_caption, mat_type = -1, mat_index = -1 })
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.use_inorganic then
|
||||||
|
table.insert(choices, {
|
||||||
|
icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I',
|
||||||
|
cb = self:callback('initInorganicMode')
|
||||||
|
})
|
||||||
|
end
|
||||||
|
if self.use_creature then
|
||||||
|
table.insert(choices, {
|
||||||
|
icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C',
|
||||||
|
cb = self:callback('initCreatureMode')
|
||||||
|
})
|
||||||
|
end
|
||||||
|
if self.use_plant then
|
||||||
|
table.insert(choices, {
|
||||||
|
icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P',
|
||||||
|
cb = self:callback('initPlantMode')
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local table = df.global.world.raws.mat_table.builtin
|
||||||
|
for i=0,df.builtin_mats._last_item do
|
||||||
|
self:addMaterial(choices, table[i], i, -1, false, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:pushContext('Any material', choices)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:initInorganicMode()
|
||||||
|
local choices = {}
|
||||||
|
|
||||||
|
for i,mat in ipairs(df.global.world.raws.inorganics) do
|
||||||
|
self:addMaterial(choices, mat.material, 0, i, false, mat)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:pushContext('Inorganic materials', choices)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:initCreatureMode()
|
||||||
|
local choices = {}
|
||||||
|
|
||||||
|
for i,v in ipairs(df.global.world.raws.creatures.all) do
|
||||||
|
self:addObjectChoice(choices, v, v.name[0], CREATURE_BASE, i)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:pushContext('Creature materials', choices)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:initPlantMode()
|
||||||
|
local choices = {}
|
||||||
|
|
||||||
|
for i,v in ipairs(df.global.world.raws.plants.all) do
|
||||||
|
self:addObjectChoice(choices, v, v.name, PLANT_BASE, i)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:pushContext('Plant materials', choices)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:addObjectChoice(choices, obj, name, typ, index)
|
||||||
|
-- Check if any eligible children
|
||||||
|
local count = #obj.material
|
||||||
|
local idx = 0
|
||||||
|
|
||||||
|
if self.mat_filter then
|
||||||
|
count = 0
|
||||||
|
for i,v in ipairs(obj.material) do
|
||||||
|
if self.mat_filter(v, obj, typ+i, index) then
|
||||||
|
count = count + 1
|
||||||
|
if count > 1 then break end
|
||||||
|
idx = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create an entry
|
||||||
|
if count < 1 then
|
||||||
|
return
|
||||||
|
elseif count == 1 then
|
||||||
|
self:addMaterial(choices, obj.material[idx], typ+idx, index, true, obj)
|
||||||
|
else
|
||||||
|
table.insert(choices, {
|
||||||
|
icon = ARROW, text = name, mat_type = typ, mat_index = index,
|
||||||
|
obj = obj, cb = self:callback('onSelectObj')
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:onSelectObj(item)
|
||||||
|
local choices = {}
|
||||||
|
for i,v in ipairs(item.obj.material) do
|
||||||
|
self:addMaterial(choices, v, item.mat_type+i, item.mat_index, false, item.obj)
|
||||||
|
end
|
||||||
|
self:pushContext(item.text, choices)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix, parent)
|
||||||
|
-- Check the filter
|
||||||
|
if self.mat_filter and not self.mat_filter(mat, parent, typ, idx) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find the material name
|
||||||
|
local state = 0
|
||||||
|
if mat.heat.melting_point <= 10015 then
|
||||||
|
state = 1
|
||||||
|
end
|
||||||
|
local name = mat.state_name[state]
|
||||||
|
name = string.gsub(name, '^frozen ','')
|
||||||
|
name = string.gsub(name, '^molten ','')
|
||||||
|
name = string.gsub(name, '^condensed ','')
|
||||||
|
|
||||||
|
-- Add prefix if requested
|
||||||
|
local key
|
||||||
|
if pfix and mat.prefix ~= '' then
|
||||||
|
name = mat.prefix .. ' ' .. name
|
||||||
|
key = mat.prefix
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(choices, {
|
||||||
|
text = name,
|
||||||
|
search_key = key,
|
||||||
|
material = mat,
|
||||||
|
mat_type = typ, mat_index = idx
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:pushContext(name, choices)
|
||||||
|
if not self.back_stack then
|
||||||
|
self.back_stack = {}
|
||||||
|
self.subviews.back.visible = false
|
||||||
|
else
|
||||||
|
table.insert(self.back_stack, {
|
||||||
|
context_str = self.context_str,
|
||||||
|
all_choices = self.subviews.list:getChoices(),
|
||||||
|
edit_text = self.subviews.list:getFilter(),
|
||||||
|
selected = self.subviews.list:getSelected(),
|
||||||
|
})
|
||||||
|
self.subviews.back.visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
self.context_str = name
|
||||||
|
self.subviews.list:setChoices(choices, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:onGoBack()
|
||||||
|
local save = table.remove(self.back_stack)
|
||||||
|
self.subviews.back.visible = (#self.back_stack > 0)
|
||||||
|
|
||||||
|
self.context_str = save.context_str
|
||||||
|
self.subviews.list:setChoices(save.all_choices)
|
||||||
|
self.subviews.list:setFilter(save.edit_text, save.selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:submitMaterial(typ, index)
|
||||||
|
self:dismiss()
|
||||||
|
|
||||||
|
if self.on_select then
|
||||||
|
self.on_select(typ, index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:onSubmitItem(idx, item)
|
||||||
|
if item.cb then
|
||||||
|
item:cb(idx)
|
||||||
|
else
|
||||||
|
self:submitMaterial(item.mat_type, item.mat_index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MaterialDialog:onInput(keys)
|
||||||
|
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
|
||||||
|
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
|
||||||
|
self:onGoBack()
|
||||||
|
else
|
||||||
|
self:dismiss()
|
||||||
|
if self.on_cancel then
|
||||||
|
self.on_cancel()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self:inputToSubviews(keys)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter)
|
||||||
|
MaterialDialog{
|
||||||
|
frame_title = title,
|
||||||
|
prompt = prompt,
|
||||||
|
mat_filter = mat_filter,
|
||||||
|
on_select = on_select,
|
||||||
|
on_cancel = on_cancel,
|
||||||
|
}:show()
|
||||||
|
end
|
||||||
|
|
||||||
|
function ItemTypeDialog(args)
|
||||||
|
args.text = args.prompt or 'Type or select an item type'
|
||||||
|
args.text_pen = COLOR_WHITE
|
||||||
|
args.with_filter = true
|
||||||
|
args.icon_width = 2
|
||||||
|
|
||||||
|
local choices = {}
|
||||||
|
|
||||||
|
if not args.hide_none then
|
||||||
|
table.insert(choices, {
|
||||||
|
icon = '?', text = args.none_caption or 'none',
|
||||||
|
item_type = -1, item_subtype = -1
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local filter = args.item_filter
|
||||||
|
|
||||||
|
for itype = 0,df.item_type._last_item do
|
||||||
|
local attrs = df.item_type.attrs[itype]
|
||||||
|
local defcnt = dfhack.items.getSubtypeCount(itype)
|
||||||
|
|
||||||
|
if not filter or filter(itype,-1) then
|
||||||
|
local name = attrs.caption or df.item_type[itype]
|
||||||
|
local icon
|
||||||
|
if defcnt >= 0 then
|
||||||
|
name = 'any '..name
|
||||||
|
icon = '+'
|
||||||
|
end
|
||||||
|
table.insert(choices, {
|
||||||
|
icon = icon, text = string.lower(name), item_type = itype, item_subtype = -1
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if defcnt > 0 then
|
||||||
|
for subtype = 0,defcnt-1 do
|
||||||
|
local def = dfhack.items.getSubtypeDef(itype, subtype)
|
||||||
|
if not filter or filter(itype,subtype,def) then
|
||||||
|
table.insert(choices, {
|
||||||
|
icon = '\x1e', text = ' '..def.name, item_type = itype, item_subtype = subtype
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
args.choices = choices
|
||||||
|
|
||||||
|
if args.on_select then
|
||||||
|
local cb = args.on_select
|
||||||
|
args.on_select = function(idx, obj)
|
||||||
|
return cb(obj.item_type, obj.item_subtype)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return dlg.ListBox(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV
|
@ -0,0 +1,164 @@
|
|||||||
|
-- Support for scripted interaction sequences via coroutines.
|
||||||
|
|
||||||
|
local _ENV = mkmodule('gui.script')
|
||||||
|
|
||||||
|
local dlg = require('gui.dialogs')
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Example:
|
||||||
|
|
||||||
|
start(function()
|
||||||
|
sleep(100, 'frames')
|
||||||
|
print(showYesNoPrompt('test', 'print true?'))
|
||||||
|
end)
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Table of running background scripts.
|
||||||
|
if not scripts then
|
||||||
|
scripts = {}
|
||||||
|
setmetatable(scripts, { __mode = 'k' })
|
||||||
|
end
|
||||||
|
|
||||||
|
local function do_resume(inst, ...)
|
||||||
|
inst.gen = inst.gen + 1
|
||||||
|
return (dfhack.saferesume(inst.coro, ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Starts a new background script by calling the function.
|
||||||
|
function start(fn,...)
|
||||||
|
local coro = coroutine.create(fn)
|
||||||
|
local inst = {
|
||||||
|
coro = coro,
|
||||||
|
gen = 0,
|
||||||
|
}
|
||||||
|
scripts[coro] = inst
|
||||||
|
return do_resume(inst, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Checks if called from a background script
|
||||||
|
function in_script()
|
||||||
|
return scripts[coroutine.running()] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getinst()
|
||||||
|
local inst = scripts[coroutine.running()]
|
||||||
|
if not inst then
|
||||||
|
error('Not in a gui script coroutine.')
|
||||||
|
end
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
|
local function invoke_resume(inst,gen,quiet,...)
|
||||||
|
local state = coroutine.status(inst.coro)
|
||||||
|
if state ~= 'suspended' then
|
||||||
|
if state ~= 'dead' then
|
||||||
|
dfhack.printerr(debug.traceback('resuming a non-waiting continuation'))
|
||||||
|
end
|
||||||
|
elseif inst.gen > gen then
|
||||||
|
if not quiet then
|
||||||
|
dfhack.printerr(debug.traceback('resuming an expired continuation'))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
do_resume(inst, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns a callback that resumes the script from wait with given return values
|
||||||
|
function mkresume(...)
|
||||||
|
local inst = getinst()
|
||||||
|
return curry(invoke_resume, inst, inst.gen, false, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Like mkresume, but does not complain if already resumed from this wait
|
||||||
|
function qresume(...)
|
||||||
|
local inst = getinst()
|
||||||
|
return curry(invoke_resume, inst, inst.gen, true, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Wait until a mkresume callback is called, then return its arguments.
|
||||||
|
-- Once it returns, all mkresume callbacks created before are invalidated.
|
||||||
|
function wait()
|
||||||
|
getinst() -- check that the context is right
|
||||||
|
return coroutine.yield()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Wraps dfhack.timeout for coroutines.
|
||||||
|
function sleep(time, quantity)
|
||||||
|
if dfhack.timeout(time, quantity, mkresume()) then
|
||||||
|
wait()
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Some dialog wrappers:
|
||||||
|
|
||||||
|
function showMessage(title, text, tcolor)
|
||||||
|
dlg.MessageBox{
|
||||||
|
frame_title = title,
|
||||||
|
text = text,
|
||||||
|
text_pen = tcolor,
|
||||||
|
on_close = qresume(nil)
|
||||||
|
}:show()
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
function showYesNoPrompt(title, text, tcolor)
|
||||||
|
dlg.MessageBox{
|
||||||
|
frame_title = title,
|
||||||
|
text = text,
|
||||||
|
text_pen = tcolor,
|
||||||
|
on_accept = mkresume(true),
|
||||||
|
on_cancel = mkresume(false),
|
||||||
|
on_close = qresume(nil)
|
||||||
|
}:show()
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
function showInputPrompt(title, text, tcolor, input, min_width)
|
||||||
|
dlg.InputBox{
|
||||||
|
frame_title = title,
|
||||||
|
text = text,
|
||||||
|
text_pen = tcolor,
|
||||||
|
input = input,
|
||||||
|
frame_width = min_width,
|
||||||
|
on_input = mkresume(true),
|
||||||
|
on_cancel = mkresume(false),
|
||||||
|
on_close = qresume(nil)
|
||||||
|
}:show()
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
function showListPrompt(title, text, tcolor, choices, min_width, filter)
|
||||||
|
dlg.ListBox{
|
||||||
|
frame_title = title,
|
||||||
|
text = text,
|
||||||
|
text_pen = tcolor,
|
||||||
|
choices = choices,
|
||||||
|
frame_width = min_width,
|
||||||
|
with_filter = filter,
|
||||||
|
on_select = mkresume(true),
|
||||||
|
on_cancel = mkresume(false),
|
||||||
|
on_close = qresume(nil)
|
||||||
|
}:show()
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
function showMaterialPrompt(title, prompt)
|
||||||
|
require('gui.materials').MaterialDialog{
|
||||||
|
frame_title = title,
|
||||||
|
prompt = prompt,
|
||||||
|
on_select = mkresume(true,
|
||||||
|
on_cancel = mkresume(false),
|
||||||
|
on_close = qresume(nil)
|
||||||
|
}:show()
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV
|
@ -0,0 +1,783 @@
|
|||||||
|
-- Simple widgets for screens
|
||||||
|
|
||||||
|
local _ENV = mkmodule('gui.widgets')
|
||||||
|
|
||||||
|
local gui = require('gui')
|
||||||
|
local utils = require('utils')
|
||||||
|
|
||||||
|
local dscreen = dfhack.screen
|
||||||
|
|
||||||
|
local function show_view(view,vis)
|
||||||
|
if view then
|
||||||
|
view.visible = vis
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getval(obj)
|
||||||
|
if type(obj) == 'function' then
|
||||||
|
return obj()
|
||||||
|
else
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function map_opttab(tab,idx)
|
||||||
|
if tab then
|
||||||
|
return tab[idx]
|
||||||
|
else
|
||||||
|
return idx
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
------------
|
||||||
|
-- Widget --
|
||||||
|
------------
|
||||||
|
|
||||||
|
Widget = defclass(Widget, gui.View)
|
||||||
|
|
||||||
|
Widget.ATTRS {
|
||||||
|
frame = DEFAULT_NIL,
|
||||||
|
frame_inset = DEFAULT_NIL,
|
||||||
|
frame_background = DEFAULT_NIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Widget:computeFrame(parent_rect)
|
||||||
|
local sw, sh = parent_rect.width, parent_rect.height
|
||||||
|
return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:onRenderFrame(dc, rect)
|
||||||
|
if self.frame_background then
|
||||||
|
dc:fill(rect, self.frame_background)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------
|
||||||
|
-- Panel --
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Panel = defclass(Panel, Widget)
|
||||||
|
|
||||||
|
Panel.ATTRS {
|
||||||
|
on_render = DEFAULT_NIL,
|
||||||
|
on_layout = DEFAULT_NIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Panel:init(args)
|
||||||
|
self:addviews(args.subviews)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Panel:onRenderBody(dc)
|
||||||
|
if self.on_render then self.on_render(dc) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Panel:postComputeFrame(body)
|
||||||
|
if self.on_layout then self.on_layout(body) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------
|
||||||
|
-- Pages --
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Pages = defclass(Pages, Panel)
|
||||||
|
|
||||||
|
function Pages:init(args)
|
||||||
|
for _,v in ipairs(self.subviews) do
|
||||||
|
v.visible = false
|
||||||
|
end
|
||||||
|
self:setSelected(args.selected or 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Pages:setSelected(idx)
|
||||||
|
if type(idx) ~= 'number' then
|
||||||
|
local key = idx
|
||||||
|
if type(idx) == 'string' then
|
||||||
|
key = self.subviews[key]
|
||||||
|
end
|
||||||
|
idx = utils.linear_index(self.subviews, key)
|
||||||
|
if not idx then
|
||||||
|
error('Unknown page: '..key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
show_view(self.subviews[self.selected], false)
|
||||||
|
self.selected = math.min(math.max(1, idx), #self.subviews)
|
||||||
|
show_view(self.subviews[self.selected], true)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Pages:getSelected()
|
||||||
|
return self.selected, self.subviews[self.selected]
|
||||||
|
end
|
||||||
|
|
||||||
|
----------------
|
||||||
|
-- Edit field --
|
||||||
|
----------------
|
||||||
|
|
||||||
|
EditField = defclass(EditField, Widget)
|
||||||
|
|
||||||
|
EditField.ATTRS{
|
||||||
|
text = '',
|
||||||
|
text_pen = DEFAULT_NIL,
|
||||||
|
on_char = DEFAULT_NIL,
|
||||||
|
on_change = DEFAULT_NIL,
|
||||||
|
on_submit = DEFAULT_NIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditField:onRenderBody(dc)
|
||||||
|
dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0)
|
||||||
|
|
||||||
|
local cursor = '_'
|
||||||
|
if not self.active or gui.blink_visible(300) then
|
||||||
|
cursor = ' '
|
||||||
|
end
|
||||||
|
local txt = self.text .. cursor
|
||||||
|
if #txt > dc.width then
|
||||||
|
txt = string.char(27)..string.sub(txt, #txt-dc.width+2)
|
||||||
|
end
|
||||||
|
dc:string(txt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function EditField:onInput(keys)
|
||||||
|
if self.on_submit and keys.SELECT then
|
||||||
|
self.on_submit(self.text)
|
||||||
|
return true
|
||||||
|
elseif keys._STRING then
|
||||||
|
local old = self.text
|
||||||
|
if keys._STRING == 0 then
|
||||||
|
self.text = string.sub(old, 1, #old-1)
|
||||||
|
else
|
||||||
|
local cv = string.char(keys._STRING)
|
||||||
|
if not self.on_char or self.on_char(cv, old) then
|
||||||
|
self.text = old .. cv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.on_change and self.text ~= old then
|
||||||
|
self.on_change(self.text, old)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------
|
||||||
|
-- Label --
|
||||||
|
-----------
|
||||||
|
|
||||||
|
function parse_label_text(obj)
|
||||||
|
local text = obj.text or {}
|
||||||
|
if type(text) ~= 'table' then
|
||||||
|
text = { text }
|
||||||
|
end
|
||||||
|
local curline = nil
|
||||||
|
local out = { }
|
||||||
|
local active = nil
|
||||||
|
local idtab = nil
|
||||||
|
for _,v in ipairs(text) do
|
||||||
|
local vv
|
||||||
|
if type(v) == 'string' then
|
||||||
|
vv = utils.split_string(v, NEWLINE)
|
||||||
|
else
|
||||||
|
vv = { v }
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1,#vv do
|
||||||
|
local cv = vv[i]
|
||||||
|
if i > 1 then
|
||||||
|
if not curline then
|
||||||
|
table.insert(out, {})
|
||||||
|
end
|
||||||
|
curline = nil
|
||||||
|
end
|
||||||
|
if cv ~= '' then
|
||||||
|
if not curline then
|
||||||
|
curline = {}
|
||||||
|
table.insert(out, curline)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(cv) == 'string' then
|
||||||
|
table.insert(curline, { text = cv })
|
||||||
|
else
|
||||||
|
table.insert(curline, cv)
|
||||||
|
|
||||||
|
if cv.on_activate then
|
||||||
|
active = active or {}
|
||||||
|
table.insert(active, cv)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cv.id then
|
||||||
|
idtab = idtab or {}
|
||||||
|
idtab[cv.id] = cv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
obj.text_lines = out
|
||||||
|
obj.text_active = active
|
||||||
|
obj.text_ids = idtab
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_disabled(token)
|
||||||
|
return (token.disabled ~= nil and getval(token.disabled)) or
|
||||||
|
(token.enabled ~= nil and not getval(token.enabled))
|
||||||
|
end
|
||||||
|
|
||||||
|
function render_text(obj,dc,x0,y0,pen,dpen,disabled)
|
||||||
|
local width = 0
|
||||||
|
for iline,line in ipairs(obj.text_lines) do
|
||||||
|
local x = 0
|
||||||
|
if dc then
|
||||||
|
dc:seek(x+x0,y0+iline-1)
|
||||||
|
end
|
||||||
|
for _,token in ipairs(line) do
|
||||||
|
token.line = iline
|
||||||
|
token.x1 = x
|
||||||
|
|
||||||
|
if token.gap then
|
||||||
|
x = x + token.gap
|
||||||
|
if dc then
|
||||||
|
dc:advance(token.gap)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if token.tile then
|
||||||
|
x = x + 1
|
||||||
|
if dc then
|
||||||
|
dc:char(nil, token.tile)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if token.text or token.key then
|
||||||
|
local text = ''..(getval(token.text) or '')
|
||||||
|
local keypen
|
||||||
|
|
||||||
|
if dc then
|
||||||
|
local tpen = getval(token.pen)
|
||||||
|
if disabled or is_disabled(token) then
|
||||||
|
dc:pen(getval(token.dpen) or tpen or dpen)
|
||||||
|
keypen = COLOR_GREEN
|
||||||
|
else
|
||||||
|
dc:pen(tpen or pen)
|
||||||
|
keypen = COLOR_LIGHTGREEN
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local width = getval(token.width)
|
||||||
|
local padstr
|
||||||
|
if width then
|
||||||
|
x = x + width
|
||||||
|
if #text > width then
|
||||||
|
text = string.sub(text,1,width)
|
||||||
|
else
|
||||||
|
if token.pad_char then
|
||||||
|
padstr = string.rep(token.pad_char,width-#text)
|
||||||
|
end
|
||||||
|
if dc and token.rjustify then
|
||||||
|
if padstr then dc:string(padstr) else dc:advance(width-#text) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
x = x + #text
|
||||||
|
end
|
||||||
|
|
||||||
|
if token.key then
|
||||||
|
local keystr = gui.getKeyDisplay(token.key)
|
||||||
|
local sep = token.key_sep or ''
|
||||||
|
|
||||||
|
x = x + #keystr
|
||||||
|
|
||||||
|
if sep == '()' then
|
||||||
|
if dc then
|
||||||
|
dc:string(text)
|
||||||
|
dc:string(' ('):string(keystr,keypen):string(')')
|
||||||
|
end
|
||||||
|
x = x + 3
|
||||||
|
else
|
||||||
|
if dc then
|
||||||
|
dc:string(keystr,keypen):string(sep):string(text)
|
||||||
|
end
|
||||||
|
x = x + #sep
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if dc then
|
||||||
|
dc:string(text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if width and dc and not token.rjustify then
|
||||||
|
if padstr then dc:string(padstr) else dc:advance(width-#text) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
token.x2 = x
|
||||||
|
end
|
||||||
|
width = math.max(width, x)
|
||||||
|
end
|
||||||
|
obj.text_width = width
|
||||||
|
end
|
||||||
|
|
||||||
|
function check_text_keys(self, keys)
|
||||||
|
if self.text_active then
|
||||||
|
for _,item in ipairs(self.text_active) do
|
||||||
|
if item.key and keys[item.key] and not is_disabled(item) then
|
||||||
|
item.on_activate()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Label = defclass(Label, Widget)
|
||||||
|
|
||||||
|
Label.ATTRS{
|
||||||
|
text_pen = COLOR_WHITE,
|
||||||
|
text_dpen = COLOR_DARKGREY,
|
||||||
|
disabled = DEFAULT_NIL,
|
||||||
|
enabled = DEFAULT_NIL,
|
||||||
|
auto_height = true,
|
||||||
|
auto_width = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Label:init(args)
|
||||||
|
self:setText(args.text)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Label:setText(text)
|
||||||
|
self.text = text
|
||||||
|
parse_label_text(self)
|
||||||
|
|
||||||
|
if self.auto_height then
|
||||||
|
self.frame = self.frame or {}
|
||||||
|
self.frame.h = self:getTextHeight()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Label:preUpdateLayout()
|
||||||
|
if self.auto_width then
|
||||||
|
self.frame = self.frame or {}
|
||||||
|
self.frame.w = self:getTextWidth()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Label:itemById(id)
|
||||||
|
if self.text_ids then
|
||||||
|
return self.text_ids[id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Label:getTextHeight()
|
||||||
|
return #self.text_lines
|
||||||
|
end
|
||||||
|
|
||||||
|
function Label:getTextWidth()
|
||||||
|
render_text(self)
|
||||||
|
return self.text_width
|
||||||
|
end
|
||||||
|
|
||||||
|
function Label:onRenderBody(dc)
|
||||||
|
render_text(self,dc,0,0,self.text_pen,self.text_dpen,is_disabled(self))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Label:onInput(keys)
|
||||||
|
if not is_disabled(self) then
|
||||||
|
return check_text_keys(self, keys)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
----------
|
||||||
|
-- List --
|
||||||
|
----------
|
||||||
|
|
||||||
|
List = defclass(List, Widget)
|
||||||
|
|
||||||
|
STANDARDSCROLL = {
|
||||||
|
STANDARDSCROLL_UP = -1,
|
||||||
|
STANDARDSCROLL_DOWN = 1,
|
||||||
|
STANDARDSCROLL_PAGEUP = '-page',
|
||||||
|
STANDARDSCROLL_PAGEDOWN = '+page',
|
||||||
|
}
|
||||||
|
|
||||||
|
SECONDSCROLL = {
|
||||||
|
SECONDSCROLL_UP = -1,
|
||||||
|
SECONDSCROLL_DOWN = 1,
|
||||||
|
SECONDSCROLL_PAGEUP = '-page',
|
||||||
|
SECONDSCROLL_PAGEDOWN = '+page',
|
||||||
|
}
|
||||||
|
|
||||||
|
List.ATTRS{
|
||||||
|
text_pen = COLOR_CYAN,
|
||||||
|
cursor_pen = COLOR_LIGHTCYAN,
|
||||||
|
inactive_pen = DEFAULT_NIL,
|
||||||
|
on_select = DEFAULT_NIL,
|
||||||
|
on_submit = DEFAULT_NIL,
|
||||||
|
on_submit2 = DEFAULT_NIL,
|
||||||
|
row_height = 1,
|
||||||
|
scroll_keys = STANDARDSCROLL,
|
||||||
|
icon_width = DEFAULT_NIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
function List:init(info)
|
||||||
|
self.page_top = 1
|
||||||
|
self.page_size = 1
|
||||||
|
|
||||||
|
if info.choices then
|
||||||
|
self:setChoices(info.choices, info.selected)
|
||||||
|
else
|
||||||
|
self.choices = {}
|
||||||
|
self.selected = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:setChoices(choices, selected)
|
||||||
|
self.choices = choices or {}
|
||||||
|
|
||||||
|
for i,v in ipairs(self.choices) do
|
||||||
|
if type(v) ~= 'table' then
|
||||||
|
v = { text = v }
|
||||||
|
self.choices[i] = v
|
||||||
|
end
|
||||||
|
v.text = v.text or v.caption or v[1]
|
||||||
|
parse_label_text(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:setSelected(selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:setSelected(selected)
|
||||||
|
self.selected = selected or self.selected or 1
|
||||||
|
self:moveCursor(0, true)
|
||||||
|
return self.selected
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:getChoices()
|
||||||
|
return self.choices
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:getSelected()
|
||||||
|
if #self.choices > 0 then
|
||||||
|
return self.selected, self.choices[self.selected]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:getContentWidth()
|
||||||
|
local width = 0
|
||||||
|
for i,v in ipairs(self.choices) do
|
||||||
|
render_text(v)
|
||||||
|
local roww = v.text_width
|
||||||
|
if v.key then
|
||||||
|
roww = roww + 3 + #gui.getKeyDisplay(v.key)
|
||||||
|
end
|
||||||
|
width = math.max(width, roww)
|
||||||
|
end
|
||||||
|
return width + (self.icon_width or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:getContentHeight()
|
||||||
|
return #self.choices * self.row_height
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:postComputeFrame(body)
|
||||||
|
self.page_size = math.max(1, math.floor(body.height / self.row_height))
|
||||||
|
self:moveCursor(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:moveCursor(delta, force_cb)
|
||||||
|
local page = math.max(1, self.page_size)
|
||||||
|
local cnt = #self.choices
|
||||||
|
|
||||||
|
if cnt < 1 then
|
||||||
|
self.page_top = 1
|
||||||
|
self.selected = 1
|
||||||
|
if force_cb and self.on_select then
|
||||||
|
self.on_select(nil,nil)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local off = self.selected+delta-1
|
||||||
|
local ds = math.abs(delta)
|
||||||
|
|
||||||
|
if ds > 1 then
|
||||||
|
if off >= cnt+ds-1 then
|
||||||
|
off = 0
|
||||||
|
else
|
||||||
|
off = math.min(cnt-1, off)
|
||||||
|
end
|
||||||
|
if off <= -ds then
|
||||||
|
off = cnt-1
|
||||||
|
else
|
||||||
|
off = math.max(0, off)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.selected = 1 + off % cnt
|
||||||
|
self.page_top = 1 + page * math.floor((self.selected-1) / page)
|
||||||
|
|
||||||
|
if (force_cb or delta ~= 0) and self.on_select then
|
||||||
|
self.on_select(self:getSelected())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:onRenderBody(dc)
|
||||||
|
local choices = self.choices
|
||||||
|
local top = self.page_top
|
||||||
|
local iend = math.min(#choices, top+self.page_size-1)
|
||||||
|
local iw = self.icon_width
|
||||||
|
|
||||||
|
local function paint_icon(icon, obj)
|
||||||
|
if type(icon) ~= 'string' then
|
||||||
|
dc:char(nil,icon)
|
||||||
|
else
|
||||||
|
if current then
|
||||||
|
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
|
||||||
|
else
|
||||||
|
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = top,iend do
|
||||||
|
local obj = choices[i]
|
||||||
|
local current = (i == self.selected)
|
||||||
|
local cur_pen = self.cursor_pen
|
||||||
|
local cur_dpen = self.text_pen
|
||||||
|
|
||||||
|
if not self.active then
|
||||||
|
cur_pen = self.inactive_pen or self.cursor_pen
|
||||||
|
end
|
||||||
|
|
||||||
|
local y = (i - top)*self.row_height
|
||||||
|
local icon = getval(obj.icon)
|
||||||
|
|
||||||
|
if iw and icon then
|
||||||
|
dc:seek(0, y)
|
||||||
|
paint_icon(icon, obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
|
||||||
|
|
||||||
|
local ip = dc.width
|
||||||
|
|
||||||
|
if obj.key then
|
||||||
|
local keystr = gui.getKeyDisplay(obj.key)
|
||||||
|
ip = ip-2-#keystr
|
||||||
|
dc:seek(ip,y):pen(self.text_pen)
|
||||||
|
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
|
||||||
|
end
|
||||||
|
|
||||||
|
if icon and not iw then
|
||||||
|
dc:seek(ip-1,y)
|
||||||
|
paint_icon(icon, obj)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:submit()
|
||||||
|
if self.on_submit and #self.choices > 0 then
|
||||||
|
self.on_submit(self:getSelected())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:submit2()
|
||||||
|
if self.on_submit2 and #self.choices > 0 then
|
||||||
|
self.on_submit2(self:getSelected())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function List:onInput(keys)
|
||||||
|
if self.on_submit and keys.SELECT then
|
||||||
|
self:submit()
|
||||||
|
return true
|
||||||
|
elseif self.on_submit2 and keys.SEC_SELECT then
|
||||||
|
self:submit2()
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
for k,v in pairs(self.scroll_keys) do
|
||||||
|
if keys[k] then
|
||||||
|
if v == '+page' then
|
||||||
|
v = self.page_size
|
||||||
|
elseif v == '-page' then
|
||||||
|
v = -self.page_size
|
||||||
|
end
|
||||||
|
|
||||||
|
self:moveCursor(v)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i,v in ipairs(self.choices) do
|
||||||
|
if v.key and keys[v.key] then
|
||||||
|
self:setSelected(i)
|
||||||
|
self:submit()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local current = self.choices[self.selected]
|
||||||
|
if current then
|
||||||
|
return check_text_keys(current, keys)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------
|
||||||
|
-- Filtered List --
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
FilteredList = defclass(FilteredList, Widget)
|
||||||
|
|
||||||
|
FilteredList.ATTRS {
|
||||||
|
edit_below = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
function FilteredList:init(info)
|
||||||
|
self.edit = EditField{
|
||||||
|
text_pen = info.edit_pen or info.cursor_pen,
|
||||||
|
frame = { l = info.icon_width, t = 0, h = 1 },
|
||||||
|
on_change = self:callback('onFilterChange'),
|
||||||
|
on_char = self:callback('onFilterChar'),
|
||||||
|
}
|
||||||
|
self.list = List{
|
||||||
|
frame = { t = 2 },
|
||||||
|
text_pen = info.text_pen,
|
||||||
|
cursor_pen = info.cursor_pen,
|
||||||
|
inactive_pen = info.inactive_pen,
|
||||||
|
icon_pen = info.icon_pen,
|
||||||
|
row_height = info.row_height,
|
||||||
|
scroll_keys = info.scroll_keys,
|
||||||
|
icon_width = info.icon_width,
|
||||||
|
}
|
||||||
|
if self.edit_below then
|
||||||
|
self.edit.frame = { l = info.icon_width, b = 0, h = 1 }
|
||||||
|
self.list.frame = { t = 0, b = 2 }
|
||||||
|
end
|
||||||
|
if info.on_select then
|
||||||
|
self.list.on_select = function()
|
||||||
|
return info.on_select(self:getSelected())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if info.on_submit then
|
||||||
|
self.list.on_submit = function()
|
||||||
|
return info.on_submit(self:getSelected())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if info.on_submit2 then
|
||||||
|
self.list.on_submit2 = function()
|
||||||
|
return info.on_submit2(self:getSelected())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.not_found = Label{
|
||||||
|
visible = true,
|
||||||
|
text = info.not_found_label or 'No matches',
|
||||||
|
text_pen = COLOR_LIGHTRED,
|
||||||
|
frame = { l = info.icon_width, t = self.list.frame.t },
|
||||||
|
}
|
||||||
|
self:addviews{ self.edit, self.list, self.not_found }
|
||||||
|
if info.choices then
|
||||||
|
self:setChoices(info.choices, info.selected)
|
||||||
|
else
|
||||||
|
self.choices = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:getChoices()
|
||||||
|
return self.choices
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:setChoices(choices, pos)
|
||||||
|
choices = choices or {}
|
||||||
|
self.choices = choices
|
||||||
|
self.edit.text = ''
|
||||||
|
self.list:setChoices(choices, pos)
|
||||||
|
self.not_found.visible = (#choices == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:submit()
|
||||||
|
return self.list:submit()
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:submit2()
|
||||||
|
return self.list:submit2()
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:canSubmit()
|
||||||
|
return not self.not_found.visible
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:getSelected()
|
||||||
|
local i,v = self.list:getSelected()
|
||||||
|
if i then
|
||||||
|
return map_opttab(self.choice_index, i), v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:getContentWidth()
|
||||||
|
return self.list:getContentWidth()
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:getContentHeight()
|
||||||
|
return self.list:getContentHeight() + 2
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:getFilter()
|
||||||
|
return self.edit.text, self.list.choices
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:setFilter(filter, pos)
|
||||||
|
local choices = self.choices
|
||||||
|
local cidx = nil
|
||||||
|
|
||||||
|
filter = filter or ''
|
||||||
|
self.edit.text = filter
|
||||||
|
|
||||||
|
if filter ~= '' then
|
||||||
|
local tokens = utils.split_string(filter, ' ')
|
||||||
|
local ipos = pos
|
||||||
|
|
||||||
|
choices = {}
|
||||||
|
cidx = {}
|
||||||
|
pos = nil
|
||||||
|
|
||||||
|
for i,v in ipairs(self.choices) do
|
||||||
|
local ok = true
|
||||||
|
local search_key = v.search_key or v.text
|
||||||
|
for _,key in ipairs(tokens) do
|
||||||
|
if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if ok then
|
||||||
|
table.insert(choices, v)
|
||||||
|
cidx[#choices] = i
|
||||||
|
if ipos == i then
|
||||||
|
pos = #choices
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.choice_index = cidx
|
||||||
|
self.list:setChoices(choices, pos)
|
||||||
|
self.not_found.visible = (#choices == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function FilteredList:onFilterChange(text)
|
||||||
|
self:setFilter(text)
|
||||||
|
end
|
||||||
|
|
||||||
|
local bad_chars = {
|
||||||
|
['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true,
|
||||||
|
['['] = true, [']'] = true, ['('] = true, [')'] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
function FilteredList:onFilterChar(char, text)
|
||||||
|
if bad_chars[char] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if char == ' ' then
|
||||||
|
return string.match(text, '%S$')
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV
|
@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
https://github.com/peterix/dfhack
|
|
||||||
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any
|
|
||||||
damages arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any
|
|
||||||
purpose, including commercial applications, and to alter it and
|
|
||||||
redistribute it freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must
|
|
||||||
not claim that you wrote the original software. If you use this
|
|
||||||
software in a product, an acknowledgment in the product documentation
|
|
||||||
would be appreciated but is not required.
|
|
||||||
|
|
||||||
2. Altered source versions must be plainly marked as such, and
|
|
||||||
must not be misrepresented as being the original software.
|
|
||||||
|
|
||||||
3. This notice may not be removed or altered from any source
|
|
||||||
distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "Internal.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <map>
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
#include "VersionInfo.h"
|
|
||||||
#include "MemAccess.h"
|
|
||||||
#include "Types.h"
|
|
||||||
#include "Core.h"
|
|
||||||
using namespace DFHack;
|
|
||||||
|
|
||||||
#include "modules/Vegetation.h"
|
|
||||||
#include "df/world.h"
|
|
||||||
|
|
||||||
using namespace DFHack;
|
|
||||||
using df::global::world;
|
|
||||||
|
|
||||||
bool Vegetation::isValid()
|
|
||||||
{
|
|
||||||
return (world != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t Vegetation::getCount()
|
|
||||||
{
|
|
||||||
return world->plants.all.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
df::plant * Vegetation::getPlant(const int32_t index)
|
|
||||||
{
|
|
||||||
if (uint32_t(index) >= getCount())
|
|
||||||
return NULL;
|
|
||||||
return world->plants.all[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Vegetation::copyPlant(const int32_t index, t_plant &out)
|
|
||||||
{
|
|
||||||
if (uint32_t(index) >= getCount())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
out.origin = world->plants.all[index];
|
|
||||||
|
|
||||||
out.name = out.origin->name;
|
|
||||||
out.flags = out.origin->flags;
|
|
||||||
out.material = out.origin->material;
|
|
||||||
out.pos = out.origin->pos;
|
|
||||||
out.grow_counter = out.origin->grow_counter;
|
|
||||||
out.temperature_1 = out.origin->temperature_1;
|
|
||||||
out.temperature_2 = out.origin->temperature_2;
|
|
||||||
out.is_burning = out.origin->is_burning;
|
|
||||||
out.hitpoints = out.origin->hitpoints;
|
|
||||||
out.update_order = out.origin->update_order;
|
|
||||||
//out.unk1 = out.origin->anon_1;
|
|
||||||
//out.unk2 = out.origin->anon_2;
|
|
||||||
//out.temperature_3 = out.origin->temperature_3;
|
|
||||||
//out.temperature_4 = out.origin->temperature_4;
|
|
||||||
//out.temperature_5 = out.origin->temperature_5;
|
|
||||||
return true;
|
|
||||||
}
|
|