diff options
-rwxr-xr-x | doc/tools/doc_status.py | 430 | ||||
-rw-r--r-- | scene/gui/control.cpp | 2 | ||||
-rw-r--r-- | scene/resources/theme.cpp | 9 | ||||
-rw-r--r-- | scene/resources/theme.h | 2 | ||||
-rw-r--r-- | tools/editor/plugins/theme_editor_plugin.cpp | 64 | ||||
-rw-r--r-- | tools/editor/plugins/theme_editor_plugin.h | 1 |
6 files changed, 505 insertions, 3 deletions
diff --git a/doc/tools/doc_status.py b/doc/tools/doc_status.py new file mode 100755 index 0000000000..5faee570d8 --- /dev/null +++ b/doc/tools/doc_status.py @@ -0,0 +1,430 @@ +#!/usr/bin/python3 + +import sys +import re +import math +import platform +import xml.etree.ElementTree as ET + +################################################################################ +# Config # +################################################################################ + +flags = { + 'c': platform.platform() != 'Windows', # Disable by default on windows, since we use ANSI escape codes + 'b': False, + 'g': False, + 'u': False, + 'h': False, + 'p': False, + 'o': True, + 'i': False, +} +flag_descriptions = { + 'c': 'Toggle colors when outputting.', + 'b': 'Toggle showing only not fully described classes.', + 'g': 'Toggle showing only completed classes.', + 'u': 'Toggle URLs to docs.', + 'h': 'Show help and exit.', + 'p': 'Toggle showing percentage as well as counts.', + 'o': 'Toggle overall column.', + 'i': 'Toggle collapse of class items columns.', +} +long_flags = { + 'colors': 'c', + 'color': 'c', + 'use-colors': 'c', + + 'bad': 'b', + 'only-bad': 'b', + + 'good': 'g', + 'only-good': 'g', + + 'urls': 'u', + 'url': 'u', + 'generate-urls': 'u', + 'gen-url': 'u', + + 'help': 'h', + + 'percent': 'p', + 'percentages': 'p', + 'use-percentages': 'p', + + 'overall': 'o', + 'use-overall': 'o', + + 'items': 'i', + 'collapse-items': 'i', + 'collapse': 'i', + 'narrow': 'i', +} +table_columns = ['name', 'brief_description', 'description', 'methods', 'constants', 'members', 'signals'] +table_column_names = ['Name', 'Brief Description', 'Description', 'Methods', 'Constants', 'Members', 'Signals'] +colors = { + 'name': [34], # blue + 'part_big_problem': [4, 31], # underline, red + 'part_problem': [31], # red + 'part_mostly_good': [33], # yellow + 'part_good': [32], # green + 'url': [4, 34], # underline, blue + 'section': [1, 4], # bold, underline + 'state_off': [36], # cyan + 'state_on': [1, 35], # bold, magenta/plum +} +overall_progress_description_weigth = 10 + + + +################################################################################ +# Utils # +################################################################################ + +def validate_tag(elem, tag): + if elem.tag != tag: + print('Tag mismatch, expected "' + tag + '", got ' + elem.tag) + sys.exit(255) + +def color(color, string): + if flags['c']: + color_format = '' + for code in colors[color]: + color_format += '\033[' + str(code) + 'm' + return color_format + string + '\033[0m' + else: + return string + +ansi_escape = re.compile(r'\x1b[^m]*m') +def nonescape_len(s): + return len(ansi_escape.sub('', s)) + + + +################################################################################ +# Classes # +################################################################################ + +class ClassStatusProgress: + def __init__(self, described = 0, total = 0): + self.described = described + self.total = total + + def __add__(self, other): + return ClassStatusProgress(self.described + other.described, self.total + other.total) + + def increment(self, described): + if described: + self.described += 1 + self.total += 1 + + def is_ok(self): + return self.described >= self.total + + def to_configured_colored_string(self): + if flags['p']: + return self.to_colored_string('{percent}% ({has}/{total})', '{pad_percent}{pad_described}{s}{pad_total}') + else: + return self.to_colored_string() + + def to_colored_string(self, format='{has}/{total}', pad_format='{pad_described}{s}{pad_total}'): + ratio = self.described/self.total if self.total != 0 else 1 + percent = round(100*ratio) + s = format.format(has = str(self.described), total = str(self.total), percent = str(percent)) + if self.described >= self.total: + s = color('part_good', s) + elif self.described >= self.total/4*3: + s = color('part_mostly_good', s) + elif self.described > 0: + s = color('part_problem', s) + else: + s = color('part_big_problem', s) + pad_size = max(len(str(self.described)), len(str(self.total))) + pad_described = ''.ljust(pad_size - len(str(self.described))) + pad_percent = ''.ljust(3 - len(str(percent))) + pad_total = ''.ljust(pad_size - len(str(self.total))) + return pad_format.format(pad_described = pad_described, pad_total = pad_total, pad_percent = pad_percent, s = s) + + +class ClassStatus: + def __init__(self, name=''): + self.name = name + self.has_brief_description = True + self.has_description = True + self.progresses = { + 'methods': ClassStatusProgress(), + 'constants': ClassStatusProgress(), + 'members': ClassStatusProgress(), + 'signals': ClassStatusProgress() + } + + def __add__(self, other): + new_status = ClassStatus() + new_status.name = self.name + new_status.has_brief_description = self.has_brief_description and other.has_brief_description + new_status.has_description = self.has_description and other.has_description + for k in self.progresses: + new_status.progresses[k] = self.progresses[k] + other.progresses[k] + return new_status + + def is_ok(self): + ok = True + ok = ok and self.has_brief_description + ok = ok and self.has_description + for k in self.progresses: + ok = ok and self.progresses[k].is_ok() + return ok + + def make_output(self): + output = {} + output['name'] = color('name', self.name) + + ok_string = color('part_good', 'OK') + missing_string = color('part_big_problem', 'MISSING') + + output['brief_description'] = ok_string if self.has_brief_description else missing_string + output['description'] = ok_string if self.has_description else missing_string + + description_progress = ClassStatusProgress( + (self.has_brief_description + self.has_description) * overall_progress_description_weigth, + 2 * overall_progress_description_weigth + ) + items_progress = ClassStatusProgress() + + for k in ['methods', 'constants', 'members', 'signals']: + items_progress += self.progresses[k] + output[k] = self.progresses[k].to_configured_colored_string() + + output['items'] = items_progress.to_configured_colored_string() + + output['overall'] = (description_progress + items_progress).to_colored_string('{percent}%', '{pad_percent}{s}') + + if self.name.startswith('Total'): + output['url'] = color('url', 'http://docs.godotengine.org/en/latest/classes/_classes.html') + output['comment'] = color('part_good', 'ALL OK') + else: + output['url'] = color('url', 'http://docs.godotengine.org/en/latest/classes/class_{name}.html'.format(name=self.name.lower())) + + if not flags['g'] and self.is_ok(): + output['comment'] = color('part_good', 'ALL OK') + + return output + + def generate_for_class(c): + status = ClassStatus() + status.name = c.attrib['name'] + for tag in list(c): + + if tag.tag == 'brief_description': + status.has_brief_description = len(tag.text.strip()) > 0 + + elif tag.tag == 'description': + status.has_description = len(tag.text.strip()) > 0 + + elif tag.tag in ['methods', 'signals']: + for sub_tag in list(tag): + descr = sub_tag.find('description') + status.progresses[tag.tag].increment(len(descr.text.strip()) > 0) + + elif tag.tag in ['constants', 'members']: + for sub_tag in list(tag): + status.progresses[tag.tag].increment(len(sub_tag.text.strip()) > 0) + + elif tag.tag in ['theme_items']: + pass #Ignore those tags, since they seem to lack description at all + + else: + print(tag.tag, tag.attrib) + + return status + + + +################################################################################ +# Arguments # +################################################################################ + +input_file_list = [] +input_class_list = [] + +for arg in sys.argv[1:]: + if arg.startswith('--'): + flags[long_flags[arg[2:]]] = not flags[long_flags[arg[2:]]] + elif arg.startswith('-'): + for f in arg[1:]: + flags[f] = not flags[f] + elif arg.endswith('.xml'): + input_file_list.append(arg) + else: + input_class_list.append(arg) + +if flags['i']: + for r in ['methods', 'constants', 'members', 'signals']: + index = table_columns.index(r) + del table_column_names[index] + del table_columns[index] + table_column_names.append('Items') + table_columns.append('items') + +if flags['o'] == (not flags['i']): + table_column_names.append('Overall') + table_columns.append('overall') + +if flags['u']: + table_column_names.append('Docs URL') + table_columns.append('url') + + +################################################################################ +# Help # +################################################################################ + +if len(input_file_list) < 1 or flags['h']: + if not flags['h']: + print(color('section', 'Invalid usage') + ': At least one classes.xml file is required') + print(color('section', 'Usage') + ': doc_status.py [flags] <classes.xml> [class names]') + print('\t< and > signify required parameters, while [ and ] signify optional parameters.') + print('\tNote that you can give more than one classes file, in which case they will be merged on-the-fly.') + print(color('section', 'Available flags') + ':') + possible_synonym_list = list(long_flags) + possible_synonym_list.sort() + flag_list = list(flags) + flag_list.sort() + for flag in flag_list: + synonyms = [color('name', '-' + flag)] + for synonym in possible_synonym_list: + if long_flags[synonym] == flag: + synonyms.append(color('name', '--' + synonym)) + + print(('{synonyms} (Currently '+color('state_'+('on' if flags[flag] else 'off'), '{value}')+')\n\t{description}').format( + synonyms = ', '.join(synonyms), + value = ('on' if flags[flag] else 'off'), + description = flag_descriptions[flag] + )) + sys.exit(0) + + + +################################################################################ +# Parse class list # +################################################################################ + +class_names = [] +classes = {} + +for file in input_file_list: + tree = ET.parse(file) + doc = tree.getroot() + + if 'version' not in doc.attrib: + print('Version missing from "doc"') + sys.exit(255) + + version = doc.attrib['version'] + + for c in list(doc): + if c.attrib['name'] in class_names: + continue + class_names.append(c.attrib['name']) + classes[c.attrib['name']] = c + +class_names.sort() + +if len(input_class_list) < 1: + input_class_list = class_names + + + +################################################################################ +# Make output table # +################################################################################ + +table = [table_column_names] +table_row_chars = '+- ' +table_column_chars = '|' + +total_status = ClassStatus('Total') + +for cn in input_class_list: + if not cn in classes: + print('Cannot find class ' + cn + '!') + sys.exit(255) + + c = classes[cn] + validate_tag(c, 'class') + status = ClassStatus.generate_for_class(c) + + if flags['b'] and status.is_ok(): + continue + if flags['g'] and not status.is_ok(): + continue + + total_status = total_status + status + out = status.make_output() + row = [] + for column in table_columns: + if column in out: + row.append(out[column]) + else: + row.append('') + + if 'comment' in out and out['comment'] != '': + row.append(out['comment']) + + table.append(row) + + + + +################################################################################ +# Print output table # +################################################################################ + +if len(table) == 1: + print(color('part_big_problem', 'No classes suitable for printing!')) + sys.exit(0) + +if len(table) > 2: + total_status.name = 'Total = {0}'.format(len(table) - 1) + out = total_status.make_output() + row = [] + for column in table_columns: + if column in out: + row.append(out[column]) + else: + row.append('') + table.append(row) + +table_column_sizes = [] +for row in table: + for cell_i, cell in enumerate(row): + if cell_i >= len(table_column_sizes): + table_column_sizes.append(0) + + table_column_sizes[cell_i] = max(nonescape_len(cell), table_column_sizes[cell_i]) + +divider_string = table_row_chars[0] +for cell_i in range(len(table[0])): + divider_string += table_row_chars[1] * (table_column_sizes[cell_i] + 2) + table_row_chars[0] +print(divider_string) + +for row_i, row in enumerate(table): + row_string = table_column_chars + for cell_i, cell in enumerate(row): + padding_needed = table_column_sizes[cell_i] - nonescape_len(cell) + 2 + if cell_i == 0: + row_string += table_row_chars[2] + cell + table_row_chars[2]*(padding_needed-1) + else: + row_string += table_row_chars[2]*math.floor(padding_needed/2) + cell + table_row_chars[2]*math.ceil((padding_needed/2)) + row_string += table_column_chars + + print(row_string) + + if row_i == 0 or row_i == len(table) - 2: + print(divider_string) + +print(divider_string) + +if total_status.is_ok() and not flags['g']: + print('All listed classes are ' + color('part_good', 'OK') + '!') + diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index a30c05527c..2912dcb3ae 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -2310,7 +2310,7 @@ void Control::_bind_methods() { BIND_CONSTANT( ANCHOR_BEGIN ); BIND_CONSTANT( ANCHOR_END ); BIND_CONSTANT( ANCHOR_RATIO ); - BIND_CONSTANT( ANCHOR_CENTER ); + BIND_CONSTANT( ANCHOR_CENTER ); BIND_CONSTANT( FOCUS_NONE ); BIND_CONSTANT( FOCUS_CLICK ); BIND_CONSTANT( FOCUS_ALL ); diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index 74e59b09c3..20405a57b2 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -370,6 +370,13 @@ void Theme::get_stylebox_list(StringName p_type, List<StringName> *p_list) const } +void Theme::get_stylebox_types(List<StringName> *p_list) const { + const StringName *key=NULL; + while((key=style_map.next(key))) { + p_list->push_back(*key); + } +} + void Theme::set_font(const StringName& p_name,const StringName& p_type,const Ref<Font>& p_font) { ERR_FAIL_COND(p_font.is_null()); @@ -380,7 +387,6 @@ void Theme::set_font(const StringName& p_name,const StringName& p_type,const Ref if (new_value) { _change_notify(); emit_changed();; - } } Ref<Font> Theme::get_font(const StringName& p_name,const StringName& p_type) const { @@ -604,6 +610,7 @@ void Theme::_bind_methods() { ObjectTypeDB::bind_method(_MD("has_stylebox","name","type"),&Theme::has_stylebox); ObjectTypeDB::bind_method(_MD("clear_stylebox","name","type"),&Theme::clear_stylebox); ObjectTypeDB::bind_method(_MD("get_stylebox_list","type"),&Theme::_get_stylebox_list); + ObjectTypeDB::bind_method(_MD("get_stylebox_types"),&Theme::_get_stylebox_types); ObjectTypeDB::bind_method(_MD("set_font","name","type","font:Font"),&Theme::set_font); ObjectTypeDB::bind_method(_MD("get_font:Font","name","type"),&Theme::get_font); diff --git a/scene/resources/theme.h b/scene/resources/theme.h index 36630087f6..9b84d0e8ad 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -65,6 +65,7 @@ protected: DVector<String> _get_icon_list(const String& p_type) const { DVector<String> ilret; List<StringName> il; get_icon_list(p_type,&il); for(List<StringName>::Element *E=il.front();E;E=E->next()) { ilret.push_back(E->get()); } return ilret; } DVector<String> _get_stylebox_list(const String& p_type) const { DVector<String> ilret; List<StringName> il; get_stylebox_list(p_type,&il); for(List<StringName>::Element *E=il.front();E;E=E->next()) { ilret.push_back(E->get()); } return ilret; } + DVector<String> _get_stylebox_types(void) const { DVector<String> ilret; List<StringName> il; get_stylebox_types(&il); for(List<StringName>::Element *E=il.front();E;E=E->next()) { ilret.push_back(E->get()); } return ilret; } DVector<String> _get_font_list(const String& p_type) const { DVector<String> ilret; List<StringName> il; get_font_list(p_type,&il); for(List<StringName>::Element *E=il.front();E;E=E->next()) { ilret.push_back(E->get()); } return ilret; } DVector<String> _get_color_list(const String& p_type) const { DVector<String> ilret; List<StringName> il; get_color_list(p_type,&il); for(List<StringName>::Element *E=il.front();E;E=E->next()) { ilret.push_back(E->get()); } return ilret; } DVector<String> _get_constant_list(const String& p_type) const { DVector<String> ilret; List<StringName> il; get_constant_list(p_type,&il); for(List<StringName>::Element *E=il.front();E;E=E->next()) { ilret.push_back(E->get()); } return ilret; } @@ -100,6 +101,7 @@ public: bool has_stylebox(const StringName& p_name,const StringName& p_type) const; void clear_stylebox(const StringName& p_name,const StringName& p_type); void get_stylebox_list(StringName p_type, List<StringName> *p_list) const; + void get_stylebox_types(List<StringName> *p_list) const; void set_font(const StringName& p_name,const StringName& p_type,const Ref<Font>& p_font); Ref<Font> get_font(const StringName& p_name,const StringName& p_type) const; diff --git a/tools/editor/plugins/theme_editor_plugin.cpp b/tools/editor/plugins/theme_editor_plugin.cpp index 938b8344ba..e792a2a9e2 100644 --- a/tools/editor/plugins/theme_editor_plugin.cpp +++ b/tools/editor/plugins/theme_editor_plugin.cpp @@ -398,7 +398,55 @@ void ThemeEditor::_dialog_cbk() { } - } break; + } break; + case POPUP_CLASS_REMOVE: { + StringName fromtype = type_edit->get_text(); + List<StringName> names; + + { + names.clear(); + Theme::get_default()->get_icon_list(fromtype,&names); + for(List<StringName>::Element *E=names.front();E;E=E->next()) { + theme->clear_icon(E->get(),fromtype); + + } + + } + { + names.clear(); + Theme::get_default()->get_stylebox_list(fromtype,&names); + for(List<StringName>::Element *E=names.front();E;E=E->next()) { + theme->clear_stylebox(E->get(),fromtype); + + } + + } + { + names.clear(); + Theme::get_default()->get_font_list(fromtype,&names); + for(List<StringName>::Element *E=names.front();E;E=E->next()) { + theme->clear_font(E->get(),fromtype); + + } + } + { + names.clear(); + Theme::get_default()->get_color_list(fromtype,&names); + for(List<StringName>::Element *E=names.front();E;E=E->next()) { + theme->clear_color(E->get(),fromtype); + + } + } + { + names.clear(); + Theme::get_default()->get_constant_list(fromtype,&names); + for(List<StringName>::Element *E=names.front();E;E=E->next()) { + theme->clear_constant(E->get(),fromtype); + + } + } + + }break; } } @@ -453,6 +501,19 @@ void ThemeEditor::_theme_menu_cbk(int p_option) { base_theme=theme; + } else if (p_option==POPUP_CLASS_REMOVE) { + + add_del_dialog->set_title("Remove All Items"); + add_del_dialog->get_ok()->set_text("Remove All"); + add_del_dialog->popup_centered(Size2(240,85)); + + base_theme=Theme::get_default(); + + type_select->hide(); + name_select_label->hide(); + type_select_label->hide(); + name_edit->hide(); + name_menu->hide(); } popup_mode=p_option; @@ -538,6 +599,7 @@ ThemeEditor::ThemeEditor() { theme_menu->get_popup()->add_item("Add Item",POPUP_ADD); theme_menu->get_popup()->add_item("Add Class Items",POPUP_CLASS_ADD); theme_menu->get_popup()->add_item("Remove Item",POPUP_REMOVE); + theme_menu->get_popup()->add_item("Remove Class Items",POPUP_CLASS_REMOVE); theme_menu->get_popup()->add_separator(); theme_menu->get_popup()->add_item("Create Template",POPUP_CREATE_TEMPLATE); hb_menu->add_child(theme_menu); diff --git a/tools/editor/plugins/theme_editor_plugin.h b/tools/editor/plugins/theme_editor_plugin.h index 52c4aed839..49d5ae3096 100644 --- a/tools/editor/plugins/theme_editor_plugin.h +++ b/tools/editor/plugins/theme_editor_plugin.h @@ -67,6 +67,7 @@ class ThemeEditor : public Control { POPUP_ADD, POPUP_CLASS_ADD, POPUP_REMOVE, + POPUP_CLASS_REMOVE, POPUP_CREATE_TEMPLATE }; |