1998-10-21 20:29:01 +04:00
|
|
|
//
|
2001-11-25 19:38:11 +03:00
|
|
|
// "$Id: code.cxx,v 1.9.2.9.2.2 2001/11/25 16:38:11 easysw Exp $"
|
1998-10-21 20:29:01 +04:00
|
|
|
//
|
|
|
|
// Code output routines for the Fast Light Tool Kit (FLTK).
|
|
|
|
//
|
2001-01-22 18:13:41 +03:00
|
|
|
// Copyright 1998-2001 by Bill Spitzak and others.
|
1998-10-21 20:29:01 +04:00
|
|
|
//
|
|
|
|
// This library is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU Library General Public
|
|
|
|
// License as published by the Free Software Foundation; either
|
|
|
|
// version 2 of the License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This library is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// Library General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Library General Public
|
|
|
|
// License along with this library; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
|
|
// USA.
|
|
|
|
//
|
2000-06-06 01:21:24 +04:00
|
|
|
// Please report all bugs and problems to "fltk-bugs@fltk.org".
|
1998-10-21 20:29:01 +04:00
|
|
|
//
|
1998-10-06 22:21:25 +04:00
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
|
|
|
#include <FL/Fl.H>
|
1998-12-06 18:52:17 +03:00
|
|
|
#include "Fl_Type.h"
|
1998-12-06 18:09:22 +03:00
|
|
|
#include "alignment_panel.h"
|
1998-10-06 22:21:25 +04:00
|
|
|
|
|
|
|
static FILE *code_file;
|
|
|
|
static FILE *header_file;
|
2000-05-16 16:26:07 +04:00
|
|
|
|
|
|
|
extern char i18n_program[];
|
2000-04-24 22:22:50 +04:00
|
|
|
extern int i18n_type;
|
|
|
|
extern const char* i18n_include;
|
|
|
|
extern const char* i18n_function;
|
|
|
|
extern const char* i18n_file;
|
|
|
|
extern const char* i18n_set;
|
1998-10-06 22:21:25 +04:00
|
|
|
|
|
|
|
// return true if c can be in a C identifier. I needed this so
|
|
|
|
// it is not messed up by locale settings:
|
|
|
|
int is_id(char c) {
|
|
|
|
return c>='a' && c<='z' || c>='A' && c<='Z' || c>='0' && c<='9' || c=='_';
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Generate unique but human-readable identifiers:
|
|
|
|
|
|
|
|
struct id {
|
|
|
|
char* text;
|
|
|
|
void* object;
|
|
|
|
id *left, *right;
|
|
|
|
id (const char* t, void* o) : text(strdup(t)), object(o) {left = right = 0;}
|
|
|
|
~id();
|
|
|
|
};
|
|
|
|
|
|
|
|
id::~id() {
|
|
|
|
delete left;
|
|
|
|
free((void *)text);
|
|
|
|
delete right;
|
|
|
|
}
|
|
|
|
|
|
|
|
static id* id_root;
|
|
|
|
|
|
|
|
const char* unique_id(void* o, const char* type, const char* name, const char* label) {
|
|
|
|
char buffer[128];
|
|
|
|
char* q = buffer;
|
|
|
|
while (*type) *q++ = *type++;
|
|
|
|
*q++ = '_';
|
|
|
|
const char* n = name;
|
|
|
|
if (!n || !*n) n = label;
|
|
|
|
if (n && *n) {
|
2000-06-16 11:08:16 +04:00
|
|
|
while (*n && !is_id(*n)) n++;
|
1998-10-06 22:21:25 +04:00
|
|
|
while (is_id(*n)) *q++ = *n++;
|
|
|
|
}
|
|
|
|
*q = 0;
|
|
|
|
// okay, search the tree and see if the name was already used:
|
|
|
|
id** p = &id_root;
|
|
|
|
int which = 0;
|
|
|
|
while (*p) {
|
|
|
|
int i = strcmp(buffer, (*p)->text);
|
|
|
|
if (!i) {
|
|
|
|
if ((*p)->object == o) return (*p)->text;
|
|
|
|
// already used, we need to pick a new name:
|
|
|
|
sprintf(q,"%x",++which);
|
|
|
|
p = &id_root;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (i < 0) p = &((*p)->left);
|
|
|
|
else p = &((*p)->right);
|
|
|
|
}
|
|
|
|
*p = new id(buffer, o);
|
|
|
|
return (*p)->text;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// return current indentation:
|
|
|
|
|
|
|
|
static const char* spaces = " ";
|
|
|
|
int indentation;
|
|
|
|
const char* indent() {
|
|
|
|
int i = indentation; if (i>16) i = 16;
|
|
|
|
return spaces+16-i;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// declarations/include files:
|
1999-08-05 12:01:40 +04:00
|
|
|
// Each string generated by write_declare is written only once to
|
|
|
|
// the header file. This is done by keeping a binary tree of all
|
|
|
|
// the calls so far and not printing it if it is in the tree.
|
1998-10-06 22:21:25 +04:00
|
|
|
|
|
|
|
struct included {
|
|
|
|
char *text;
|
|
|
|
included *left, *right;
|
|
|
|
included(const char *t) {
|
|
|
|
text = strdup(t);
|
|
|
|
left = right = 0;
|
|
|
|
}
|
|
|
|
~included();
|
|
|
|
};
|
|
|
|
|
|
|
|
included::~included() {
|
|
|
|
delete left;
|
|
|
|
free((void *)text);
|
|
|
|
delete right;
|
|
|
|
}
|
|
|
|
static included *included_root;
|
|
|
|
|
|
|
|
int write_declare(const char *format, ...) {
|
|
|
|
va_list args;
|
|
|
|
char buf[1024];
|
|
|
|
va_start(args, format);
|
|
|
|
vsprintf(buf, format, args);
|
|
|
|
va_end(args);
|
|
|
|
included **p = &included_root;
|
|
|
|
while (*p) {
|
|
|
|
int i = strcmp(buf,(*p)->text);
|
|
|
|
if (!i) return 0;
|
|
|
|
else if (i < 0) p = &((*p)->left);
|
|
|
|
else p = &((*p)->right);
|
|
|
|
}
|
1999-08-05 12:01:40 +04:00
|
|
|
fprintf(header_file,"%s\n",buf);
|
1998-10-06 22:21:25 +04:00
|
|
|
*p = new included(buf);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// silly thing to prevent declaring unused variables:
|
|
|
|
// When this symbol is on, all attempts to write code don't write
|
|
|
|
// anything, but set a variable if it looks like the varaible "o" is used:
|
|
|
|
int varused_test;
|
|
|
|
int varused;
|
|
|
|
|
|
|
|
// write an array of C characters (adds a null):
|
|
|
|
void write_cstring(const char *w, int length) {
|
|
|
|
if (varused_test) return;
|
|
|
|
const char *e = w+length;
|
|
|
|
int linelength = 1;
|
|
|
|
putc('\"', code_file);
|
|
|
|
for (; w < e;) {
|
|
|
|
int c = *w++;
|
|
|
|
switch (c) {
|
|
|
|
case '\b': c = 'b'; goto QUOTED;
|
|
|
|
case '\t': c = 't'; goto QUOTED;
|
|
|
|
case '\n': c = 'n'; goto QUOTED;
|
|
|
|
case '\f': c = 'f'; goto QUOTED;
|
|
|
|
case '\r': c = 'r'; goto QUOTED;
|
|
|
|
case '\"':
|
|
|
|
case '\'':
|
|
|
|
case '\\':
|
|
|
|
QUOTED:
|
1999-07-31 12:00:09 +04:00
|
|
|
if (linelength >= 77) {fputs("\\\n",code_file); linelength = 0;}
|
|
|
|
putc('\\', code_file);
|
|
|
|
putc(c, code_file);
|
1998-10-06 22:21:25 +04:00
|
|
|
linelength += 2;
|
|
|
|
break;
|
1999-07-31 12:00:09 +04:00
|
|
|
case '?': // prevent trigraphs by writing ?? as ?\?
|
|
|
|
if (*(w-2) == '?') goto QUOTED;
|
1998-10-06 22:21:25 +04:00
|
|
|
// else fall through:
|
|
|
|
default:
|
1999-07-31 12:00:09 +04:00
|
|
|
if (c >= ' ' && c < 127) {
|
|
|
|
// a legal ASCII character
|
|
|
|
if (linelength >= 78) {fputs("\\\n",code_file); linelength = 0;}
|
|
|
|
putc(c, code_file);
|
1998-10-06 22:21:25 +04:00
|
|
|
linelength++;
|
1999-07-31 12:00:09 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
// otherwise we must print it as an octal constant:
|
|
|
|
c &= 255;
|
|
|
|
if (c < 8) {
|
|
|
|
if (linelength >= 76) {fputs("\\\n",code_file); linelength = 0;}
|
|
|
|
fprintf(code_file, "\\%o",c);
|
|
|
|
linelength += 2;
|
|
|
|
} else if (c < 64) {
|
|
|
|
if (linelength >= 75) {fputs("\\\n",code_file); linelength = 0;}
|
|
|
|
fprintf(code_file, "\\%o",c);
|
|
|
|
linelength += 3;
|
|
|
|
} else {
|
|
|
|
if (linelength >= 74) {fputs("\\\n",code_file); linelength = 0;}
|
|
|
|
fprintf(code_file, "\\%o",c);
|
|
|
|
linelength += 4;
|
|
|
|
}
|
|
|
|
// We must not put more numbers after it, because some C compilers
|
|
|
|
// consume them as part of the quoted sequence. Use string constant
|
|
|
|
// pasting to avoid this:
|
|
|
|
c = *w;
|
|
|
|
if (w < e && (c>='0'&&c<='9' || c>='a'&&c<='f' || c>='A'&&c<='F')) {
|
|
|
|
putc('\"', code_file); linelength++;
|
|
|
|
if (linelength >= 79) {fputs("\n",code_file); linelength = 0;}
|
|
|
|
putc('\"', code_file); linelength++;
|
1998-10-06 22:21:25 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
putc('\"', code_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
// write a C string, quoting characters if necessary:
|
|
|
|
void write_cstring(const char *w) {write_cstring(w,strlen(w));}
|
|
|
|
|
|
|
|
void write_c(const char* format,...) {
|
|
|
|
if (varused_test) {varused = 1; return;}
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
vfprintf(code_file, format, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
void write_h(const char* format,...) {
|
|
|
|
if (varused_test) return;
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
vfprintf(header_file, format, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
#include <FL/filename.H>
|
|
|
|
int write_number;
|
|
|
|
|
|
|
|
// recursively dump code, putting children between the two parts
|
|
|
|
// of the parent code:
|
|
|
|
static Fl_Type* write_code(Fl_Type* p) {
|
|
|
|
p->write_code1();
|
|
|
|
Fl_Type* q;
|
|
|
|
for (q = p->next; q && q->level > p->level;) q = write_code(q);
|
|
|
|
p->write_code2();
|
|
|
|
return q;
|
|
|
|
}
|
|
|
|
|
2001-08-02 20:17:04 +04:00
|
|
|
extern const char* header_file_name;
|
|
|
|
|
1998-10-06 22:21:25 +04:00
|
|
|
int write_code(const char *s, const char *t) {
|
|
|
|
write_number++;
|
|
|
|
delete id_root; id_root = 0;
|
|
|
|
indentation = 0;
|
|
|
|
if (!s) code_file = stdout;
|
|
|
|
else {
|
|
|
|
FILE *f = fopen(s,"w");
|
|
|
|
if (!f) return 0;
|
|
|
|
code_file = f;
|
|
|
|
}
|
|
|
|
if (!t) header_file = stdout;
|
|
|
|
else {
|
|
|
|
FILE *f = fopen(t,"w");
|
|
|
|
if (!f) {fclose(code_file); return 0;}
|
|
|
|
header_file = f;
|
|
|
|
}
|
|
|
|
const char *hdr = "\
|
2000-02-25 06:44:22 +03:00
|
|
|
// generated by Fast Light User Interface Designer (fluid) version %.4f\n\n";
|
1998-10-06 22:21:25 +04:00
|
|
|
fprintf(header_file, hdr, FL_VERSION);
|
|
|
|
fprintf(code_file, hdr, FL_VERSION);
|
|
|
|
|
1999-01-19 22:10:39 +03:00
|
|
|
{char define_name[102];
|
|
|
|
const char* a = filename_name(t);
|
|
|
|
char* b = define_name;
|
|
|
|
if (!isalpha(*a)) {*b++ = '_';}
|
|
|
|
while (*a) {*b++ = isalnum(*a) ? *a : '_'; a++;}
|
|
|
|
*b = 0;
|
|
|
|
fprintf(header_file, "#ifndef %s\n", define_name);
|
|
|
|
fprintf(header_file, "#define %s\n", define_name);
|
|
|
|
}
|
|
|
|
|
1999-08-05 12:01:40 +04:00
|
|
|
write_declare("#include <FL/Fl.H>");
|
2000-04-25 05:57:51 +04:00
|
|
|
if (i18n_type && i18n_include[0]) {
|
|
|
|
if (i18n_include[0] != '<' &&
|
|
|
|
i18n_include[0] != '\"')
|
|
|
|
write_c("#include \"%s\"\n", i18n_include);
|
2000-04-24 22:22:50 +04:00
|
|
|
else
|
2000-04-25 05:57:51 +04:00
|
|
|
write_c("#include %s\n", i18n_include);
|
|
|
|
if (i18n_type == 2) {
|
|
|
|
if (i18n_file[0]) write_c("extern nl_catd %s;\n", i18n_file);
|
2000-05-16 16:26:07 +04:00
|
|
|
else {
|
|
|
|
write_c("// Initialize I18N stuff now for menus...\n");
|
|
|
|
write_c("#include <locale.h>\n");
|
|
|
|
write_c("static char *_locale = setlocale(LC_MESSAGES, \"\");\n");
|
|
|
|
write_c("static nl_catd _catalog = catopen(\"%s\", 0);\n",
|
|
|
|
i18n_program);
|
|
|
|
}
|
2000-04-25 05:57:51 +04:00
|
|
|
}
|
2000-04-24 22:22:50 +04:00
|
|
|
}
|
2001-08-02 20:17:04 +04:00
|
|
|
if (t && include_H_from_C) {
|
|
|
|
if (*header_file_name == '.' && strchr(header_file_name, '/') == NULL) {
|
|
|
|
write_c("#include \"%s\"\n", filename_name(t));
|
|
|
|
} else {
|
|
|
|
write_c("#include \"%s\"\n", t);
|
|
|
|
}
|
|
|
|
}
|
1999-08-05 12:01:40 +04:00
|
|
|
for (Fl_Type* p = Fl_Type::first; p;) {
|
1998-10-21 19:42:05 +04:00
|
|
|
// write all static data for this & all children first
|
|
|
|
p->write_static();
|
|
|
|
for (Fl_Type* q = p->next; q && q->level > p->level; q = q->next)
|
|
|
|
q->write_static();
|
|
|
|
// then write the nested code:
|
|
|
|
p = write_code(p);
|
|
|
|
}
|
1998-10-06 22:21:25 +04:00
|
|
|
|
1999-08-05 12:01:40 +04:00
|
|
|
delete included_root; included_root = 0;
|
|
|
|
|
1998-10-06 22:21:25 +04:00
|
|
|
if (!s) return 1;
|
|
|
|
int x = fclose(code_file);
|
|
|
|
code_file = 0;
|
1999-01-19 22:10:39 +03:00
|
|
|
fprintf(header_file, "#endif\n");
|
1998-10-06 22:21:25 +04:00
|
|
|
int y = fclose(header_file);
|
|
|
|
header_file = 0;
|
|
|
|
return x >= 0 && y >= 0;
|
|
|
|
}
|
|
|
|
|
2000-04-24 22:22:50 +04:00
|
|
|
int write_strings(const char *sfile) {
|
|
|
|
FILE *fp = fopen(sfile, "w");
|
|
|
|
Fl_Type *p;
|
2001-11-25 19:38:11 +03:00
|
|
|
Fl_Widget_Type *w;
|
2000-05-16 16:26:07 +04:00
|
|
|
int i;
|
2000-04-24 22:22:50 +04:00
|
|
|
|
|
|
|
if (!fp) return 1;
|
|
|
|
|
|
|
|
switch (i18n_type) {
|
|
|
|
case 0 : /* None, just put static text out */
|
|
|
|
fprintf(fp, "# generated by Fast Light User Interface Designer (fluid) version %.4f\n",
|
|
|
|
FL_VERSION);
|
|
|
|
for (p = Fl_Type::first; p; p = p->next) {
|
2001-11-25 19:38:11 +03:00
|
|
|
if (p->is_widget()) {
|
|
|
|
w = (Fl_Widget_Type *)p;
|
|
|
|
|
|
|
|
if (w->label()) {
|
|
|
|
for (const char *s = w->label(); *s; s ++)
|
|
|
|
if (*s < 32 || *s > 126 || *s == '\"')
|
|
|
|
fprintf(fp, "\\%03o", *s);
|
|
|
|
else
|
|
|
|
putc(*s, fp);
|
|
|
|
putc('\n', fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (w->tooltip()) {
|
|
|
|
for (const char *s = w->tooltip(); *s; s ++)
|
|
|
|
if (*s < 32 || *s > 126 || *s == '\"')
|
|
|
|
fprintf(fp, "\\%03o", *s);
|
|
|
|
else
|
|
|
|
putc(*s, fp);
|
|
|
|
putc('\n', fp);
|
|
|
|
}
|
2000-04-24 22:22:50 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 1 : /* GNU gettext, put a .po file out */
|
|
|
|
fprintf(fp, "# generated by Fast Light User Interface Designer (fluid) version %.4f\n",
|
|
|
|
FL_VERSION);
|
|
|
|
for (p = Fl_Type::first; p; p = p->next) {
|
2001-11-25 19:38:11 +03:00
|
|
|
if (p->is_widget()) {
|
|
|
|
w = (Fl_Widget_Type *)p;
|
|
|
|
|
|
|
|
if (w->label()) {
|
|
|
|
const char *s;
|
|
|
|
|
|
|
|
fputs("msgid \"", fp);
|
|
|
|
for (s = w->label(); *s; s ++)
|
|
|
|
if (*s < 32 || *s > 126 || *s == '\"')
|
|
|
|
fprintf(fp, "\\%03o", *s);
|
|
|
|
else
|
|
|
|
putc(*s, fp);
|
|
|
|
fputs("\"\n", fp);
|
|
|
|
|
|
|
|
fputs("msgstr \"", fp);
|
|
|
|
for (s = w->label(); *s; s ++)
|
|
|
|
if (*s < 32 || *s > 126 || *s == '\"')
|
|
|
|
fprintf(fp, "\\%03o", *s);
|
|
|
|
else
|
|
|
|
putc(*s, fp);
|
|
|
|
fputs("\"\n", fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (w->tooltip()) {
|
|
|
|
const char *s;
|
|
|
|
|
|
|
|
fputs("msgid \"", fp);
|
|
|
|
for (s = w->tooltip(); *s; s ++)
|
|
|
|
if (*s < 32 || *s > 126 || *s == '\"')
|
|
|
|
fprintf(fp, "\\%03o", *s);
|
|
|
|
else
|
|
|
|
putc(*s, fp);
|
|
|
|
fputs("\"\n", fp);
|
|
|
|
|
|
|
|
fputs("msgstr \"", fp);
|
|
|
|
for (s = w->tooltip(); *s; s ++)
|
|
|
|
if (*s < 32 || *s > 126 || *s == '\"')
|
|
|
|
fprintf(fp, "\\%03o", *s);
|
|
|
|
else
|
|
|
|
putc(*s, fp);
|
|
|
|
fputs("\"\n", fp);
|
|
|
|
}
|
2000-04-24 22:22:50 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 2 : /* POSIX catgets, put a .msg file out */
|
|
|
|
fprintf(fp, "$ generated by Fast Light User Interface Designer (fluid) version %.4f\n",
|
|
|
|
FL_VERSION);
|
2000-04-25 05:57:51 +04:00
|
|
|
fprintf(fp, "$set %s\n", i18n_set);
|
2000-04-24 22:22:50 +04:00
|
|
|
fputs("$quote \"\n", fp);
|
|
|
|
|
2000-05-16 16:26:07 +04:00
|
|
|
for (i = 1, p = Fl_Type::first; p; p = p->next) {
|
2001-11-25 19:38:11 +03:00
|
|
|
if (p->is_widget()) {
|
|
|
|
w = (Fl_Widget_Type *)p;
|
|
|
|
|
|
|
|
if (w->label()) {
|
|
|
|
fprintf(fp, "%d \"", i ++);
|
|
|
|
for (const char *s = w->label(); *s; s ++)
|
|
|
|
if (*s < 32 || *s > 126 || *s == '\"')
|
|
|
|
fprintf(fp, "\\%03o", *s);
|
|
|
|
else
|
|
|
|
putc(*s, fp);
|
|
|
|
fputs("\"\n", fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (w->tooltip()) {
|
|
|
|
fprintf(fp, "%d \"", i ++);
|
|
|
|
for (const char *s = w->tooltip(); *s; s ++)
|
|
|
|
if (*s < 32 || *s > 126 || *s == '\"')
|
|
|
|
fprintf(fp, "\\%03o", *s);
|
|
|
|
else
|
|
|
|
putc(*s, fp);
|
|
|
|
fputs("\"\n", fp);
|
|
|
|
}
|
2000-04-24 22:22:50 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fclose(fp);
|
|
|
|
}
|
|
|
|
|
1998-10-06 22:21:25 +04:00
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void Fl_Type::write_static() {}
|
|
|
|
void Fl_Type::write_code1() {
|
|
|
|
write_h("// Header for %s\n", title());
|
|
|
|
write_c("// Code for %s\n", title());
|
|
|
|
}
|
|
|
|
void Fl_Type::write_code2() {}
|
1998-10-21 20:29:01 +04:00
|
|
|
|
|
|
|
//
|
2001-11-25 19:38:11 +03:00
|
|
|
// End of "$Id: code.cxx,v 1.9.2.9.2.2 2001/11/25 16:38:11 easysw Exp $".
|
1998-10-21 20:29:01 +04:00
|
|
|
//
|