class File_Saver(pride.gui.form.Scrollable_Window): defaults = {"data" : bytes(), "filename" : '', "autodelete" : False, "location" : "top"} subcomponents = {"vertical_slider" : Component(location=None), "horizontal_slider" : Component(location=None), "_file_saver" : Component("pride.gui.programs.fileexplorer._File_Saver", h_range=(0, .1), location="left"), "directory_viewer" : Component("pride.gui.programs.fileexplorer.Directory_Viewer", location="left")} autoreferences = ("prompt", "file_saver", "directory_viewer") def create_subcomponents(self): super(File_Saver, self).create_subcomponents() window = self.main_window self.file_saver = window.create(self._file_saver_type, data=self.data, target_object=self, **self._file_saver_kwargs) self.directory_viewer = window.create(self.directory_viewer_type, **self.directory_viewer_kwargs) def save_data(self, overwrite=False): filename = os.path.join(self.directory_viewer.current_node, self.filename) filename = os.path.expanduser(filename) already_exists = os.path.exists(filename) if already_exists and self.prompt is None: if overwrite == False: self.prompt = self.main_window.create(Overwrite_Prompt) #else: # if self.prompt is not None and not self.prompt.deleted: # self.prompt.delete() # assert self.prompt is None if overwrite or not already_exists: size = len(self.data) units = ["bytes", "KB", "MB", "GB", "TB"] index = 0 while size > 1024: index += 1 size /= 1024.0 self.show_status("Saving {0:.2f}{1} to {2}...".format(size, units[index], filename)) try: with open(filename, "wb") as _file: _file.write(self.data) except IOError: if os.path.exists(filename): raise self.show_status("Unable to write to file {}; Ensure all directories exist and are spelled correctly and try again.".format(filename)) else: if self.autodelete: self.delete()
class Theme_Editor(pride.gui.link.Linked_Form): defaults = {"target_theme" : None} subcomponents = {"form" : Component("pride.gui.programs.themecustomizer2.Theme_Form"), "tab_bar" : Component(include_new_tab_button=False), "file_saver" : Component("pride.gui.programs.fileexplorer.File_Saver", location="top", autodelete=True), "file_selector" : Component("pride.gui.programs.fileexplorer.File_Selector")} autoreferences = ("file_saver", "file_selector") def create_subcomponents(self): if self.target_theme is None: self.target_theme = self.theme theme = self.target_theme theme_editor_layout = generate_theme_editor_layout(theme) options_layout = generate_options_layout() self.layout = layout(links=(page("editor", theme_editor_layout), page("options", options_layout))) super(Theme_Editor, self).create_subcomponents() def save_theme(self): if self.file_saver is None: # have to use this window to make the result look "right" window = self.main_window.children[0].form.main_window file_saver = window.create(self.file_saver_type, data=self.theme.serialize(), **self.file_saver_kwargs) self.file_saver = file_saver else: self.file_saver.delete() def load_theme(self): if self.file_selector is None: # have to use this window to make the result look "right" window = self.main_window.children[0].form.main_window file_selector = window.create(self.file_selector_type, callback=self._load_theme, **self.file_selector_kwargs) self.file_selector = file_selector else: self.file_selector.delete() def _load_theme(self, filename): with open(filename, 'r') as _file: _bytes = _file.read() theme_colors = self.theme.deserialize(_bytes) self.theme.update_theme_colors(theme_colors)
class Spinbox(Field): defaults = {"minimum" : None, "maximum" : None} # can only use either minimum or maximum but not both by default # must specify Spinbox type explicitly if min and max are used. subcomponents = {"entry" : Component("pride.gui.fields.Spinbox_Entry", location="left")} interface = (tuple(), ("minimum", "maximum")) def create_entry(self, location): container = self.create(pride.gui.gui.Container, location=location) entry_type = self.entry_type entry = self.entry = container.create(entry_type, parent_field=self, tip_bar_text=self.tip_bar_text, **self.entry_kwargs) if self.editable: subcontainer = container.create(pride.gui.gui.Container, location="left", w_range=(0, .05)) kwargs = {"target_entry" : entry, "location" : "top"} self.inc_button = subcontainer.create(Increment_Button, **kwargs) assert kwargs == {"target_entry" : entry, "location" : "top"} self.dec_button = subcontainer.create(Decrement_Button, **kwargs) def handle_value_changed(self, old_value, new_value): return super(Spinbox, self).handle_value_changed(int(old_value), int(new_value))
class Callable_Field(Field): defaults = {"orientation" : "side by side", "has_label" : False, "button_text" : '', "args" : tuple()} mutable_defaults = {"kwargs" : dict} subcomponents = {"entry" : Component("pride.gui.fields.Callable_Entry", scale_to_text=True)} interface = (tuple(), ("button_text", "args", "kwargs")) def create_entry(self, location): super(Callable_Field, self).create_entry(location) self.entry.text = self.entry.text # .text may not get set, if so then scale_to_text wont happen def delete(self): del self.kwargs super(Callable_Field, self).delete()
class Media_Entry(Entry): subcomponents = {"player" : Component("pride.gui.fields.Media_Player")} autoreferences = ("player", ) def _get_text(self): return '' def _set_text(self, value): super(Media_Entry, self)._set_text(value) text = property(_get_text, _set_text) def __init__(self, **kwargs): super(Media_Entry, self).__init__(**kwargs) self.create_subcomponents() def create_subcomponents(self): self.player = self.create(self.player_type, **self.player_kwargs)
class Theme_Form(pride.gui.form.Form): subcomponents = {"form" : Component("pride.gui.programs.themecustomizer2.Theme_Form")} def get_parent_theme_editor(self): parent = self.parent while not hasattr(parent, "target_theme"): parent = parent.parent return parent def handle_value_changed(self, field, old, new): self.get_parent_theme_editor().target_theme.update_theme_users() def save_theme(self): self.get_parent_theme_editor().save_theme() def load_theme(self): self.get_parent_theme_editor().load_theme()
class Linked_Form(pride.gui.tabs.Tabbed_Window): subcomponents = {"form" : Component("pride.gui.form2.Remote_Form")} autoreferences = ("form", ) def create_subcomponents(self): _layout = self.layout form_type = self.form_type tabs = [] for page_id, page_layout in _layout[-1].get("links", tuple()): target = lazy_loaded(Linked_Form, layout=page_layout, form_type=form_type) tabs.append(tab_info(target, button_text=page_id, entry_kwargs={"scale_to_text" : False})) self.tab_bar_kwargs["tab_info"] = tabs super(Linked_Form, self).create_subcomponents() if not tabs: self.tab_bar.hide() if _layout[0]: self.form = self.main_window.create(form_type, layout=_layout)
class Media_Player(pride.gui.link.Linked_Form): defaults = {"filename": ''} parser_args = ("filename", ) parser_modifiers = {"filename": {"types": ("positional", )}} subcomponents = {"tab_bar": Component(include_new_tab_button=False)} def create_subcomponents(self): filename = self.filename alias = os.path.split(filename)[-1] manifest, manifest_data = load_resources((alias, filename)) control_page = \ page("controls", layout( row_info(0, field_info("filename", field_type="pride.gui.fields.Media_Field", play_when_opened=True)), manifest=manifest, filename=alias)) resource_id = manifest[alias] store_resource(resource_id, manifest_data[alias]) self.layout = control_page[1] #layout(links=(control_page, )) super(Media_Player, self).create_subcomponents()
class Slider_Field(Field): predefaults = {"_minimum" : 0, "_maximum" : 0} interface = (tuple(), ("minimum", "maximum")) subcomponents = {"entry" : Component("pride.gui.fields.Slider_Entry")} def _get_minimum(self): return self._minimum def _set_minimum(self, value): self._minimum = value if self.minimum == self.maximum: self.hide() else: self.show() minimum = property(_get_minimum, _set_minimum) def _get_maximum(self): return self._maximum def _set_maximum(self, value): self._maximum = value if self.minimum == self.maximum: self.hide() else: self.show() try: self.entry.max_button.entry.texture_invalid = True except AttributeError: pass maximum = property(_get_maximum, _set_maximum) def handle_transition_animation_end(self): super(Slider_Field, self).handle_transition_animation_end() self.update_position_from_value() def update_position_from_value(self): self.entry.continuum.update_position_from_value()
class Scrollable_Window(pride.gui.gui.Window): predefaults = {"_x_scroll_value": 0, "_y_scroll_value": 0} autoreferences = ("main_window", "vertical_slider", "horizontal_slider") subcomponents = { "vertical_slider": Component("pride.gui.fields.Slider_Field", location="right", orientation="stacked", w_range=(0, .025), name="y_scroll_value", minimum=0, maximum=0, has_label=False, entry_kwargs={"orientation": "stacked"}), "horizontal_slider": Component("pride.gui.fields.Slider_Field", h_range=(0, .025), has_label=False, name="x_scroll_value", minimum=0, maximum=0, location="bottom", orientation="side by side"), "main_window": Component("pride.gui.gui.Window", location="main") } interface = (tuple(), ("horizontal_slider_kwargs", "vertical_slider_kwargs")) def _get_y_scroll_value(self): return self._y_scroll_value def _set_y_scroll_value(self, new_value): old_value = self._y_scroll_value self._y_scroll_value = new_value self.handle_y_scroll(old_value, new_value) y_scroll_value = property(_get_y_scroll_value, _set_y_scroll_value) def _get_x_scroll_value(self): return self._x_scroll_value def _set_x_scroll_value(self, new_value): old_value = self._x_scroll_value self._x_scroll_value = new_value self.handle_x_scroll(old_value, new_value) x_scroll_value = property(_get_x_scroll_value, _set_x_scroll_value) def __init__(self, **kwargs): super(Scrollable_Window, self).__init__(**kwargs) self.create_subcomponents() def create_subcomponents(self): self.main_window = self.create(self.main_window_type, **self.main_window_kwargs) kwargs = self.vertical_slider_kwargs location = kwargs["location"] if location is not None: slider_type = self.vertical_slider_type self.vertical_slider = self.create(slider_type, target_object=self, **kwargs) kwargs = self.horizontal_slider_kwargs location = kwargs["location"] if location is not None: slider_type = self.horizontal_slider_type self.horizontal_slider = self.create(slider_type, target_object=self, **kwargs) def handle_x_scroll(self, old_value, new_value): pass def handle_y_scroll(self, old_value, new_value): pass
class Image_Field(Field): subcomponents = {"entry" : Component("pride.gui.fields.Image_Entry")}
class _Endcap(Text_Field): defaults = {"editable" : False, "has_label" : False} subcomponents = {"entry" : Component("pride.gui.fields._Endcap_Entry")}
class Field(pride.gui.gui.Container): defaults = {"name" : '', "orientation" : "stacked", "_value_initialized" : False, "field_type" : None, "editable" : True, "location" : "left", "has_label" : True, "display_name" : ''} subcomponents = {"entry" : Component("pride.gui.fields.Entry"), "label" : Component("pride.gui.gui.Container")} predefaults = {"target_object" : None} autoreferences = ("label", "parent_form", "entry") allowed_values = {"orientation" : ("stacked", "side by side")} interface = (tuple(), ("name", "orientation", "field_type", "editable", "location", "has_label", "display_name", "entry_kwargs")) def _get_value(self): try: return getattr(self.target_object, self.name) except (TypeError, AttributeError) as exception: try: return self.target_object[self.name] except TypeError: raise exception def _set_value(self, value): if self.editable: old_value = self.value if self.handle_value_changed(old_value, value): try: setattr(self.target_object, self.name, value) except (TypeError, AttributeError) as exception: assert hasattr(self, "target_object") try: # duck typing might fail for mapping-like objects that don't restrict attribute assignment the way a dict does self.target_object[self.name] = value except TypeError: if hasattr(self, "target_object") and hasattr(self, "name"): raise exception self.entry.texture_invalid = True # updates text later parent_form = self.parent_form if parent_form is not None: parent_form.handle_value_changed(self, old_value, value) value = property(_get_value, _set_value) def __init__(self, **kwargs): super(Field, self).__init__(**kwargs) self.create_subcomponents() def create_subcomponents(self): label_kwargs = self.label_kwargs orientation = self.orientation if orientation == "stacked": location = "top" label_kwargs.setdefault("scale_to_text", False) label_kwargs.setdefault("h_range", (0, .05)) else: assert orientation == "side by side" location = "left" assert self.label is None self.create_label(location, **label_kwargs) self.create_entry(location) assert hasattr(self, "parent_form") def create_label(self, location, **label_kwargs): label_kwargs.setdefault("tip_bar_text", self.tip_bar_text) label_kwargs.setdefault("location", location) label_kwargs.setdefault("text", self.display_name or self.name) self.label = self.create(self.label_type, **label_kwargs) if not self.has_label: self.label.hide() def create_entry(self, location): kwargs = self.entry_kwargs kwargs.setdefault("location", location) entry_type = self.entry_type self.entry = self.create(entry_type, parent_field=self, **kwargs) def handle_value_changed(self, old_value, new_value): if old_value == new_value: return False self.alert("Value changing from {} to {}".format(old_value, new_value), level=self.verbosity["handle_value_changed"]) return True def delete(self): del self.entry_kwargs del self.target_object super(Field, self).delete()
class Toggle(Field): subcomponents = {"entry" : Component("pride.gui.fields.Toggle_Entry")}
class Form(Scrollable_Window): defaults = {"target_object": None, "max_rows": 4} subcomponents = { "row": Component("pride.gui.form.Row", location="top", h_range=(0, 1.0)), "horizontal_slider": Component(location=None) } mutable_defaults = { "rows": dict, "visible_rows": list, "layout": layout, "manifest": dict } interface = (tuple(), ("max_rows", "manifest")) hotkeys = {("\t", None): "handle_tab"} autoreferences = ("selected_entry", ) def _get_fields(self): rows = self.rows return iterrows(rows) fields = property(_get_fields) def create_subcomponents(self): if self.target_object is None: self.target_object = self for key, value in self.layout[-1].iteritems(): setattr(self, key, value) super(Form, self).create_subcomponents() _row_info = self.layout[0] max_rows = self.max_rows amount = max_rows # make a fixed number of visible rows # set the rows to represent the data of a given range of row_infos window = self.main_window self.visible_rows = [ window.create(Visible_Row) for count in range(amount) ] visible_rows = self.visible_rows for row_no in range(amount): try: self.create_row(_row_info[row_no]) except KeyError: if row_no in _row_info: raise break self.load_rows() if self.selected_entry is None and self.rows: try: self.selected_entry = self.rows[0].fields[0].entry except IndexError: pass self.synchronize_scroll_bars() def unload_rows(self, new_row_indices): for row in self.visible_rows: row.unload_row(new_row_indices) def load_rows(self): start = self.y_scroll_value rows = self.rows visible_rows = self.visible_rows row_infos = self.layout[0] max_rows = self.max_rows amount = min(max_rows, len(row_infos)) # must unload rows before calling load_row self.unload_rows(range(start, start + amount)) for offset in range(amount): row_no = start + offset try: row = self.load_row(row_no) except KeyError: break visible_row = visible_rows[offset] row.show() visible_row.load_row(row) visible_row.show() excess = max_rows - amount if excess: for offset in range(excess): row_no = start + amount + offset visible_rows[row_no].hide() def load_row(self, row_no): try: return self.rows[row_no] except KeyError: self.create_row(self.layout[0][row_no]) return self.rows[row_no] def create_row(self, _row_info): kwargs = copy.deepcopy(self.row_kwargs) row_type = self.row_type row_no, row_kwargs = _row_info[0], _row_info[-1] deep_update(kwargs, row_kwargs) kwargs.setdefault("row_number", row_no) window = self.main_window row = window.create(row_type, **kwargs) self.rows[row_no] = row _field_infos = _row_info[1:-1][0] for _field_info in _field_infos: self.create_field(_field_info, row) row.hide() window.remove(row) return row def create_field(self, _field_info, row): field_name, field_kwargs = _field_info target_object = field_kwargs.get("target_object", self.target_object) field_type = self.determine_field_type(target_object, field_name, field_kwargs) field_kwargs.setdefault("target_object", self.target_object) field_kwargs["name"] = field_name field = row.create( field_type, parent_form=self, field_no=sum(len(_row.fields) for _row in self.rows.values()), **field_kwargs) del field_kwargs["target_object"] row.fields.append(field) return field def determine_field_type(self, target_object, name, field_kwargs): field_type = field_kwargs.get("field_type", None) if field_type is None: try: value = getattr(target_object, name) except AttributeError as exception: try: value = target_object[name] except TypeError: raise exception #if hasattr(value, "field_type"): # field_type = value.field_type # check for dropdowns before checking value if "values" in field_kwargs: field_type = "pride.gui.fields.Dropdown_Field" # check for Slider before checking for int/float elif "minimum" in field_kwargs and "maximum" in field_kwargs: field_type = "pride.gui.fields.Slider_Field" # must compare for bool before comparing for int; bool is a subclass of int elif isinstance(value, bool): field_type = "pride.gui.fields.Toggle" elif isinstance(value, int) or isinstance(value, float): field_type = "pride.gui.fields.Spinbox" elif isinstance(value, str): field_type = "pride.gui.fields.Text_Field" elif hasattr(value, "__call__"): field_type = "pride.gui.fields.Callable_Field" #elif isinstance(value, tuple) or isinstance(value, list): # field_type = "pride.gui.widgets.formext.Tabbed_Form" if field_type is None: message = "Unable to determine field_type for {}" raise ValueError( message.format((target_object, name, value, field_kwargs))) return field_type def handle_value_changed(self, field, old, new): pass def synchronize_scroll_bars(self): slider = self.vertical_slider if slider is not None: slider.maximum = max(0, len(self.layout[0]) - self.max_rows) slider.update_position_from_value() slider.entry.texture_invalid = True self.pack() def handle_y_scroll(self, old, new): super(Form, self).handle_y_scroll(old, new) # y_scroll points to the current top row assert self.y_scroll_value == new, (self.y_scroll_value, new) self.load_rows() ## check if the selected entry is still visible, unselect it if not #row_no = self.selected_entry.parent_field.parent.row_number #amount = min(self.max_rows, len(self.layout[0])) #if row_no < new and row_no >= new + amount: # self.selected_entry.deselect() # new_entry = self.visible_rows[0]._current_row.fields[0].entry # self.handle_entry_selected(new_entry, scroll=False) def handle_tab(self): # go to the next field in the row # if there are no more fields, go to the next row # if there are no more rows, go to the initial row entry = self.selected_entry field = entry.parent_field row = field.parent fields = row.fields field_index = fields.index(field) next_field_index = field_index + 1 try: self.handle_entry_selected(fields[next_field_index].entry, scroll=True) except IndexError: rows = self.rows row_info = self.layout[0] row_count = len(row_info) row_index = row.row_number next_row_index = (row_index + 1) % row_count if next_row_index in row_info: if next_row_index not in rows: self.create_row(row_info[row_index + 1]) next_row = rows[next_row_index] else: next_row = rows[0] if not next_row.fields: has_fields = lambda row: row[1] has_any_fields = [ row for row in row_info.values() if has_fields(row) ] if not has_any_fields: return for next_row_index in range(next_row_index, row_count): if has_fields(row_info[next_row_index]): break else: for next_row_index in range(next_row_index + 1): if has_fields(row_info[next_row_index]): break if next_row_index not in rows: self.create_row(row_info[next_row_index]) next_row = rows[next_row_index] self.handle_entry_selected(next_row.fields[0].entry, scroll=True) def handle_entry_selected(self, entry, _needs_select=True, scroll=False): if self.selected_entry is not None: old_entry = self.selected_entry old_entry.deselect(entry) self.selected_entry = entry if _needs_select: self.sdl_window.user_input.select_active_item(entry) if scroll: row_no = entry.parent_field.parent.row_number max_index = len(self.layout[0]) - self.max_rows row_no = min(row_no, max_index) y_value = self.y_scroll_value if row_no < y_value: self.y_scroll_value = row_no self.handle_y_scroll(y_value, row_no) elif row_no > y_value + self.max_rows: self.y_scroll_value = row_no self.handle_y_scroll(y_value, self.y_scroll_value) elif row_no == max_index: self.y_scroll_value = row_no self.handle_y_scroll(y_value, row_no) self.synchronize_scroll_bars() def synchronize_fields(self): for field in self.fields: field.entry.texture_invalid = True def delete(self): self.rows.clear() del self.target_object del self.visible_rows super(Form, self).delete()
class Dropdown_Field(Callable_Field): defaults = {"has_label" : True, "values" : tuple(), "orientation" : "side by side"} interface = (tuple(), ("values", )) subcomponents = {"entry" : Component("pride.gui.fields.Dropdown_Entry")}
class Text_Field(Field): subcomponents = {"entry" : Component("pride.gui.fields.Text_Entry")}
class Media_Field(Field): defaults = {"has_label" : False, "h_range" : (0, .25), "play_when_opened" : False,} subcomponents = {"entry" : Component("pride.gui.fields.Media_Entry")} interface = (tuple(), ("play_when_opened", ))