Merge branch 'master' of https://github.com/angavrilov/dfhack into experimental-dontmerge
commit
6be65690f7
@ -0,0 +1,3 @@
|
||||
FIND_PROGRAM(RST2HTML_EXECUTABLE NAMES rst2html rst2html.py)
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Docutils DEFAULT_MSG RST2HTML_EXECUTABLE)
|
@ -0,0 +1,402 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils 0.9.1: http://docutils.sourceforge.net/" />
|
||||
<title>Contributors</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
|
||||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless, table.borderless td, table.borderless th {
|
||||
border: 0 }
|
||||
|
||||
table.borderless td, table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important }
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important }
|
||||
|
||||
.last, .with-subtitle {
|
||||
margin-bottom: 0 ! important }
|
||||
|
||||
.hidden {
|
||||
display: none }
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none ;
|
||||
color: black }
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em ; }
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em }
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold ;
|
||||
text-align: center }
|
||||
|
||||
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||
div.hint, div.important, div.note, div.tip, div.warning {
|
||||
margin: 2em ;
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||
div.important p.admonition-title, div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||
div.danger p.admonition-title, div.error p.admonition-title,
|
||||
div.warning p.admonition-title {
|
||||
color: red ;
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em ;
|
||||
text-align: center ;
|
||||
font-style: italic }
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold ;
|
||||
font-style: normal }
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
div.footer, div.header {
|
||||
clear: both;
|
||||
font-size: smaller }
|
||||
|
||||
div.line-block {
|
||||
display: block ;
|
||||
margin-top: 1em ;
|
||||
margin-bottom: 1em }
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0 ;
|
||||
margin-bottom: 0 ;
|
||||
margin-left: 1.5em }
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em ;
|
||||
border: medium outset ;
|
||||
padding: 1em ;
|
||||
background-color: #ffffee ;
|
||||
width: 40% ;
|
||||
float: right ;
|
||||
clear: right }
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif ;
|
||||
font-size: medium }
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em }
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red }
|
||||
|
||||
div.system-message {
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red ;
|
||||
font-weight: bold }
|
||||
|
||||
div.topic {
|
||||
margin: 2em }
|
||||
|
||||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||
margin-top: 0.4em }
|
||||
|
||||
h1.title {
|
||||
text-align: center }
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center }
|
||||
|
||||
hr.docutils {
|
||||
width: 75% }
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left {
|
||||
clear: left ;
|
||||
float: left ;
|
||||
margin-right: 1em }
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right {
|
||||
clear: right ;
|
||||
float: right ;
|
||||
margin-left: 1em }
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left }
|
||||
|
||||
.align-center {
|
||||
clear: both ;
|
||||
text-align: center }
|
||||
|
||||
.align-right {
|
||||
text-align: right }
|
||||
|
||||
/* reset inner alignment in figures */
|
||||
div.align-right {
|
||||
text-align: inherit }
|
||||
|
||||
/* div.align-center * { */
|
||||
/* text-align: left } */
|
||||
|
||||
ol.simple, ul.simple {
|
||||
margin-bottom: 1em }
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal }
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha }
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha }
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman }
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman }
|
||||
|
||||
p.attribution {
|
||||
text-align: right ;
|
||||
margin-left: 50% }
|
||||
|
||||
p.caption {
|
||||
font-style: italic }
|
||||
|
||||
p.credits {
|
||||
font-style: italic ;
|
||||
font-size: smaller }
|
||||
|
||||
p.label {
|
||||
white-space: nowrap }
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold ;
|
||||
font-size: larger ;
|
||||
color: maroon ;
|
||||
text-align: center }
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold ;
|
||||
font-size: larger }
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold }
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0 ;
|
||||
margin-top: 0 ;
|
||||
font: inherit }
|
||||
|
||||
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
pre.code .ln { /* line numbers */
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.code {
|
||||
background-color: #eeeeee
|
||||
}
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif ;
|
||||
font-style: oblique }
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif }
|
||||
|
||||
span.option {
|
||||
white-space: nowrap }
|
||||
|
||||
span.pre {
|
||||
white-space: pre }
|
||||
|
||||
span.problematic {
|
||||
color: red }
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80% }
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em }
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em ;
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docutils td, table.docutils th,
|
||||
table.docinfo td, table.docinfo th {
|
||||
padding-left: 0.5em ;
|
||||
padding-right: 0.5em ;
|
||||
vertical-align: top }
|
||||
|
||||
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||
font-weight: bold ;
|
||||
text-align: left ;
|
||||
white-space: nowrap ;
|
||||
padding-left: 0 }
|
||||
|
||||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||
font-size: 100% }
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="document" id="contributors">
|
||||
<h1 class="title">Contributors</h1>
|
||||
|
||||
<p>If you belong here and are missing, please add yourself and send me (peterix) a pull request :-)</p>
|
||||
<p>The following is a list of people who have contributed to <strong>DFHack</strong>.</p>
|
||||
<ul class="simple">
|
||||
<li>Petr Mrázek <<a class="reference external" href="mailto:peterix@gmail.com">peterix@gmail.com</a>></li>
|
||||
<li>Alexander Gavrilov <<a class="reference external" href="mailto:angavrilov@gmail.com">angavrilov@gmail.com</a>></li>
|
||||
<li>doomchild <<a class="reference external" href="mailto:lee.crabtree@gmail.com">lee.crabtree@gmail.com</a>></li>
|
||||
<li>Quietust <<a class="reference external" href="mailto:quietust@gmail.com">quietust@gmail.com</a>></li>
|
||||
<li>jj <<a class="reference external" href="mailto:john-git@ofjj.net">john-git@ofjj.net</a>></li>
|
||||
<li>Warmist <<a class="reference external" href="mailto:warmist@gmail.com">warmist@gmail.com</a>></li>
|
||||
<li>Robert Heinrich <<a class="reference external" href="mailto:robertheinrich73@googlemail.com">robertheinrich73@googlemail.com</a>></li>
|
||||
<li>simon <<a class="reference external" href="mailto:simon@banquise.net">simon@banquise.net</a>></li>
|
||||
<li>Kelly Martin <<a class="reference external" href="mailto:kelly.lynn.martin@gmail.com">kelly.lynn.martin@gmail.com</a>></li>
|
||||
<li>mizipzor <<a class="reference external" href="mailto:mizipzor@gmail.com">mizipzor@gmail.com</a>></li>
|
||||
<li>Simon Jackson <<a class="reference external" href="mailto:sizeak@hotmail.com">sizeak@hotmail.com</a>></li>
|
||||
<li>belal <<a class="reference external" href="mailto:jimbelal@gmail.com">jimbelal@gmail.com</a>></li>
|
||||
<li>RusAnon <<a class="reference external" href="mailto:rusanon@dollchan.ru">rusanon@dollchan.ru</a>></li>
|
||||
<li>Raoul XQ <<a class="reference external" href="mailto:raoulxq@gmail.com">raoulxq@gmail.com</a>></li>
|
||||
<li>Matthew Cline <<a class="reference external" href="mailto:zelgadis@sourceforge.net">zelgadis@sourceforge.net</a>></li>
|
||||
<li>Mike Stewart <<a class="reference external" href="mailto:thewonderidiot@gmail.com">thewonderidiot@gmail.com</a>></li>
|
||||
<li>Timothy Collett <<a class="reference external" href="mailto:tcollett+github@topazgryphon.org">tcollett+github@topazgryphon.org</a>></li>
|
||||
<li>RossM <<a class="reference external" href="mailto:Ross@Gnome">Ross@Gnome</a>></li>
|
||||
<li>Tom Prince <<a class="reference external" href="mailto:tom.prince@ualberta.net">tom.prince@ualberta.net</a>></li>
|
||||
<li>Jared Adams <<a class="reference external" href="mailto:jaxad0127@gmail.com">jaxad0127@gmail.com</a>></li>
|
||||
<li>expwnent <<a class="reference external" href="mailto:q309185@gmail.com">q309185@gmail.com</a>></li>
|
||||
<li>Erik Youngren <<a class="reference external" href="mailto:artanis.00@gmail.com">artanis.00@gmail.com</a>></li>
|
||||
<li>Espen Wiborg <<a class="reference external" href="mailto:espen.wiborg@telio.no">espen.wiborg@telio.no</a>></li>
|
||||
<li>Tim Walberg <<a class="reference external" href="mailto:twalberg@comcast.net">twalberg@comcast.net</a>></li>
|
||||
<li>Mikko Juola <<a class="reference external" href="mailto:mikko.juola@kolumbus.fi">mikko.juola@kolumbus.fi</a>></li>
|
||||
<li>rampaging-poet <<a class="reference external" href="mailto:yrudoingthis@hotmail.com">yrudoingthis@hotmail.com</a>></li>
|
||||
<li>U-glouglou\simon</li>
|
||||
<li>Clayton Hughes <<a class="reference external" href="mailto:clayton.hughes@gmail.com">clayton.hughes@gmail.com</a>></li>
|
||||
<li>zilpin <<a class="reference external" href="mailto:ziLpin@gmail.com">ziLpin@gmail.com</a>></li>
|
||||
<li>Will Rogers <<a class="reference external" href="mailto:wjrogers@gmail.com">wjrogers@gmail.com</a>></li>
|
||||
<li>NMLittle <<a class="reference external" href="mailto:nmlittle@gmail.com">nmlittle@gmail.com</a>></li>
|
||||
<li>root</li>
|
||||
<li>reverb</li>
|
||||
<li>Zhentar <<a class="reference external" href="mailto:Zhentar@gmail.com">Zhentar@gmail.com</a>></li>
|
||||
<li>Valentin Ochs <<a class="reference external" href="mailto:a@0au.de">a@0au.de</a>></li>
|
||||
<li>Priit Laes <<a class="reference external" href="mailto:plaes@plaes.org">plaes@plaes.org</a>></li>
|
||||
<li>kmartin</li>
|
||||
<li>Neil Little</li>
|
||||
<li>rout <<a class="reference external" href="mailto:rout.mail+github@gmail.com">rout.mail+github@gmail.com</a>></li>
|
||||
<li>rofl0r <<a class="reference external" href="mailto:retnyg@gmx.net">retnyg@gmx.net</a>></li>
|
||||
<li>harlanplayford <<a class="reference external" href="mailto:harlanplayford@gmail.com">harlanplayford@gmail.com</a>></li>
|
||||
<li>gsvslto <<a class="reference external" href="mailto:gsvslto@gmail.com">gsvslto@gmail.com</a>></li>
|
||||
<li>sami</li>
|
||||
<li>potato</li>
|
||||
<li>playfordh <<a class="reference external" href="mailto:harlanplayford@gmail.com">harlanplayford@gmail.com</a>></li>
|
||||
<li>feng1st <<a class="reference external" href="mailto:nf_xp@hotmail.com">nf_xp@hotmail.com</a>></li>
|
||||
<li>comestible <<a class="reference external" href="mailto:nickolas.g.russell@gmail.com">nickolas.g.russell@gmail.com</a>></li>
|
||||
<li>Rumrusher <<a class="reference external" href="mailto:Anuleakage@yahoo.com">Anuleakage@yahoo.com</a>></li>
|
||||
<li>Rinin <<a class="reference external" href="mailto:RininS@Gmail.com">RininS@Gmail.com</a>></li>
|
||||
<li>Raoul van Putten</li>
|
||||
<li>John Shade <<a class="reference external" href="mailto:gsvslto@gmail.com">gsvslto@gmail.com</a>></li>
|
||||
<li>John Beisley <<a class="reference external" href="mailto:greatred@gmail.com">greatred@gmail.com</a>></li>
|
||||
<li>Feng <<a class="reference external" href="mailto:nf_xp@hotmail.com">nf_xp@hotmail.com</a>></li>
|
||||
<li>Donald Ruegsegger <<a class="reference external" href="mailto:druegsegger@gmail.com">druegsegger@gmail.com</a>></li>
|
||||
<li>Caldfir <<a class="reference external" href="mailto:caldfir@hotmail.com">caldfir@hotmail.com</a>></li>
|
||||
<li>Antalia <<a class="reference external" href="mailto:tamarakorr@gmail.com">tamarakorr@gmail.com</a>></li>
|
||||
<li>Angus Mezick <<a class="reference external" href="mailto:amezick@gmail.com">amezick@gmail.com</a>></li>
|
||||
</ul>
|
||||
<p>And those are the cool people who made <strong>stonesense</strong>.</p>
|
||||
<ul class="simple">
|
||||
<li>Kris Parker <kaypy></li>
|
||||
<li>Japa <<a class="reference external" href="mailto:japa.mala.illo@gmail.com">japa.mala.illo@gmail.com</a>></li>
|
||||
<li>Jonas Ask <<a class="reference external" href="mailto:jonask84@gmail.com">jonask84@gmail.com</a>></li>
|
||||
<li>Petr Mrázek <<a class="reference external" href="mailto:peterix@gmail.com">peterix@gmail.com</a>></li>
|
||||
<li>Caldfir <<a class="reference external" href="mailto:aitken.tim@gmail.com">aitken.tim@gmail.com</a>></li>
|
||||
<li>8Z <<a class="reference external" href="mailto:git8z@ya.ru">git8z@ya.ru</a>></li>
|
||||
<li>Alexander Gavrilov <<a class="reference external" href="mailto:angavrilov@gmail.com">angavrilov@gmail.com</a>></li>
|
||||
<li>Timothy Collett <<a class="reference external" href="mailto:tcollett+github@topazgryphon.org">tcollett+github@topazgryphon.org</a>></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,74 @@
|
||||
Contributors
|
||||
============
|
||||
If you belong here and are missing, please add yourself and send me (peterix) a pull request :-)
|
||||
|
||||
The following is a list of people who have contributed to **DFHack**.
|
||||
|
||||
- Petr Mrázek <peterix@gmail.com>
|
||||
- Alexander Gavrilov <angavrilov@gmail.com>
|
||||
- doomchild <lee.crabtree@gmail.com>
|
||||
- Quietust <quietust@gmail.com>
|
||||
- jj <john-git@ofjj.net>
|
||||
- Warmist <warmist@gmail.com>
|
||||
- Robert Heinrich <robertheinrich73@googlemail.com>
|
||||
- simon <simon@banquise.net>
|
||||
- Kelly Martin <kelly.lynn.martin@gmail.com>
|
||||
- mizipzor <mizipzor@gmail.com>
|
||||
- Simon Jackson <sizeak@hotmail.com>
|
||||
- belal <jimbelal@gmail.com>
|
||||
- RusAnon <rusanon@dollchan.ru>
|
||||
- Raoul XQ <raoulxq@gmail.com>
|
||||
- Matthew Cline <zelgadis@sourceforge.net>
|
||||
- Mike Stewart <thewonderidiot@gmail.com>
|
||||
- Timothy Collett <tcollett+github@topazgryphon.org>
|
||||
- RossM <Ross@Gnome>
|
||||
- Tom Prince <tom.prince@ualberta.net>
|
||||
- Jared Adams <jaxad0127@gmail.com>
|
||||
- expwnent <q309185@gmail.com>
|
||||
- Erik Youngren <artanis.00@gmail.com>
|
||||
- Espen Wiborg <espen.wiborg@telio.no>
|
||||
- Tim Walberg <twalberg@comcast.net>
|
||||
- Mikko Juola <mikko.juola@kolumbus.fi>
|
||||
- rampaging-poet <yrudoingthis@hotmail.com>
|
||||
- U-glouglou\\simon
|
||||
- Clayton Hughes <clayton.hughes@gmail.com>
|
||||
- zilpin <ziLpin@gmail.com>
|
||||
- Will Rogers <wjrogers@gmail.com>
|
||||
- NMLittle <nmlittle@gmail.com>
|
||||
- root
|
||||
- reverb
|
||||
- Zhentar <Zhentar@gmail.com>
|
||||
- Valentin Ochs <a@0au.de>
|
||||
- Priit Laes <plaes@plaes.org>
|
||||
- kmartin
|
||||
- Neil Little
|
||||
- rout <rout.mail+github@gmail.com>
|
||||
- rofl0r <retnyg@gmx.net>
|
||||
- harlanplayford <harlanplayford@gmail.com>
|
||||
- gsvslto <gsvslto@gmail.com>
|
||||
- sami
|
||||
- potato
|
||||
- playfordh <harlanplayford@gmail.com>
|
||||
- feng1st <nf_xp@hotmail.com>
|
||||
- comestible <nickolas.g.russell@gmail.com>
|
||||
- Rumrusher <Anuleakage@yahoo.com>
|
||||
- Rinin <RininS@Gmail.com>
|
||||
- Raoul van Putten
|
||||
- John Shade <gsvslto@gmail.com>
|
||||
- John Beisley <greatred@gmail.com>
|
||||
- Feng <nf_xp@hotmail.com>
|
||||
- Donald Ruegsegger <druegsegger@gmail.com>
|
||||
- Caldfir <caldfir@hotmail.com>
|
||||
- Antalia <tamarakorr@gmail.com>
|
||||
- Angus Mezick <amezick@gmail.com>
|
||||
|
||||
And those are the cool people who made **stonesense**.
|
||||
|
||||
- Kris Parker <kaypy>
|
||||
- Japa <japa.mala.illo@gmail.com>
|
||||
- Jonas Ask <jonask84@gmail.com>
|
||||
- Petr Mrázek <peterix@gmail.com>
|
||||
- Caldfir <aitken.tim@gmail.com>
|
||||
- 8Z <git8z@ya.ru>
|
||||
- Alexander Gavrilov <angavrilov@gmail.com>
|
||||
- Timothy Collett <tcollett+github@topazgryphon.org>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
Subproject commit d0b2d0750dc2d529a152eba4f3f519f69ff7eab0
|
||||
Subproject commit 178a4916da838e46e46e769e566e47fff6eff8f8
|
@ -1,5 +1,18 @@
|
||||
#!/bin/bash
|
||||
rst2html README.rst > Readme.html
|
||||
rst2html COMPILE.rst > Compile.html
|
||||
rst2html DEVEL.rst > Devel.html
|
||||
rst2html LUA_API.rst > Lua\ API.html
|
||||
# regenerate documentation after editing the .rst files. Requires python and docutils.
|
||||
|
||||
cd `dirname $0`
|
||||
|
||||
function process() {
|
||||
if [ "$1" -nt "$2" ]; then
|
||||
rst2html --no-generator --no-datestamp "$1" "$2"
|
||||
else
|
||||
echo "$2 - up to date."
|
||||
fi
|
||||
}
|
||||
|
||||
process Readme.rst Readme.html
|
||||
process Compile.rst Compile.html
|
||||
process Lua\ API.rst Lua\ API.html
|
||||
process Contributors.rst Contributors.html
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -0,0 +1,329 @@
|
||||
-- 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',
|
||||
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(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 = {
|
||||
{ text = self.none_caption, mat_type = -1, mat_index = -1 },
|
||||
}
|
||||
|
||||
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 = { {
|
||||
icon = '?', text = args.none_caption or 'none', item_type = -1, item_subtype = -1
|
||||
} }
|
||||
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,707 @@
|
||||
-- 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,
|
||||
}
|
||||
|
||||
function Panel:init(args)
|
||||
self:addviews(args.subviews)
|
||||
end
|
||||
|
||||
function Panel:onRenderBody(dc)
|
||||
if self.on_render then self.on_render(dc) 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
|
||||
|
||||
x = x + #text
|
||||
|
||||
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
|
||||
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,
|
||||
row_height = 1,
|
||||
scroll_keys = STANDARDSCROLL,
|
||||
icon_width = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function List:init(info)
|
||||
self.page_top = 1
|
||||
self.page_size = 1
|
||||
self:setChoices(info.choices, info.selected)
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
if iw and obj.icon then
|
||||
local icon = getval(obj.icon)
|
||||
if icon then
|
||||
dc:seek(0, y)
|
||||
if type(icon) == 'table' 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
|
||||
end
|
||||
|
||||
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
|
||||
|
||||
if obj.key then
|
||||
local keystr = gui.getKeyDisplay(obj.key)
|
||||
dc:seek(dc.width-2-#keystr,y):pen(self.text_pen)
|
||||
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function List:submit()
|
||||
if self.on_submit and #self.choices > 0 then
|
||||
self.on_submit(self:getSelected())
|
||||
end
|
||||
end
|
||||
|
||||
function List:onInput(keys)
|
||||
if self.on_submit and keys.SELECT then
|
||||
self:submit()
|
||||
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)
|
||||
|
||||
function FilteredList:init(info)
|
||||
self.edit = EditField{
|
||||
text_pen = info.cursor_pen,
|
||||
frame = { l = info.icon_width, t = 0 },
|
||||
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,
|
||||
row_height = info.row_height,
|
||||
scroll_keys = info.scroll_keys,
|
||||
icon_width = info.icon_width,
|
||||
}
|
||||
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
|
||||
self.not_found = Label{
|
||||
visible = false,
|
||||
text = info.not_found_label or 'No matches',
|
||||
text_pen = COLOR_LIGHTRED,
|
||||
frame = { l = info.icon_width, t = 2 },
|
||||
}
|
||||
self:addviews{ self.edit, self.list, self.not_found }
|
||||
self:setChoices(info.choices, info.selected)
|
||||
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: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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue