Compare commits
10
Commits
4d61963dae
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60937fb56a | ||
|
|
9f4739a057 | ||
|
|
b459aaf65d | ||
|
|
efdb6fc409 | ||
|
|
e310643c85 | ||
|
|
5e2b5b3fdb | ||
|
|
3293d9700d | ||
|
|
2196e483ad | ||
|
|
5aaeaf3247 | ||
|
|
c96bbdbecf |
@@ -8,7 +8,7 @@ https://vene.volconst.com/en/tft
|
|||||||
|
|
||||||
Dependecies
|
Dependecies
|
||||||
-----------
|
-----------
|
||||||
ncursesw shared library and headers (libncursesw5-dev on Debian/Ubuntu)
|
ncursesw library and headers (libncurses-dev on Debian/Ubuntu)
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
@@ -26,7 +26,8 @@ Adding a member
|
|||||||
---------------
|
---------------
|
||||||
A new line begining with a name is considered a member definition, the name can include spaces.
|
A new line begining with a name is considered a member definition, the name can include spaces.
|
||||||
|
|
||||||
After the name the sex can be specifed with a comma followed by either M or F, if not given male is assumed.
|
After the name the sex can be specified with a comma followed by either M or F.
|
||||||
|
If not given male is assumed, unless a spouse is present in which case the opposite sex assumed.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
Adam, M
|
Adam, M
|
||||||
@@ -35,7 +36,7 @@ For example:
|
|||||||
Adding member information
|
Adding member information
|
||||||
-------------------------
|
-------------------------
|
||||||
In the lines following a member declaration, various info can be added:
|
In the lines following a member declaration, various info can be added:
|
||||||
s: - Spouses, must be followed by comma separated names of already declared members, "-" can be given instead in which case the last declared person of the opposite sex is used
|
s: - Spouses, must be followed by comma separated names of already declared members, "^" can be given instead in which case the last declared person is used
|
||||||
p: - Parents, must be followed by 1 or 2 names separated by a comma, "-" can be given instead in which case the previous parents are used
|
p: - Parents, must be followed by 1 or 2 names separated by a comma, "-" can be given instead in which case the previous parents are used
|
||||||
b: - Birthday in the format yyyy.mm.dd, day and month can be ommited
|
b: - Birthday in the format yyyy.mm.dd, day and month can be ommited
|
||||||
d: - Death date
|
d: - Death date
|
||||||
|
|||||||
+1
-1
@@ -30,6 +30,6 @@ g++ -o tft $language $optimizations $warnings $other -DNDEBUG -pipe -s *.cpp -ln
|
|||||||
#-static -lncursesw -ltinfo
|
#-static -lncursesw -ltinfo
|
||||||
|
|
||||||
# Install
|
# Install
|
||||||
chmod 775 tft
|
chmod 755 tft
|
||||||
mkdir -p ${PREFIX}/bin
|
mkdir -p ${PREFIX}/bin
|
||||||
mv tft ${PREFIX}/bin
|
mv tft ${PREFIX}/bin
|
||||||
|
|||||||
+74
-48
@@ -36,10 +36,10 @@ namespace /* internal */
|
|||||||
|
|
||||||
enum TokenType
|
enum TokenType
|
||||||
{
|
{
|
||||||
ATR_BIRTH,
|
ATR_BIRTH, // b:
|
||||||
ATR_DEATH,
|
ATR_DEATH, // d:
|
||||||
ATR_PARENTS,
|
ATR_PARENTS, // p:
|
||||||
ATR_SPOUSES,
|
ATR_SPOUSES, // s:
|
||||||
STRING,
|
STRING,
|
||||||
/* DATE, */
|
/* DATE, */
|
||||||
NUMBER,
|
NUMBER,
|
||||||
@@ -49,7 +49,7 @@ namespace /* internal */
|
|||||||
LAST // ^
|
LAST // ^
|
||||||
};
|
};
|
||||||
|
|
||||||
struct token
|
struct token /* todo: string_view? */
|
||||||
{
|
{
|
||||||
token(const char* b, const char* e);
|
token(const char* b, const char* e);
|
||||||
explicit token(TokenType t)
|
explicit token(TokenType t)
|
||||||
@@ -69,6 +69,7 @@ namespace /* internal */
|
|||||||
using TokenIt = vector<token>::const_iterator;
|
using TokenIt = vector<token>::const_iterator;
|
||||||
|
|
||||||
/* internal data */
|
/* internal data */
|
||||||
|
const char* cur_file;
|
||||||
unsigned line = 1;
|
unsigned line = 1;
|
||||||
pair<person_id, person_id> prev_parents(Nobody, Nobody);
|
pair<person_id, person_id> prev_parents(Nobody, Nobody);
|
||||||
Tree* cur_tree;
|
Tree* cur_tree;
|
||||||
@@ -87,6 +88,7 @@ namespace /* internal */
|
|||||||
U32 sameNamedIndex(TokenIt& it);
|
U32 sameNamedIndex(TokenIt& it);
|
||||||
Sex parseSex(TokenIt& it, TokenIt end);
|
Sex parseSex(TokenIt& it, TokenIt end);
|
||||||
I32 processDatePart(const char*& it_date, const char* end_date);
|
I32 processDatePart(const char*& it_date, const char* end_date);
|
||||||
|
person_id parsePerson(TokenIt& it, TokenIt end);
|
||||||
pair<person_id, person_id> processParents(TokenIt& it, TokenIt end);
|
pair<person_id, person_id> processParents(TokenIt& it, TokenIt end);
|
||||||
vector<person_id> parseSpouses(TokenIt& it, TokenIt end); /* return the spouse IDs */
|
vector<person_id> parseSpouses(TokenIt& it, TokenIt end); /* return the spouse IDs */
|
||||||
|
|
||||||
@@ -97,6 +99,12 @@ namespace /* internal */
|
|||||||
|
|
||||||
|
|
||||||
/* errors */
|
/* errors */
|
||||||
|
void report_err()
|
||||||
|
{
|
||||||
|
endwin();
|
||||||
|
cerr << "In file " << cur_file << " Line " << line << ": ";
|
||||||
|
}
|
||||||
|
|
||||||
[[noreturn]]void err_unexpected_end()
|
[[noreturn]]void err_unexpected_end()
|
||||||
{
|
{
|
||||||
endwin();
|
endwin();
|
||||||
@@ -106,15 +114,15 @@ namespace /* internal */
|
|||||||
|
|
||||||
[[noreturn]]void err_unexpected_char(char c)
|
[[noreturn]]void err_unexpected_char(char c)
|
||||||
{
|
{
|
||||||
endwin();
|
report_err();
|
||||||
cerr << "Line " << line << ": Unexpected '" << c << "'\n";
|
cerr << "Unexpected '" << c << "'\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]]void err_expected(const char* what, token instead)
|
[[noreturn]]void err_expected(const char* what, token instead)
|
||||||
{
|
{
|
||||||
endwin();
|
report_err();
|
||||||
cerr << "Line " << line << ": Expected " << what << " got ";
|
cerr << "Expected " << what << " got ";
|
||||||
cerr.write(instead.begin, instead.size());
|
cerr.write(instead.begin, instead.size());
|
||||||
cerr << " instead\n";
|
cerr << " instead\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -123,8 +131,8 @@ namespace /* internal */
|
|||||||
|
|
||||||
[[noreturn]]void err_unexpected_token(token t)
|
[[noreturn]]void err_unexpected_token(token t)
|
||||||
{
|
{
|
||||||
endwin();
|
report_err();
|
||||||
cerr << "Line " << line << ": Unexpected token '";
|
cerr << "Unexpected token '";
|
||||||
cerr.write(t.begin, t.size());
|
cerr.write(t.begin, t.size());
|
||||||
cerr << "'\n";
|
cerr << "'\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -132,8 +140,8 @@ namespace /* internal */
|
|||||||
|
|
||||||
[[noreturn]]void err_illegal_attribute(token attr)
|
[[noreturn]]void err_illegal_attribute(token attr)
|
||||||
{
|
{
|
||||||
endwin();
|
report_err();
|
||||||
cerr << "Line " << line << ": Illegal attribute '";
|
cerr << "Illegal attribute '";
|
||||||
cerr.write(attr.begin, attr.size());
|
cerr.write(attr.begin, attr.size());
|
||||||
cerr << "'\n";
|
cerr << "'\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -149,26 +157,34 @@ namespace /* internal */
|
|||||||
|
|
||||||
[[noreturn]]void err_personUndefined(const string& name)
|
[[noreturn]]void err_personUndefined(const string& name)
|
||||||
{
|
{
|
||||||
endwin();
|
report_err();
|
||||||
cerr << "Line " << line << ": '" << name << "' is undefined.\n"
|
cerr << "'" << name << "' is undefined.\n"
|
||||||
"Members need to be defined before they are used as relations\n";
|
"Members need to be defined before they are used as relations\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]]void err_indexExcedes(U32 index, U32 max)
|
[[noreturn]]void err_indexExcedes(U32 index, U32 max)
|
||||||
{
|
{
|
||||||
endwin();
|
report_err();
|
||||||
cerr << "Line " << line << ": '" << index << "' is too large.\n"
|
cerr << "'" << index << "' is too large.\n"
|
||||||
<< "At most the index after a name can be the number of people with the "
|
<< "At most the index after a name can be the number of people with the "
|
||||||
"same name already defined - 1, since the count starts from 0.\n"
|
"same name already defined - 1, since the count starts from 0.\n"
|
||||||
"In this case the max is " << max << '\n';
|
"In this case the max is " << max << '\n';
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[noreturn]]void err_no_previous(token t, U32 requested, U32 actual)
|
||||||
|
{
|
||||||
|
report_err();
|
||||||
|
/* todo: print t */
|
||||||
|
cerr << requested << "th last person requested but only " << actual << " are defined so far\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
[[noreturn]]void err_same_sex_spouse(const string& name, const Person& spouse)
|
[[noreturn]]void err_same_sex_spouse(const string& name, const Person& spouse)
|
||||||
{
|
{
|
||||||
endwin();
|
report_err();
|
||||||
cerr << "Line " << line << ": '" << name << "' and their spouse " << spouse.name
|
cerr << "'" << name << "' and their spouse " << spouse.name
|
||||||
<< " are both " << (spouse.sex == M ? "male" : "female") << '\n';
|
<< " are both " << (spouse.sex == M ? "male" : "female") << '\n';
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -199,9 +215,16 @@ namespace /* internal */
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type == STRING)
|
if(type != NUMBER)
|
||||||
{
|
{
|
||||||
if(t.size() == 1)
|
if(t.begin[0] == '^')
|
||||||
|
{
|
||||||
|
type = LAST;
|
||||||
|
for(const char* p = t.begin; p < t.end; ++p)
|
||||||
|
if(*p != '^')
|
||||||
|
err_unexpected_char(*p);
|
||||||
|
}
|
||||||
|
else if(t.size() == 1)
|
||||||
{
|
{
|
||||||
switch(t.begin[0])
|
switch(t.begin[0])
|
||||||
{
|
{
|
||||||
@@ -211,9 +234,6 @@ namespace /* internal */
|
|||||||
case '-':
|
case '-':
|
||||||
type = REPEAT;
|
type = REPEAT;
|
||||||
break;
|
break;
|
||||||
case '^':
|
|
||||||
type = LAST;
|
|
||||||
break;
|
|
||||||
case '\n':
|
case '\n':
|
||||||
type = NEW_LINE;
|
type = NEW_LINE;
|
||||||
}
|
}
|
||||||
@@ -395,7 +415,7 @@ namespace /* internal */
|
|||||||
if(val == 0)
|
if(val == 0)
|
||||||
err_unexpected_char('.');
|
err_unexpected_char('.');
|
||||||
else if(unknown)
|
else if(unknown)
|
||||||
return EventTime::UNKNOWN_DATE;
|
return UNKNOWN_DATE;
|
||||||
else
|
else
|
||||||
return sign * val;
|
return sign * val;
|
||||||
break;
|
break;
|
||||||
@@ -439,6 +459,27 @@ namespace /* internal */
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
person_id parsePerson(TokenIt& it, TokenIt end)
|
||||||
|
{
|
||||||
|
person_id person;
|
||||||
|
if(it->type == LAST)
|
||||||
|
{
|
||||||
|
if(it->size() > cur_tree->size())
|
||||||
|
err_no_previous(*it, it->size(), cur_tree->size());
|
||||||
|
|
||||||
|
person = cur_tree->last() - (it->size() - 1);
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string name = parseName(it, end);
|
||||||
|
U32 same_name_index = sameNamedIndex(it);
|
||||||
|
person = findPerson(name, same_name_index);
|
||||||
|
}
|
||||||
|
return person;
|
||||||
|
}
|
||||||
|
|
||||||
pair<person_id, person_id> processParents(TokenIt& it, TokenIt end)
|
pair<person_id, person_id> processParents(TokenIt& it, TokenIt end)
|
||||||
{
|
{
|
||||||
++it; /* skip attribute */
|
++it; /* skip attribute */
|
||||||
@@ -449,23 +490,13 @@ namespace /* internal */
|
|||||||
parents = prev_parents;
|
parents = prev_parents;
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
else if(it->type == LAST)
|
|
||||||
{
|
|
||||||
parents.first = cur_tree->last();
|
|
||||||
prev_parents = parents;
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string name = parseName(it, end);
|
parents.first = parsePerson(it, end);
|
||||||
U32 same_name_index = sameNamedIndex(it);
|
|
||||||
parents.first = findPerson(name, same_name_index);
|
|
||||||
if(it->type == SEPARATOR)
|
if(it->type == SEPARATOR)
|
||||||
{
|
{
|
||||||
++it;
|
++it;
|
||||||
string name = parseName(it, end);
|
parents.second = parsePerson(it, end);
|
||||||
U32 same_name_index = sameNamedIndex(it);
|
|
||||||
parents.second = findPerson(name, same_name_index);
|
|
||||||
}
|
}
|
||||||
prev_parents = parents;
|
prev_parents = parents;
|
||||||
}
|
}
|
||||||
@@ -482,21 +513,13 @@ namespace /* internal */
|
|||||||
{
|
{
|
||||||
switch(it->type)
|
switch(it->type)
|
||||||
{
|
{
|
||||||
|
case LAST:
|
||||||
case STRING:
|
case STRING:
|
||||||
{
|
spouses.push_back(parsePerson(it, end));
|
||||||
string name = parseName(it, end); /* todo unify in 1 function */
|
|
||||||
U32 same_name_index = sameNamedIndex(it);
|
|
||||||
spouses.push_back(findPerson(name, same_name_index));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SEPARATOR:
|
case SEPARATOR:
|
||||||
++it;
|
++it;
|
||||||
break;
|
break;
|
||||||
case REPEAT:
|
|
||||||
case LAST:
|
|
||||||
++it;
|
|
||||||
spouses.push_back( cur_tree->last() );
|
|
||||||
break;
|
|
||||||
case NEW_LINE:
|
case NEW_LINE:
|
||||||
++line;
|
++line;
|
||||||
++it;
|
++it;
|
||||||
@@ -547,7 +570,7 @@ namespace /* internal */
|
|||||||
|
|
||||||
Sex sex = parseSex(it, end);
|
Sex sex = parseSex(it, end);
|
||||||
|
|
||||||
while(true)
|
while(true) /* Parse person attributes */
|
||||||
{
|
{
|
||||||
switch(it->type)
|
switch(it->type)
|
||||||
{
|
{
|
||||||
@@ -647,10 +670,13 @@ Tree parseTftFile(const char* path)
|
|||||||
|
|
||||||
Tree result(path);
|
Tree result(path);
|
||||||
cur_tree = &result;
|
cur_tree = &result;
|
||||||
|
cur_file = path;
|
||||||
|
|
||||||
MappedFile mapedfile = mapfile(file);
|
MappedFile mapedfile = mapfile(file);
|
||||||
|
|
||||||
vector<token> tokens = tokenize(mapedfile);
|
vector<token> tokens = tokenize(mapedfile);
|
||||||
|
|
||||||
|
// Parse the tokens
|
||||||
state_neutral(tokens);
|
state_neutral(tokens);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
+97
-39
@@ -1,22 +1,21 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string_view>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <ncursesw/ncurses.h>
|
#include <ncurses.h>
|
||||||
|
|
||||||
#include "person.hpp"
|
#include "person.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
|
#include "ui.hpp"
|
||||||
|
|
||||||
using std::cout;
|
using namespace std;
|
||||||
using std::string;
|
|
||||||
|
|
||||||
EventTime::EventTime(I16 Year, U8 Month, U16 Day, I8 Hour, I8 Minute)
|
EventTime::EventTime(I16 Year, U8 Month, U16 Day)
|
||||||
: year(Year)
|
: year(Year)
|
||||||
, month(Month)
|
, month(Month)
|
||||||
, day(Day)
|
, day(Day)
|
||||||
, hour(Hour)
|
|
||||||
, minute(Minute)
|
|
||||||
{
|
{
|
||||||
assert(month <= 12 && day <= 31 && hour <= 23 && minute <= 59 && "Invalid EventTime data");
|
assert(month <= 12 && day <= 31 && "Invalid EventTime data");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventTime::print(WINDOW* info_tab) const
|
void EventTime::print(WINDOW* info_tab) const
|
||||||
@@ -41,21 +40,47 @@ void EventTime::print(WINDOW* info_tab) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Person::Person(const char* aName, EventTime aBirth, Sex aSex, EventTime aDeath)
|
Person::Person(const char* aName, Sex aSex, EventTime aBirth, EventTime aDeath)
|
||||||
: name(aName)
|
: name(aName)
|
||||||
, birth(aBirth)
|
|
||||||
, sex(aSex)
|
, sex(aSex)
|
||||||
|
, birth(aBirth)
|
||||||
, death(aDeath)
|
, death(aDeath)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static string_view nameWithoutFamily(const string& name)
|
||||||
bool Person::operator==(const Person &other)const
|
|
||||||
{
|
{
|
||||||
return sex == other.sex && name == other.name &&
|
U32 fam_start = name.rfind(' ') ;
|
||||||
(birth.day == other.birth.day || birth.day == EventTime::UNKNOWN_DATE || other.birth.day == EventTime::UNKNOWN_DATE) &&
|
string_view name_no_fam;
|
||||||
(birth.month == other.birth.month || birth.month == EventTime::UNKNOWN_DATE || other.birth.month == EventTime::UNKNOWN_DATE) &&
|
|
||||||
(birth.year == other.birth.year || birth.year == EventTime::UNKNOWN_DATE || other.birth.year == EventTime::UNKNOWN_DATE);
|
if(fam_start != string::npos)
|
||||||
|
name_no_fam = string_view(name.data(), name.size() - fam_start);
|
||||||
|
else
|
||||||
|
name_no_fam = string_view(name);
|
||||||
|
|
||||||
|
return name_no_fam;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Person::likelySame(const Person& other)const
|
||||||
|
{
|
||||||
|
bool dates_pass = (birth.day == other.birth.day || birth.day == UNKNOWN_DATE || other.birth.day == UNKNOWN_DATE) &&
|
||||||
|
(birth.month == other.birth.month || birth.month == UNKNOWN_DATE || other.birth.month == UNKNOWN_DATE) &&
|
||||||
|
(birth.year == other.birth.year || birth.year == UNKNOWN_DATE || other.birth.year == UNKNOWN_DATE);
|
||||||
|
|
||||||
|
bool names_pass;
|
||||||
|
if(sex == M)
|
||||||
|
{
|
||||||
|
names_pass = name == other.name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string_view name_no_fam = nameWithoutFamily(name);
|
||||||
|
string_view other_name_no_fam = nameWithoutFamily(other.name);
|
||||||
|
|
||||||
|
names_pass = name_no_fam == other_name_no_fam;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sex == other.sex && names_pass && dates_pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Person::displayInfo()const
|
void Person::displayInfo()const
|
||||||
@@ -74,7 +99,7 @@ void Person::displayInfo()const
|
|||||||
mvwaddstr(info_tab, LINES-1, 0, "Press any key to return.");
|
mvwaddstr(info_tab, LINES-1, 0, "Press any key to return.");
|
||||||
|
|
||||||
wrefresh(info_tab);
|
wrefresh(info_tab);
|
||||||
getch(); /* waint press */
|
getch(); /* wait press */
|
||||||
|
|
||||||
werase(info_tab);
|
werase(info_tab);
|
||||||
wrefresh(info_tab); /* todo: dont update */
|
wrefresh(info_tab); /* todo: dont update */
|
||||||
@@ -90,34 +115,57 @@ static U16 Utf8SymbolsCount(const string& str)
|
|||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
U16 Person::draw(const I16 drawY, const I16 drawX, bool selected, bool hasSpouse,
|
U16 Person::drawAsFocused(const I16 drawY, const I16 drawX, bool selected, bool hasSpouse,
|
||||||
bool hasParents, bool hasKids, bool wholeName) const
|
bool hasKids, ZoomLevel zoom) const
|
||||||
{
|
{
|
||||||
string display_name; /* todo remove? */
|
auto topright = hasSpouse ? ACS_TTEE : ACS_URCORNER;
|
||||||
if(wholeName)
|
auto botleft = hasKids ? ACS_LTEE : ACS_LLCORNER;
|
||||||
{
|
|
||||||
display_name = name;
|
return draw(drawY, drawX, selected, zoom,
|
||||||
|
ACS_LTEE, topright,
|
||||||
|
botleft, ACS_LRCORNER );
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
U16 Person::drawAsSpouse(I16 drawY, I16 drawX, bool selected, bool hasTree, ZoomLevel zoom) const
|
||||||
{
|
{
|
||||||
size_t space = name.find(' ');
|
auto topright = hasTree ? '@' : ACS_URCORNER;
|
||||||
|
return draw(drawY, drawX, selected, zoom,
|
||||||
|
ACS_TTEE, topright,
|
||||||
|
ACS_LLCORNER, ACS_LRCORNER );
|
||||||
|
}
|
||||||
|
|
||||||
|
U16 Person::draw(I16 drawY, I16 drawX, bool selected, ZoomLevel zoom,
|
||||||
|
U32 topleft, U32 topright, U32 botleft, U32 botright) const
|
||||||
|
{
|
||||||
|
/* pad width */
|
||||||
|
U16 width;
|
||||||
|
// pad height
|
||||||
|
U16 height;
|
||||||
|
|
||||||
|
string display_name = name;
|
||||||
|
switch(zoom)
|
||||||
|
{
|
||||||
|
case ZoomLevel::FIRST_NAME:
|
||||||
|
{
|
||||||
|
size_t space = display_name.find(' ');
|
||||||
if(space != string::npos)
|
if(space != string::npos)
|
||||||
{
|
{
|
||||||
/* name[space] = '\0'; */
|
display_name.erase(space);
|
||||||
display_name = name.substr(0, space);
|
|
||||||
/* name[space] = ' '; */
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
display_name = name;
|
|
||||||
}
|
}
|
||||||
|
case ZoomLevel::ALL_NAMES:
|
||||||
|
width = Utf8SymbolsCount(display_name) + 4;
|
||||||
|
height = 5;
|
||||||
|
break;
|
||||||
|
case ZoomLevel::NO_NAME:
|
||||||
|
width = 2;
|
||||||
|
height = 2;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const U16 width = Utf8SymbolsCount(display_name) + 4; /* pad width */
|
if(willBeVisible(drawY, drawX, height, width))
|
||||||
|
|
||||||
if(willBeVisible(drawY, drawX, BOX_HEIGHT, width))
|
|
||||||
{
|
{
|
||||||
AutoWin pad( newpad(BOX_HEIGHT, width) );
|
AutoWin pad( newpad(height, width) );
|
||||||
|
|
||||||
auto color_pair = COLOR_PAIR( selected ? COLOR_SELECTED
|
auto color_pair = COLOR_PAIR( selected ? COLOR_SELECTED
|
||||||
: (sex==M ? COLOR_MALE : COLOR_FEMALE) );
|
: (sex==M ? COLOR_MALE : COLOR_FEMALE) );
|
||||||
@@ -125,12 +173,9 @@ U16 Person::draw(const I16 drawY, const I16 drawX, bool selected, bool hasSpouse
|
|||||||
wbkgd(pad, color_pair);
|
wbkgd(pad, color_pair);
|
||||||
/* wattron(pad, color_pair); */
|
/* wattron(pad, color_pair); */
|
||||||
|
|
||||||
auto top_left = hasParents ? ACS_LTEE : ACS_TTEE;
|
|
||||||
auto top_right = hasSpouse ? ACS_TTEE : ACS_URCORNER;
|
|
||||||
auto bot_left = hasKids ? ACS_LTEE : ACS_LLCORNER;
|
|
||||||
wborder(pad, ACS_VLINE, ACS_VLINE, ACS_HLINE, ACS_HLINE,
|
wborder(pad, ACS_VLINE, ACS_VLINE, ACS_HLINE, ACS_HLINE,
|
||||||
top_left, top_right,
|
topleft, topright,
|
||||||
bot_left, ACS_LRCORNER );
|
botleft, botright);
|
||||||
|
|
||||||
mvwaddstr(pad, 2, 2, display_name.data());
|
mvwaddstr(pad, 2, 2, display_name.data());
|
||||||
|
|
||||||
@@ -139,4 +184,17 @@ U16 Person::draw(const I16 drawY, const I16 drawX, bool selected, bool hasSpouse
|
|||||||
drawpad(pad, drawY, drawX);
|
drawpad(pad, drawY, drawX);
|
||||||
}
|
}
|
||||||
return width;
|
return width;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
U16 person_box_height(ZoomLevel z)
|
||||||
|
{
|
||||||
|
switch(z)
|
||||||
|
{
|
||||||
|
case ZoomLevel::ALL_NAMES:
|
||||||
|
case ZoomLevel::FIRST_NAME:
|
||||||
|
return 5;
|
||||||
|
case ZoomLevel::NO_NAME:
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-16
@@ -2,22 +2,22 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <ncursesw/ncurses.h>
|
#include <ncurses.h>
|
||||||
#include "common/basicTypes.h"
|
#include "common/basicTypes.h"
|
||||||
|
|
||||||
|
enum ZoomLevel: U8;
|
||||||
|
|
||||||
|
enum: I16 { UNKNOWN_DATE=0 };
|
||||||
|
|
||||||
struct EventTime
|
struct EventTime
|
||||||
{
|
{
|
||||||
enum: I16 { UNKNOWN_DATE=0 };
|
|
||||||
enum: I8 { UNKNOWN_TIME=-1 };
|
|
||||||
|
|
||||||
EventTime(I16 Year=UNKNOWN_DATE, U8 Month=UNKNOWN_DATE, U16 Day=UNKNOWN_DATE, I8 Hour=UNKNOWN_TIME, I8 Minute=UNKNOWN_TIME);
|
EventTime(I16 Year=UNKNOWN_DATE, U8 Month=UNKNOWN_DATE, U16 Day=UNKNOWN_DATE);
|
||||||
void print(WINDOW*) const;
|
void print(WINDOW*) const;
|
||||||
|
|
||||||
I16 year; //The year in relation to Chritst's birth - 1 (AD) is the year He was born, -1 (BC) is the previous year. There is no year 0
|
I16 year; //The year in relation to Chritst's birth - 1 (AD) is the year He was born, -1 (BC) is the previous year. There is no year 0
|
||||||
U8 month;
|
U8 month;
|
||||||
U16 day;
|
U16 day;
|
||||||
I8 hour; //0-23
|
|
||||||
I8 minute; //0-59
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Sex: U8
|
enum Sex: U8
|
||||||
@@ -34,7 +34,7 @@ enum BoxColors: U8 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum DisplayDimensons: U8 {
|
enum DisplayDimensons: U8 {
|
||||||
BOX_HEIGHT = 5,
|
// BOX_HEIGHT = 5,
|
||||||
LINK_HEIGHT = 3
|
LINK_HEIGHT = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,24 +42,38 @@ class Person
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Person() = default;
|
Person() = default;
|
||||||
Person(const char* aName, EventTime aBirth={}, Sex=M, EventTime aDeath={});
|
Person(const char* aName, Sex=M, EventTime aBirth={}, EventTime aDeath={});
|
||||||
/* Person(Person&&) = default; */
|
|
||||||
/* Person& operator=(Person&&) = default; */
|
|
||||||
/* Person& operator=(const Person&) = default; */
|
|
||||||
|
|
||||||
bool operator==(const Person &other) const;
|
/* Are the two very likely the same person based on sex, name, dates */
|
||||||
|
bool likelySame(const Person& other) const;
|
||||||
|
|
||||||
|
/* Display the person's info on the screen */
|
||||||
void displayInfo() const;
|
void displayInfo() const;
|
||||||
|
|
||||||
/* Draw a Person's box on the terminal
|
/* Draw a Person's box on the terminal, as a part of the focused tree
|
||||||
drawY,drawX - top left coordinates where to draw (negative if outside the screen)
|
drawY,drawX - top left coordinates where to draw (negative if outside the screen)
|
||||||
|
partOfMain - is the person a decendant of the current root
|
||||||
hasSpouse, hasSpouse, hasChildren - wheather the person has them on display
|
hasSpouse, hasSpouse, hasChildren - wheather the person has them on display
|
||||||
|
ZoomLevel - how the box will be drawn
|
||||||
returns the width of the box*/
|
returns the width of the box*/
|
||||||
U16 draw(I16 drawY, I16 drawX, bool selected, bool hasSpouse,
|
U16 drawAsFocused(I16 drawY, I16 drawX, bool selected, bool hasSpouse,
|
||||||
bool hasParents, bool hasChildren=false, bool wholeName=true) const;
|
bool hasChildren, ZoomLevel) const;
|
||||||
|
|
||||||
|
/* Draw a Person's box on the terminal, as a spouse of someone in the focused tree
|
||||||
|
drawY,drawX - top left coordinates where to draw (negative if outside the screen)
|
||||||
|
hasTree - weather the spouse has a tree that can be displayed
|
||||||
|
ZoomLevel - how the box will be drawn
|
||||||
|
returns the width of the box*/
|
||||||
|
U16 drawAsSpouse(I16 drawY, I16 drawX, bool selected, bool hasTree, ZoomLevel) const;
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
EventTime birth;
|
|
||||||
Sex sex;
|
Sex sex;
|
||||||
|
EventTime birth;
|
||||||
EventTime death;
|
EventTime death;
|
||||||
|
|
||||||
|
private:
|
||||||
|
U16 draw(I16 drawY, I16 drawX, bool selected, ZoomLevel,
|
||||||
|
U32 topleft, U32 topright, U32 botleft, U32 botright) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
U16 person_box_height(ZoomLevel);
|
||||||
|
|||||||
+3
-1
@@ -2,7 +2,7 @@
|
|||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include "parser.hpp"
|
#include "parser.hpp"
|
||||||
#include "ui.hpp"
|
#include "ui.hpp"
|
||||||
#include <ncursesw/ncurses.h>
|
#include <ncurses.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -32,6 +32,8 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
init_ncurses();
|
init_ncurses();
|
||||||
|
|
||||||
|
if( !tree.empty() )
|
||||||
displayTree(tree);
|
displayTree(tree);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+35
-180
@@ -1,8 +1,10 @@
|
|||||||
#include"tree.hpp"
|
#include <assert.h>
|
||||||
#include"utils.hpp"
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <assert.h>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include"tree.hpp"
|
||||||
|
#include"utils.hpp"
|
||||||
|
|
||||||
using std::vector;
|
using std::vector;
|
||||||
using std::string;
|
using std::string;
|
||||||
@@ -24,7 +26,7 @@ Tree& Tree::operator+=(const Tree& other)
|
|||||||
{
|
{
|
||||||
for (j = 0; j < initNum; ++j)
|
for (j = 0; j < initNum; ++j)
|
||||||
{
|
{
|
||||||
if (other.people_[i] == people_[j])
|
if (other.people_[i].likelySame(people_[j]))
|
||||||
{
|
{
|
||||||
newId[i] = j;
|
newId[i] = j;
|
||||||
break;
|
break;
|
||||||
@@ -37,28 +39,40 @@ Tree& Tree::operator+=(const Tree& other)
|
|||||||
people_.reserve(idCounter);
|
people_.reserve(idCounter);
|
||||||
for (person_id i = 0; i < other.people_.size(); ++i) /* add the personal data of II to I */
|
for (person_id i = 0; i < other.people_.size(); ++i) /* add the personal data of II to I */
|
||||||
{
|
{
|
||||||
if (newId[i] >= initNum) /* isnt already in I */
|
if (newId[i] >= initNum) /* isnt present in I */
|
||||||
people_.push_back(other.people_[i]);
|
people_.push_back(other.people_[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
relations_.reserve(idCounter);
|
relations_.reserve(idCounter);
|
||||||
for (person_id i = 0; i < other.people_.size(); ++i) //merge the relative data
|
for (person_id idIn2 = 0; idIn2 < other.people_.size(); ++idIn2) // merge the relational data
|
||||||
{
|
{
|
||||||
if (newId[i] >= initNum) /* isnt already in I */
|
if (newId[idIn2] >= initNum) /* isnt present in I */
|
||||||
{
|
{
|
||||||
relations_.push_back(other.relations_[i]);
|
relations_.push_back(other.relations_[idIn2]);
|
||||||
for (Relation& rel: relations_.back())
|
for (Relation& rel: relations_.back())
|
||||||
rel.id = newId[rel.id];
|
rel.id = newId[rel.id];
|
||||||
}
|
}
|
||||||
else // if present in both trees
|
else // if present in both trees
|
||||||
{
|
{
|
||||||
person_id idIn1 = newId[i];
|
person_id idIn1 = newId[idIn2];
|
||||||
relations_[idIn1].reserve(relations_[idIn1].size() + other.relations_[i].size());
|
std::vector<Relation>& relsIn1 = relations_[idIn1];
|
||||||
|
relsIn1.reserve(relsIn1.size() + other.relations_[idIn2].size());
|
||||||
|
|
||||||
for (Relation rel: other.relations_[i])
|
for (Relation relIn2: other.relations_[idIn2])
|
||||||
{
|
{
|
||||||
if(newId[rel.id] >= initNum) /* not already in I */
|
if(newId[relIn2.id] >= initNum) /* relative not present in I */
|
||||||
relations_[idIn1].push_back( {newId[rel.id], rel.type} );
|
relsIn1.push_back( {newId[relIn2.id], relIn2.type} );
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool foundIn1 = false;
|
||||||
|
for(Relation relIn1: relsIn1)
|
||||||
|
if(relIn1.id == newId[relIn2.id])
|
||||||
|
foundIn1 = true;
|
||||||
|
|
||||||
|
if( !foundIn1 ) // relative is present in I, but relation only in II
|
||||||
|
relsIn1.push_back( {newId[relIn2.id], relIn2.type} );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -73,7 +87,7 @@ Tree& Tree::operator-= (const Tree& other)
|
|||||||
{
|
{
|
||||||
for (const Person& othPers: other.people_)
|
for (const Person& othPers: other.people_)
|
||||||
{
|
{
|
||||||
if (people_[i] == othPers)
|
if (people_[i].likelySame(othPers))
|
||||||
{
|
{
|
||||||
removePerson(i);
|
removePerson(i);
|
||||||
--i;
|
--i;
|
||||||
@@ -84,7 +98,12 @@ Tree& Tree::operator-= (const Tree& other)
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Tree::isEmpty()
|
U32 Tree::size()
|
||||||
|
{
|
||||||
|
return people_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tree::empty()
|
||||||
{
|
{
|
||||||
return people_.empty();
|
return people_.empty();
|
||||||
}
|
}
|
||||||
@@ -242,7 +261,7 @@ const std::vector<Relation>& Tree::findRelations(person_id id) const
|
|||||||
|
|
||||||
void Tree::addPerson(const char* name, Sex sex, unsigned father, unsigned mother, EventTime birth, EventTime death)
|
void Tree::addPerson(const char* name, Sex sex, unsigned father, unsigned mother, EventTime birth, EventTime death)
|
||||||
{
|
{
|
||||||
people_.emplace_back(name, birth, sex, death);
|
people_.emplace_back(name, sex, birth, death);
|
||||||
|
|
||||||
relations_.emplace_back(); /* relations of the new person */
|
relations_.emplace_back(); /* relations of the new person */
|
||||||
|
|
||||||
@@ -253,12 +272,6 @@ void Tree::addPerson(const char* name, Sex sex, unsigned father, unsigned mother
|
|||||||
updateRels(new_person_id);
|
updateRels(new_person_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* bool Tree::addRelation(const char* firstName, const RelType type, const char* secondName) */
|
|
||||||
/* { */
|
|
||||||
|
|
||||||
/* return addRelation(findId(firstName)[0], type, findId(secondName)[0]); */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
bool Tree::addRelation(const unsigned first, const RelType type, const unsigned second, bool update)
|
bool Tree::addRelation(const unsigned first, const RelType type, const unsigned second, bool update)
|
||||||
{
|
{
|
||||||
if (first == Nobody || first >= people_.size() || second == Nobody || second >= people_.size() || type == None)
|
if (first == Nobody || first >= people_.size() || second == Nobody || second >= people_.size() || type == None)
|
||||||
@@ -309,11 +322,6 @@ void Tree::addRelOneSide(person_id first, RelType type, person_id second)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* void Tree::removeRelation(const char* firstName, const char* secondName) */
|
|
||||||
/* { */
|
|
||||||
/* removeRelation(findId(firstName)[0], findId(secondName)[0]); */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
void Tree::removePerson(const person_id id)
|
void Tree::removePerson(const person_id id)
|
||||||
{
|
{
|
||||||
person_id relId;
|
person_id relId;
|
||||||
@@ -345,11 +353,6 @@ void Tree::removePerson(const person_id id)
|
|||||||
remove(people_, id);
|
remove(people_, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* void Tree::removePerson(const char* personName, short year, unsigned char month, unsigned char day) */
|
|
||||||
/* { */
|
|
||||||
/* removePerson(findId(personName, year, month, day)); */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
void Tree::removeRelation(const unsigned first, const unsigned second)
|
void Tree::removeRelation(const unsigned first, const unsigned second)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < relations_[first].size(); ++i)
|
for (unsigned i = 0; i < relations_[first].size(); ++i)
|
||||||
@@ -370,16 +373,6 @@ void Tree::removeRelation(const unsigned first, const unsigned second)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tree::focusPerson(person_id id)
|
|
||||||
{
|
|
||||||
focused_ = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
person_id Tree::focused() const
|
|
||||||
{
|
|
||||||
return focused_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tree::updateRels(person_id id)
|
void Tree::updateRels(person_id id)
|
||||||
{
|
{
|
||||||
person_id dad = findParent(id, M);
|
person_id dad = findParent(id, M);
|
||||||
@@ -412,25 +405,6 @@ void Tree::updateRels(person_id id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Tree::draw(const person_id selected, const U8 zoom, const I16 offsetY, const I16 offsetX)
|
|
||||||
{
|
|
||||||
selected_ = selected;
|
|
||||||
zoom_ = zoom;
|
|
||||||
|
|
||||||
erase(); //Clear the screen
|
|
||||||
wnoutrefresh(stdscr);
|
|
||||||
|
|
||||||
drawLineWithWives(focused_, offsetY, offsetX);
|
|
||||||
|
|
||||||
doupdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tree::printMember(const unsigned id)const // todo: rename
|
|
||||||
{
|
|
||||||
people_[id].displayInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Tree::commonAncestorRec(person_id id, vector<U8> &visited) const
|
void Tree::commonAncestorRec(person_id id, vector<U8> &visited) const
|
||||||
{
|
{
|
||||||
++visited[id];
|
++visited[id];
|
||||||
@@ -442,122 +416,3 @@ void Tree::commonAncestorRec(person_id id, vector<U8> &visited) const
|
|||||||
commonAncestorRec(rel.id, visited);
|
commonAncestorRec(rel.id, visited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the height of the space occupied by the link
|
|
||||||
static U8 linkToFirstBorn(I16 startY, I16 startX, bool moreSiblings)
|
|
||||||
{
|
|
||||||
if (willBeVisible(startY, startX, 3, 1))
|
|
||||||
{
|
|
||||||
AutoWin pad( newpad(3, 1) );
|
|
||||||
|
|
||||||
waddch(pad, ACS_VLINE);
|
|
||||||
mvwaddch(pad, 1, 0,
|
|
||||||
moreSiblings ? ACS_LTEE : ACS_VLINE);
|
|
||||||
mvwaddch(pad, 2, 0, ACS_VLINE);
|
|
||||||
|
|
||||||
drawpad(pad, startY, startX);
|
|
||||||
}
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
static U8 linkToChild(I16 startY, I16 startX, I16 endX, bool moreSiblings)
|
|
||||||
{
|
|
||||||
assert(endX >= startX);
|
|
||||||
const I16 len = endX - startX;
|
|
||||||
if (willBeVisible(startY, startX, 2, len))
|
|
||||||
{
|
|
||||||
AutoWin pad( newpad(2, len) );
|
|
||||||
|
|
||||||
for(U16 i = 0; i < len-1; ++i)
|
|
||||||
waddch(pad, ACS_HLINE);
|
|
||||||
/* whline(pad, '_', len-1); */
|
|
||||||
waddch(pad, moreSiblings ? ACS_TTEE : ACS_URCORNER);
|
|
||||||
|
|
||||||
mvwaddch(pad, 1, len-1, ACS_VLINE);
|
|
||||||
|
|
||||||
drawpad(pad, startY, startX);
|
|
||||||
}
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* I16 Tree::drawLine(person_id id, I16 drawY, I16 drawX) const */
|
|
||||||
/* { */
|
|
||||||
/* people_[id].draw(drawY, drawX, true, false, true); */
|
|
||||||
|
|
||||||
/* vector<person_id> children; */
|
|
||||||
/* for(U32 i=0; i<relations_[id].size(); ++i) */
|
|
||||||
/* { */
|
|
||||||
/* if (relations_[id][i].type == RelType::Parent) */
|
|
||||||
/* children.push_back(relations_[id][i].id); */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
/* if (!children.empty()) */
|
|
||||||
/* { */
|
|
||||||
/* auto prevX = drawX; */
|
|
||||||
/* drawY += BOX_HEIGHT; */
|
|
||||||
|
|
||||||
/* auto lnHt = linkToFirstBorn(drawY, drawX); /\* Link height *\/ */
|
|
||||||
/* drawX = drawLine(children[0], drawY + lnHt, drawX); */
|
|
||||||
|
|
||||||
/* for(size_t j = 1; j < children.size(); ++j) */
|
|
||||||
/* { */
|
|
||||||
/* lnHt = linkToChild(drawY+1, prevX+1, drawX+1); */
|
|
||||||
/* prevX = drawX; */
|
|
||||||
/* drawX = drawLine(children[j], drawY + lnHt, drawX); */
|
|
||||||
/* } */
|
|
||||||
/* return drawX; */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
/* return drawX + people_[id].boxWidth() + dist_between_; */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
I16 Tree::drawLineWithWives(const person_id id, I16 drawY, const I16 drawX) const
|
|
||||||
{
|
|
||||||
const Person& person = people_[id];
|
|
||||||
const auto& rels = relations_[id];
|
|
||||||
|
|
||||||
person_id spouse = Nobody;
|
|
||||||
vector<person_id> children;
|
|
||||||
for(const auto& rel: rels)
|
|
||||||
{
|
|
||||||
if (rel.type == RelType::Parent)
|
|
||||||
children.push_back(rel.id);
|
|
||||||
|
|
||||||
else if (rel.type == RelType::Spouse && spouse == Nobody)
|
|
||||||
spouse = rel.id;
|
|
||||||
}
|
|
||||||
const size_t numKids = children.size();
|
|
||||||
U16 person_w = person.draw(drawY, drawX,
|
|
||||||
id == selected_, spouse!=Nobody, true, numKids!=0, zoom_==1);
|
|
||||||
|
|
||||||
U16 spouse_w = 0;
|
|
||||||
if (spouse != Nobody)
|
|
||||||
{
|
|
||||||
U16 spouse_box = people_[spouse].draw(drawY, drawX + person_w + spouse_dist_,
|
|
||||||
spouse == selected_, false, false, false, zoom_==1);
|
|
||||||
spouse_w = spouse_dist_ + spouse_box;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto childX = drawX;
|
|
||||||
if (numKids!=0)
|
|
||||||
{
|
|
||||||
auto prevX = childX;
|
|
||||||
drawY += BOX_HEIGHT;
|
|
||||||
|
|
||||||
auto lnHt = linkToFirstBorn(drawY, childX, numKids > 1); /* Link height */
|
|
||||||
childX = drawLineWithWives(children[0], drawY + lnHt, childX);
|
|
||||||
|
|
||||||
for(size_t j = 1; j < numKids; ++j)
|
|
||||||
{
|
|
||||||
/* +1 because LTC starts lower then LTFB */
|
|
||||||
lnHt = linkToChild(drawY+1, prevX+1, childX+1, j < numKids-1) + 1;
|
|
||||||
|
|
||||||
prevX = childX;
|
|
||||||
childX = drawLineWithWives(children[j], drawY + lnHt, childX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return max(drawX + person_w + dist_between_ + spouse_w,
|
|
||||||
childX);
|
|
||||||
}
|
|
||||||
|
|||||||
+7
-29
@@ -40,42 +40,33 @@ public:
|
|||||||
Tree& operator+= (const Tree& other);
|
Tree& operator+= (const Tree& other);
|
||||||
Tree& operator-= (const Tree& other);
|
Tree& operator-= (const Tree& other);
|
||||||
|
|
||||||
bool isEmpty();
|
U32 size();
|
||||||
|
bool empty();
|
||||||
person_id last();
|
person_id last();
|
||||||
|
|
||||||
// Get person by id
|
// Get person by id
|
||||||
const Person& operator[](person_id) const;
|
const Person& operator[](person_id) const;
|
||||||
|
|
||||||
std::vector<person_id> findId(const std::string& name) const;
|
std::vector<person_id> findId(const std::string& name) const; // todo merge the 2 findId
|
||||||
person_id findId(const std::string& name, Sex) const;
|
person_id findId(const std::string& name, Sex) const;
|
||||||
person_id findRootAncestor(person_id) const;
|
person_id findRootAncestor(person_id) const;
|
||||||
person_id findOldestAncestor(person_id, Sex) const;//
|
person_id findOldestAncestor(person_id, Sex) const;//
|
||||||
|
|
||||||
//oldest common ancestor ID, a person is considerd an ancestor of himself
|
// Oldest common ancestor ID, a person is considerd an ancestor of himself
|
||||||
person_id findCommonAncestor(person_id first, person_id second) const; /* needs to be fixed, never return nobody */
|
person_id findCommonAncestor(person_id first, person_id second) const; /* needs to be fixed, never return nobody */
|
||||||
RelType findRelation(person_id first, person_id second) const;
|
RelType findRelation(person_id first, person_id second) const;
|
||||||
const std::vector<Relation>& findRelations(person_id) const;
|
const std::vector<Relation>& findRelations(person_id) const;
|
||||||
person_id findParent(person_id child, Sex parentSex) const;
|
person_id findParent(person_id child, Sex parentSex) const;
|
||||||
person_id findSpouse(person_id person) const;
|
person_id findSpouse(person_id person) const;
|
||||||
std::vector<person_id> findChildren(person_id person) const;
|
std::vector<person_id> findChildren(person_id person) const;
|
||||||
std::vector<person_id> findChildren(person_id person, person_id exclude) const;
|
std::vector<person_id> findChildren(person_id person, person_id exclude) const; // exclude the child with the given ID
|
||||||
|
|
||||||
void addPerson(const char* name, Sex sex, person_id father=Nobody, person_id mother=Nobody, EventTime birth={}, EventTime death={});
|
void addPerson(const char* name, Sex sex, person_id father=Nobody, person_id mother=Nobody, EventTime birth={}, EventTime death={});
|
||||||
bool addRelation(const char* firstName, RelType, const char* secondName); //Adds a new relation or edits an old one. 0 - failure 1 - success
|
// Return weather it succeeded or not
|
||||||
bool addRelation(person_id first, RelType, person_id second, bool update=true); //0 - failure 1 - success
|
bool addRelation(person_id first, RelType, person_id second, bool update=true);
|
||||||
|
|
||||||
void removePerson(person_id id); //Removes a member and the last one takes his ID
|
void removePerson(person_id id); //Removes a member and the last one takes his ID
|
||||||
/* void removePerson(const char*, short year = 0, const unsigned char month = 0, const unsigned char day = 0); */
|
|
||||||
void removeRelation(person_id first, person_id second);
|
void removeRelation(person_id first, person_id second);
|
||||||
void removeRelation(const char* firstName, const char* secondName);
|
|
||||||
|
|
||||||
/* draw the tree based on this person */
|
|
||||||
void focusPerson(person_id);
|
|
||||||
person_id focused() const;
|
|
||||||
|
|
||||||
/* void printRel(person_id id)const; //Prints out close relatives */
|
|
||||||
void printMember(person_id)const;
|
|
||||||
void draw(person_id selected, U8 zoom, I16 offsetY, I16 offsetX);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// recursion to find common ancestor
|
// recursion to find common ancestor
|
||||||
@@ -86,20 +77,7 @@ private:
|
|||||||
(example adds siblings after parents are added) */
|
(example adds siblings after parents are added) */
|
||||||
void updateRels(person_id);
|
void updateRels(person_id);
|
||||||
|
|
||||||
/* draw a line of people starting from person with id
|
|
||||||
return the farthest X that will be drawn */
|
|
||||||
I16 drawLineWithWives(person_id, I16 drawY, I16 drawX) const;
|
|
||||||
|
|
||||||
std::string treeName_;
|
std::string treeName_;
|
||||||
std::vector<Person> people_; //The data for each member
|
std::vector<Person> people_; //The data for each member
|
||||||
std::vector<std::vector<Relation>> relations_; //The relatives of each member
|
std::vector<std::vector<Relation>> relations_; //The relatives of each member
|
||||||
|
|
||||||
// 0 is the first person in the tree
|
|
||||||
person_id focused_ = 0; /* the tree was draw based on this person */
|
|
||||||
person_id selected_ = 0;
|
|
||||||
U8 zoom_ = 0;
|
|
||||||
|
|
||||||
// todo: figure out what to do with these constants
|
|
||||||
const U8 dist_between_ = 1; /* distance between people */
|
|
||||||
const U8 spouse_dist_ = 0; /* distance between spouses */
|
|
||||||
};
|
};
|
||||||
|
|||||||
+156
-24
@@ -1,25 +1,55 @@
|
|||||||
|
#include "ui.hpp"
|
||||||
#include "tree.hpp"
|
#include "tree.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include "assert.h"
|
#include "assert.h"
|
||||||
#include "../config.h" /* user's configuration */
|
#include "../config.h" // user's configuration
|
||||||
|
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
// State
|
||||||
|
person_id focused_; // the tree was draw based on this person
|
||||||
|
person_id selected_; // the currently selected selected
|
||||||
|
ZoomLevel zoom_; // how much info to show for each selected
|
||||||
|
|
||||||
|
// todo: figure out what to do with these constants
|
||||||
|
const U8 dist_between_ = 1; /* distance between people */
|
||||||
|
const U8 spouse_dist_ = 0; /* distance between spouses */
|
||||||
|
|
||||||
|
void drawTree(const Tree&, const I16 offsetY, const I16 offsetX);
|
||||||
|
/* draw a line of people starting from person with id
|
||||||
|
return the farthest X on the screen where it will be drawn */
|
||||||
|
I16 drawTreeLine(const Tree&, person_id, I16 drawY, I16 drawX);
|
||||||
|
|
||||||
|
|
||||||
void displayHelp();
|
void displayHelp();
|
||||||
person_id selectLeft(const Tree& t, person_id selected);
|
person_id selectLeft(const Tree&, person_id selected);
|
||||||
person_id selectDown(const Tree& t, person_id selected);
|
person_id selectDown(const Tree&, person_id selected);
|
||||||
person_id selectUp(const Tree& t, person_id selected);
|
person_id selectUp(const Tree&, person_id selected);
|
||||||
person_id selectRight(const Tree& t, person_id selected);
|
person_id selectRight(const Tree&, person_id selected);
|
||||||
person_id selectLeftSibSpouse(const Tree& t, person_id selected); /* todo: split? */
|
person_id selectLeftSibSpouse(const Tree&, person_id selected); /* todo: split? */
|
||||||
person_id selectRightSib(const Tree& t, person_id selected);
|
person_id selectRightSib(const Tree&, person_id selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void displayTree(Tree& t)
|
ZoomLevel operator++(ZoomLevel& z)
|
||||||
{
|
{
|
||||||
// checck there are people
|
z = (ZoomLevel)(z + 1);
|
||||||
|
return z;
|
||||||
|
}
|
||||||
|
|
||||||
person_id selected = 0; // the currently selected selected
|
ZoomLevel operator--(ZoomLevel& z)
|
||||||
U8 zoom = 0; // how much info to show for each selected
|
{
|
||||||
|
z = (ZoomLevel)(z - 1);
|
||||||
|
return z;
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayTree(const Tree& t)
|
||||||
|
{
|
||||||
|
// 0 is the first person in the tree
|
||||||
|
focused_ = 0;
|
||||||
|
selected_ = 0;
|
||||||
|
zoom_ = ZoomLevel::FIRST_NAME;
|
||||||
|
|
||||||
// offset of the tree in relation to the top left corner of the screen
|
// offset of the tree in relation to the top left corner of the screen
|
||||||
I16 offsetY = 0, offsetX = 0;
|
I16 offsetY = 0, offsetX = 0;
|
||||||
@@ -29,7 +59,7 @@ void displayTree(Tree& t)
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
if(to_draw)
|
if(to_draw)
|
||||||
t.draw(selected, zoom, offsetY, offsetX);
|
drawTree(t, offsetY, offsetX);
|
||||||
else
|
else
|
||||||
to_draw = true;
|
to_draw = true;
|
||||||
|
|
||||||
@@ -41,7 +71,7 @@ void displayTree(Tree& t)
|
|||||||
break;
|
break;
|
||||||
case SELECT:
|
case SELECT:
|
||||||
case '\n':
|
case '\n':
|
||||||
t.focusPerson( t.findRootAncestor(selected) );
|
focused_ = t.findRootAncestor(selected_);
|
||||||
offsetX = 0;
|
offsetX = 0;
|
||||||
offsetY = 0;
|
offsetY = 0;
|
||||||
break;
|
break;
|
||||||
@@ -59,25 +89,35 @@ void displayTree(Tree& t)
|
|||||||
break;
|
break;
|
||||||
case '=':
|
case '=':
|
||||||
case '+':
|
case '+':
|
||||||
zoom = 1;
|
if(zoom_ < ZoomLevel::MAX_ZOOM)
|
||||||
|
{
|
||||||
|
++zoom_;
|
||||||
|
offsetX = 0;
|
||||||
|
offsetY = 0;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case '-':
|
case '-':
|
||||||
zoom = 0;
|
if(zoom_ > 0)
|
||||||
|
{
|
||||||
|
--zoom_;
|
||||||
|
offsetX = 0;
|
||||||
|
offsetY = 0;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case SELECT_LEFT:
|
case SELECT_LEFT:
|
||||||
selected = selectLeft(t, selected);
|
selected_ = selectLeft(t, selected_);
|
||||||
break;
|
break;
|
||||||
case SELECT_DOWN:
|
case SELECT_DOWN:
|
||||||
selected = selectDown(t, selected);
|
selected_ = selectDown(t, selected_);
|
||||||
break;
|
break;
|
||||||
case SELECT_UP:
|
case SELECT_UP:
|
||||||
selected = selectUp(t, selected);
|
selected_ = selectUp(t, selected_);
|
||||||
break;
|
break;
|
||||||
case SELECT_RIGHT:
|
case SELECT_RIGHT:
|
||||||
selected = selectRight(t, selected);
|
selected_ = selectRight(t, selected_);
|
||||||
break;
|
break;
|
||||||
case DISPLAY_INFO:
|
case DISPLAY_INFO:
|
||||||
t.printMember(selected);
|
t[selected_].displayInfo();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
to_draw = false; /* invalid key, don't redraw */
|
to_draw = false; /* invalid key, don't redraw */
|
||||||
@@ -90,6 +130,98 @@ void displayTree(Tree& t)
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
void drawTree(const Tree& t, const I16 offsetY, const I16 offsetX)
|
||||||
|
{
|
||||||
|
erase(); //Clear the screen
|
||||||
|
wnoutrefresh(stdscr);
|
||||||
|
|
||||||
|
drawTreeLine(t, focused_, offsetY, offsetX);
|
||||||
|
|
||||||
|
doupdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the height of the space occupied by the link
|
||||||
|
U8 linkToFirstBorn(I16 startY, I16 startX, bool moreSiblings)
|
||||||
|
{
|
||||||
|
if (willBeVisible(startY, startX, 3, 1))
|
||||||
|
{
|
||||||
|
AutoWin pad( newpad(3, 1) );
|
||||||
|
|
||||||
|
waddch(pad, ACS_VLINE);
|
||||||
|
mvwaddch(pad, 1, 0,
|
||||||
|
moreSiblings ? ACS_LTEE : ACS_VLINE);
|
||||||
|
mvwaddch(pad, 2, 0, ACS_VLINE);
|
||||||
|
|
||||||
|
drawpad(pad, startY, startX);
|
||||||
|
}
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 linkToChild(I16 startY, I16 startX, I16 endX, bool moreSiblings)
|
||||||
|
{
|
||||||
|
assert(endX >= startX);
|
||||||
|
const I16 len = endX - startX;
|
||||||
|
if (willBeVisible(startY, startX, 2, len))
|
||||||
|
{
|
||||||
|
AutoWin pad( newpad(2, len) );
|
||||||
|
|
||||||
|
for(U16 i = 0; i < len-1; ++i)
|
||||||
|
waddch(pad, ACS_HLINE);
|
||||||
|
/* whline(pad, '_', len-1); */
|
||||||
|
waddch(pad, moreSiblings ? ACS_TTEE : ACS_URCORNER);
|
||||||
|
|
||||||
|
mvwaddch(pad, 1, len-1, ACS_VLINE);
|
||||||
|
|
||||||
|
drawpad(pad, startY, startX);
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
I16 drawTreeLine(const Tree& t, const person_id id, I16 drawY, const I16 drawX)
|
||||||
|
{
|
||||||
|
const Person& person = t[id];
|
||||||
|
|
||||||
|
person_id spouse = t.findSpouse(id);
|
||||||
|
vector<person_id> children = t.findChildren(id);
|
||||||
|
|
||||||
|
const size_t numKids = children.size();
|
||||||
|
U16 person_w = person.drawAsFocused(drawY, drawX,
|
||||||
|
id == selected_, spouse!=Nobody, numKids!=0, zoom_);
|
||||||
|
|
||||||
|
U16 spouse_w = 0;
|
||||||
|
if (spouse != Nobody)
|
||||||
|
{
|
||||||
|
bool spouse_has_tree = (t.findParent(spouse, M) != Nobody) || (t.findParent(spouse, F) != Nobody);
|
||||||
|
U16 spouse_box = t[spouse].drawAsSpouse(drawY, drawX + person_w + spouse_dist_,
|
||||||
|
spouse == selected_, spouse_has_tree, zoom_);
|
||||||
|
spouse_w = spouse_dist_ + spouse_box;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto childX = drawX;
|
||||||
|
if (numKids!=0)
|
||||||
|
{
|
||||||
|
auto prevX = childX;
|
||||||
|
drawY += person_box_height(zoom_);
|
||||||
|
|
||||||
|
auto lnHt = linkToFirstBorn(drawY, childX, numKids > 1); /* Link height */
|
||||||
|
childX = drawTreeLine(t, children[0], drawY + lnHt, childX);
|
||||||
|
|
||||||
|
for(size_t j = 1; j < numKids; ++j)
|
||||||
|
{
|
||||||
|
/* +1 because LTC starts lower then LTFB */
|
||||||
|
lnHt = linkToChild(drawY+1, prevX+1, childX+1, j < numKids-1) + 1;
|
||||||
|
|
||||||
|
prevX = childX;
|
||||||
|
childX = drawTreeLine(t, children[j], drawY + lnHt, childX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(drawX + person_w + dist_between_ + spouse_w,
|
||||||
|
childX);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void displayHelp()
|
void displayHelp()
|
||||||
{
|
{
|
||||||
AutoWin help_tab( newwin(LINES, COLS, 0, 0) );
|
AutoWin help_tab( newwin(LINES, COLS, 0, 0) );
|
||||||
@@ -107,7 +239,7 @@ namespace
|
|||||||
|
|
||||||
person_id selectLeft(const Tree& t, person_id selected)
|
person_id selectLeft(const Tree& t, person_id selected)
|
||||||
{
|
{
|
||||||
if (t.findCommonAncestor(t.focused(), selected) != Nobody) //if the selected is part of main tree
|
if (t.findCommonAncestor(focused_, selected) != Nobody) //if the selected is part of main tree
|
||||||
{
|
{
|
||||||
selected = selectLeftSibSpouse(t, selected);
|
selected = selectLeftSibSpouse(t, selected);
|
||||||
}
|
}
|
||||||
@@ -138,12 +270,12 @@ namespace
|
|||||||
|
|
||||||
person_id selectUp(const Tree& t, person_id selected)
|
person_id selectUp(const Tree& t, person_id selected)
|
||||||
{
|
{
|
||||||
if (t.findCommonAncestor(t.focused(), selected) != Nobody) //if the selected is part of main tree
|
if (t.findCommonAncestor(focused_, selected) != Nobody) //if the selected is part of main tree
|
||||||
{
|
{
|
||||||
person_id dad = t.findParent(selected, M);
|
person_id dad = t.findParent(selected, M);
|
||||||
person_id mom = t.findParent(selected, F);
|
person_id mom = t.findParent(selected, F);
|
||||||
|
|
||||||
if (t.findCommonAncestor(t.focused(), mom) != Nobody) //if selected's mom is part of main tree
|
if (t.findCommonAncestor(focused_, mom) != Nobody) //if selected's mom is part of main tree
|
||||||
selected = mom;
|
selected = mom;
|
||||||
else if (dad != Nobody)
|
else if (dad != Nobody)
|
||||||
selected = dad;
|
selected = dad;
|
||||||
@@ -153,7 +285,7 @@ namespace
|
|||||||
|
|
||||||
person_id selectRight(const Tree& t, person_id selected)
|
person_id selectRight(const Tree& t, person_id selected)
|
||||||
{
|
{
|
||||||
if (t.findCommonAncestor(t.focused(), selected) != Nobody) //if the selected is part of main tree
|
if (t.findCommonAncestor(focused_, selected) != Nobody) //if the selected is part of main tree
|
||||||
{
|
{
|
||||||
if (person_id spouse = t.findSpouse(selected); spouse != Nobody)
|
if (person_id spouse = t.findSpouse(selected); spouse != Nobody)
|
||||||
{
|
{
|
||||||
|
|||||||
+14
-3
@@ -1,6 +1,17 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
// Handles keys and displays the tree
|
// Handles keys and displays the tree
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "common/basicTypes.h"
|
||||||
|
|
||||||
|
enum ZoomLevel: U8
|
||||||
|
{
|
||||||
|
NO_NAME,
|
||||||
|
FIRST_NAME,
|
||||||
|
ALL_NAMES,
|
||||||
|
MAX_ZOOM = ALL_NAMES
|
||||||
|
};
|
||||||
|
ZoomLevel operator++(ZoomLevel&);
|
||||||
|
ZoomLevel operator--(ZoomLevel&);
|
||||||
|
|
||||||
class Tree;
|
class Tree;
|
||||||
void displayTree(Tree&);
|
void displayTree(const Tree&);
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <ncursesw/ncurses.h>
|
#include <ncurses.h>
|
||||||
#include "common/basicTypes.h"
|
#include "common/basicTypes.h"
|
||||||
|
|
||||||
#define VISUALIZATION_HELP_STR "Visualization defaults (can be changed in config.h):\n" \
|
#define VISUALIZATION_HELP_STR "Visualization defaults (can be changed in config.h):\n" \
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
Give error when wrongly indexed person declaration
|
||||||
|
|
||||||
findRelative
|
findRelative
|
||||||
|
|
||||||
findRelation - extend
|
findRelation - extend
|
||||||
@@ -8,24 +10,25 @@ man page
|
|||||||
|
|
||||||
types - screenCoord...
|
types - screenCoord...
|
||||||
|
|
||||||
|
tft descriton - multiple trees, why its better, features, ^
|
||||||
|
tft merge trees relation
|
||||||
|
|
||||||
Visualization:
|
Visualization:
|
||||||
- zooming
|
- more granular zooming
|
||||||
- snap to selection/scroll with selection
|
- snap to selection/scroll with selection
|
||||||
- horizontal navigation between cousins - finish
|
- centralize when changing focus
|
||||||
|
- horizontal navigation between cousins
|
||||||
- color on different consoles
|
- color on different consoles
|
||||||
- screen resize
|
- screen resize
|
||||||
- empty tree
|
- empty tree
|
||||||
- half children and main tree
|
- half children and main tree
|
||||||
- distinct tree regions
|
|
||||||
- aproximate birth day
|
- aproximate birth day
|
||||||
- indicator for people with their own tree
|
|
||||||
|
|
||||||
Compilation
|
Compilation
|
||||||
- static and dynamic ncurses
|
- static and dynamic ncurses
|
||||||
|
|
||||||
?
|
?
|
||||||
- info bar
|
- info bar
|
||||||
- tree visualizer component
|
|
||||||
- distinct regions of a tree
|
- distinct regions of a tree
|
||||||
- same person can be displayed twice
|
- same person can be displayed twice
|
||||||
- coordinate types for large trees
|
- coordinate types for large trees
|
||||||
@@ -33,7 +36,8 @@ Compilation
|
|||||||
Refactoring
|
Refactoring
|
||||||
- comment style /*
|
- comment style /*
|
||||||
- check return codes - setlocale,
|
- check return codes - setlocale,
|
||||||
- focused -> root ?
|
- focused -> root
|
||||||
|
- tree::relations_ to std::set
|
||||||
|
|
||||||
test
|
test
|
||||||
- no Adam
|
- no Adam
|
||||||
|
|||||||
Reference in New Issue
Block a user