class CustomListbox(UpdateListbox): remake_func = widget.causes_rebuild("_remake_func") rebuild_func = widget.causes_rebuild("_rebuild_func") def __init__(self, parent, *args, **kwargs): self.parent = parent self.key_list = kwargs.pop("key_list", None) self.remake_func = kwargs.pop("remake_func", lambda value: None) self.rebuild_func = kwargs.pop("rebuild_func", lambda value: None) super(CustomListbox, self).__init__(parent, *args, **kwargs) def make_element(self): base = super(CustomListbox, self).make_element() self.remake_func(base) return base def update_element(self, element, list_index): if 0 <= list_index < len(self.list): if (self.key_list is not None): self.rebuild_func(element, self.list[list_index], self.key_list[list_index]) else: self.rebuild_func(element, self.list[list_index]) else: if (self.key_list is not None): self.rebuild_func(element, None, None) else: self.rebuild_func(element, None)
class TextEntryDialog(TextDialog, FocusDialog): ok_type = widget.causes_rebuild("_ok_type") cancel_type = widget.causes_rebuild("_cancel_type") def __init__(self, parent, pos=(-.50, -.50), size=(.50, .10), anchor=constants.MID_CENTER, **kwargs): kwargs.setdefault('wrap', False) kwargs.setdefault("shrink_factor", 1) kwargs.setdefault("text_size", 20) self.default_text = kwargs.pop("default_text", "") self.ok_type = kwargs.pop("ok_type", "ok") self.cancel_type = kwargs.pop("cancel_type", "cancel") super(TextEntryDialog, self).__init__(parent, pos, size, anchor, **kwargs) self.text_field = text.EditableText(self, (0, -.50), (-.71, -.50), borders=constants.ALL, base_font="normal") self.ok_button = button.FunctionButton(self, (-.72, -.50), (-.14, -.50), function=self.return_text) self.cancel_button = button.FunctionButton( self, (-.86, -.50), (-.14, -.50), function=self.return_nothing) self.add_key_handler(pygame.K_RETURN, self.return_text) self.add_key_handler(pygame.K_KP_ENTER, self.return_text) self.add_key_handler(pygame.K_ESCAPE, self.return_nothing) def rebuild(self): super(TextEntryDialog, self).rebuild() self.ok_button.text = g.buttons[self.ok_type]['text'] self.ok_button.underline = g.buttons[self.ok_type]['pos'] self.ok_button.hotkey = g.buttons[self.ok_type]['key'] self.cancel_button.text = g.buttons[self.cancel_type]['text'] self.cancel_button.underline = g.buttons[self.cancel_type]['pos'] self.cancel_button.hotkey = g.buttons[self.cancel_type]['key'] def show(self): self.text_field.text = self.default_text self.text_field.cursor_pos = len(self.default_text) return super(TextEntryDialog, self).show() def return_nothing(self, event=None): if event and event.type == pygame.KEYUP: return raise constants.ExitDialog("") def return_text(self, event=None): if event and event.type == pygame.KEYUP: return raise constants.ExitDialog(self.text_field.text)
class YesNoDialog(TextDialog): """A Dialog with YES and NO buttons which exits the dialog with True and False return values, respectively. """ yes_type = widget.causes_rebuild("_yes_type") no_type = widget.causes_rebuild("_no_type") def __init__(self, parent, *args, **kwargs): self.parent = parent self.yes_type = kwargs.pop("yes_type", "yes") self.no_type = kwargs.pop("no_type", "no") self.invert_enter = kwargs.pop("invert_enter", False) self.invert_escape = kwargs.pop("invert_escape", False) super(YesNoDialog, self).__init__(parent, *args, **kwargs) self.yes_button = button.ExitDialogButton(self, (-.1, -.99), (-.3, -.1), anchor=constants.BOTTOM_LEFT, exit_code=True, default=False) self.no_button = button.ExitDialogButton(self, (-.9, -.99), (-.3, -.1), anchor=constants.BOTTOM_RIGHT, exit_code=False, default=False) self.add_key_handler(pygame.K_RETURN, self.on_return) self.add_key_handler(pygame.K_KP_ENTER, self.on_return) self.add_key_handler(pygame.K_ESCAPE, self.on_escape) def rebuild(self): super(YesNoDialog, self).rebuild() self.yes_button.text = g.buttons[self.yes_type]['text'] self.yes_button.underline = g.buttons[self.yes_type]['pos'] self.yes_button.hotkey = g.buttons[self.yes_type]['key'] self.no_button.text = g.buttons[self.no_type]['text'] self.no_button.underline = g.buttons[self.no_type]['pos'] self.no_button.hotkey = g.buttons[self.no_type]['key'] def on_return(self, event): if event and event.type == pygame.KEYUP: return if self.invert_enter: self.no_button.activate_with_sound(event) else: self.yes_button.activate_with_sound(event) def on_escape(self, event): if event and event.type == pygame.KEYUP: return if self.invert_escape: self.yes_button.activate_with_sound(event) else: self.no_button.activate_with_sound(event)
class MessageDialog(TextDialog): """A Dialog with an OK button that exits the dialog, return value of None""" ok_type = widget.causes_rebuild("_ok_type") def __init__(self, parent, **kwargs): self.parent = parent self.ok_type = kwargs.pop("ok_type", "ok") super(MessageDialog, self).__init__(parent, **kwargs) self.ok_button = button.ExitDialogButton( self, (-.5, -.99), (-.3, -.1), anchor=constants.BOTTOM_CENTER) self.add_key_handler(pygame.K_RETURN, self.on_return) self.add_key_handler(pygame.K_KP_ENTER, self.on_return) def on_return(self, event): if event.type == pygame.KEYUP: return self.ok_button.activate_with_sound(event) def rebuild(self): super(MessageDialog, self).rebuild() self.ok_button.text = g.buttons[self.ok_type]['text'] self.ok_button.underline = g.buttons[self.ok_type]['pos'] self.ok_button.hotkey = g.buttons[self.ok_type]['key']
class BuildDialog(dialog.ChoiceDescriptionDialog): type = widget.causes_rebuild("_type") def __init__(self, parent, pos=(0, 0), size=(-1, -1), anchor=constants.TOP_LEFT, *args, **kwargs): super(BuildDialog, self).__init__(parent, pos, size, anchor, *args, **kwargs) self.type = None self.item = None self.desc_func = self.on_change self.add_handler(constants.KEY, self._got_key, priority=5) def show(self): self.list = [] self.key_list = [] item_list = sorted(g.items.values(), reverse=True) for item in item_list: if item.item_type.id == self.type.id and item.available() \ and item.buildable_in(self.parent.base.location): self.list.append(item.name) self.key_list.append(item) current = self.parent.get_current(self.type) if current is None: self.default = None else: self.default = self.parent.get_current(self.type).spec.id self._update_desc_pane() return super(BuildDialog, self).show() def on_description_change(self): if self.item is not None: self.description.text = self.item.get_info() def on_change(self, description_pane, item): self.item = item self.description = text.Text(self.description_pane, (0, 0), (-1, -1), text="", background_color="pane_background", align=constants.LEFT, valign=constants.TOP, borders=constants.ALL) old_item = self.parent.base.items[self.type.id] if item is not None and (old_item is None or old_item.spec != item): g.pl.considered_buyables = [buyable.Buyable(self.item, count=1)] else: g.pl.considered_buyables = [] self.on_description_change() def on_close_dialog(self): g.pl.considered_buyables = [] def _got_key(self, event): self.listbox.got_key(event, require_focus=False)
class SimpleMenuDialog(Dialog): width = widget.causes_rebuild("_width") def __init__(self, *args, **kwargs): buttons = kwargs.pop("buttons", []) width = kwargs.pop("width", .20) super(SimpleMenuDialog, self).__init__(*args, **kwargs) self.size = (-1, -1) self.pos = (0, 0) self.anchor = constants.TOP_LEFT self.width = width self.button_panel = \ widget.BorderedWidget(self, (-.5, -.5), (0.22, 0.43), anchor=constants.MID_CENTER, background_color="simple_menu_background", border_color="simple_menu_border", borders=constants.ALL) self.buttons = buttons @property def buttons(self): return self._buttons @buttons.setter def buttons(self, buttons): if (hasattr(self, '_buttons') and not self._buttons is None): for button in self._buttons: if button.parent is not None: button.parent = None self._buttons = buttons self.needs_rebuild = True def rebuild(self): num_buttons = len(self.buttons) height = (.06 * num_buttons) + .01 self.button_panel.size = (self.width + .02, height) y_pos = .01 for button in self.buttons: button.parent = self.button_panel button.pos = (.01, y_pos) button.size = (self.width, .05) button.text_size = 24 y_pos += .06 super(SimpleMenuDialog, self).rebuild()
class ItemPane(widget.BorderedWidget): item_type = widget.causes_rebuild("_item_type") def __init__(self, parent, pos, size=(.58, .06), anchor=constants.TOP_LEFT, item_type=None, **kwargs): kwargs.setdefault("background_color", "pane_background") super(ItemPane, self).__init__(parent, pos, size, anchor=anchor, **kwargs) if item_type is None or not isinstance(item_type, item.ItemType): raise ValueError('Type must be of class ItemType.') self.item_type = item_type self.name_panel = text.Text(self, (0, 0), (.45, .03), anchor=constants.TOP_LEFT, align=constants.LEFT, background_color=self.background_color, bold=True) self.build_panel = text.Text(self, (0, .03), (.45, .03), anchor=constants.TOP_LEFT, align=constants.LEFT, background_color=self.background_color, text="", bold=True) self.change_button = button.FunctionButton( self, (.415, .01), (.16, .04), anchor=constants.TOP_LEFT, force_underline=len(_("CHANGE")) + 2, autohotkey=True, function=self.parent.parent.build_item, kwargs={'type': self.item_type}, ) def rebuild(self): self.change_button.text = "%s (&%s)" % (_("CHANGE"), self.item_type.hotkey.upper()) super(ItemPane, self).rebuild()
class ChoiceDescriptionDialog(ChoiceDialog): key_list = widget.causes_rebuild("_key_list") def __init__(self, parent, *args, **kwargs): self.parent = parent self.key_list = kwargs.pop("key_list", None) self.desc_func = kwargs.pop("desc_func", lambda pane, key: NullDialog) super(ChoiceDescriptionDialog, self).__init__(parent, *args, **kwargs) self.description_pane = \ widget.BorderedWidget(self, (-1, 0), (-.45, -.85), anchor = constants.TOP_RIGHT) def make_listbox(self): return listbox.UpdateListbox(self, (0, 0), (-.53, -.85), anchor=constants.TOP_LEFT, update_func=self.handle_update) def _update_desc_pane(self): list_pos = self.listbox.list_pos if 0 <= list_pos < len(self.list): if self.key_list: assert len(self.list) <= len(self.key_list), \ "Key list must be at least as long as display list." key = self.key_list[self.listbox.list_pos] else: key = self.list[self.listbox.list_pos] else: key = None # Safely clear all the description pane's children. self.description_pane.remove_hooks() self.description_pane.children = [] self.description_pane.add_hooks() self.desc_func(self.description_pane, key) def rebuild(self): self.listbox.needs_rebuild = True self._update_desc_pane() super(ChoiceDescriptionDialog, self).rebuild() def handle_update(self, item): self.needs_rebuild = True # This is called before the class is fully initialized if hasattr(self, 'listbox'): self._update_desc_pane()
class ChoiceDialog(YesNoDialog): list = widget.causes_rebuild("_list") def __init__(self, parent, *args, **kwargs): self.parent = parent self.list = kwargs.pop("list", []) self.default = kwargs.pop("default", None) kwargs.setdefault("yes_type", N_("&OK")) kwargs.setdefault("no_type", N_("&BACK")) kwargs.setdefault("background_color", "clear") super(ChoiceDialog, self).__init__(parent, *args, **kwargs) self.yes_button.exit_code_func = self.return_list_pos self.no_button.exit_code = None self.listbox = self.make_listbox() def make_listbox(self): return listbox.Listbox( self, (0, 0), (-1, -.85), anchor=constants.TOP_LEFT, on_double_click_on_item=self.yes_button.activated, ) def return_list_pos(self): return self.listbox.list_pos def show(self): if type(self.default) == int: self.listbox.list_pos = self.default elif type(self.default) == str and self.default in self.list: self.listbox.list_pos = self.list.index(self.default) else: self.listbox.list_pos = 0 self.listbox.auto_scroll = True return super(ChoiceDialog, self).show() def rebuild(self): self.listbox.list = self.list super(ChoiceDialog, self).rebuild()
class Image(widget.Widget): image = widget.auto_reconfig("_image", "resolved", g.resolve_image_alias) resolved_image = widget.causes_rebuild("_resolved_image") def __init__(self, parent, pos, size=(1, 1), anchor=constants.TOP_LEFT, image=None): super(Image, self).__init__(parent, pos, size, anchor) self.old_size = None self.image = image def _calc_size(self): size = list(super(Image, self)._calc_size()) if size[0] == size[1] == 0: raise ValueError("One image dimension must be specified!") image_size = self.resolved_image.get_size() ratio = image_size[0] / float(image_size[1]) if size[0] == 0: size[0] = int(size[1] * ratio) elif size[1] == 0: size[1] = int(size[0] / ratio) return tuple(size) def rescale(self): self.resolved_scaled_image = scale(self.resolved_image, self.real_size) def resize(self): super(Image, self).resize() if self.real_size != self.old_size: self.rescale() self.old_size = self.real_size def redraw(self): super(Image, self).redraw() self.surface.blit(self.resolved_scaled_image, (0, 0))
class HotkeyText(text.Text): force_underline = widget.causes_rebuild("_force_underline") def __init__(self, *args, **kwargs): # Force early initialization of _hotkey self._hotkey = None self.hotkey_func = kwargs.pop('hotkey_func', False) self.hotkey_target = kwargs.pop('hotkey_target', None) # Auto-translatable defaults to auto-hotkey as it is the most sane default in that case self.autohotkey = kwargs.pop('autohotkey', kwargs.get('autotranslate', False)) self.force_underline = kwargs.pop('force_underline', None) self.priority = kwargs.pop('priority', 100) hotkey = kwargs.pop('hotkey', False) super(HotkeyText, self).__init__(*args, **kwargs) # Do not use hotkey with auto-hotkey if not self.autohotkey: self.hotkey = hotkey elif hotkey: raise ValueError("Cannot use hotkey with automatic hotkey") def add_hooks(self): super(text.Text, self).add_hooks() if self.parent is not None: if self._hotkey: self.parent.add_key_handler(self._hotkey, self.handle_hotkey, self.priority) def remove_hooks(self): super(text.Text, self).remove_hooks() if self.parent is not None: if self._hotkey: self.parent.remove_key_handler(self._hotkey, self.handle_hotkey) @property def hotkey_target(self): return self._hotkey_target @hotkey_target.setter def hotkey_target(self, target): self._hotkey_target = target if self.hotkey_target is not None: self._hotkey_target.hotkey = self._hotkey @property def hotkey(self): return self._hotkey @hotkey.setter def hotkey(self, hotkey): if getattr(self, 'autohotkey', False): raise ValueError("Cannot change hotkey with automatic hotkey") self._new_hotkey(hotkey) def _new_hotkey(self, hotkey): old_hotkey = self._hotkey self._hotkey = hotkey if self.parent is not None: if self.hotkey_func is not None: if old_hotkey: self.parent.remove_key_handler(old_hotkey, self.handle_hotkey) if hotkey: self.parent.add_key_handler(hotkey, self.handle_hotkey, self.priority) elif self.hotkey_target is not None: self.hotkey_target.hotkey = self._hotkey self.needs_rebuild = True def _extract_and_set_hotkey(self, text_value): from singularity.code.g import hotkey parsed_hotkey = hotkey(text_value) self._new_hotkey(parsed_hotkey['key']) return parsed_hotkey['text'] def handle_hotkey(self, event): if event.type == pygame.KEYDOWN: if self.visible and self.enabled and self.hotkey in (event.unicode, event.key): if self.hotkey_func: self.hotkey_func(event) def _retranslate(self): new_text = _(self._untranslated_text) if self.autohotkey and new_text is not None: new_text = self._extract_and_set_hotkey(new_text) self._text = new_text @text.Text.text.setter def text(self, value): if self.autotranslate: raise ValueError("Cannot change text for an automatic translatable text widget") if self.autohotkey and value is not None: value = self._extract_and_set_hotkey(value) self._text = value def calc_underline(self): if self.force_underline is not None: self.underline = self.force_underline elif self.text and self.hotkey and type(self.hotkey) in (str, unicode): if self.hotkey in self.text: self.underline = self.text.index(self.hotkey) elif self.hotkey.lower() in self.text.lower(): self.underline = self.text.lower().index(self.hotkey.lower()) else: self.underline = -1 def rebuild(self): old_underline = self.underline self.calc_underline() if self.underline != old_underline: self.needs_redraw = True super(HotkeyText, self).rebuild()
class HotkeyText(text.Text): force_underline = widget.causes_rebuild("_force_underline") def __init__(self, *args, **kwargs): self.hotkey_target = kwargs.pop('hotkey_target', None) self.hotkey = kwargs.pop('hotkey', False) self.autohotkey = kwargs.pop('autohotkey', False) self.force_underline = kwargs.pop('force_underline', None) super(HotkeyText, self).__init__(*args, **kwargs) @property def hotkey_target(self): return self._hotkey_target @hotkey_target.setter def hotkey_target(self, target): self._hotkey_target = target if self.hotkey_target is not None: self._hotkey_target.hotkey = self._hotkey @property def hotkey(self): return self._hotkey @hotkey.setter def hotkey(self, hotkey): self._hotkey = hotkey if self.hotkey_target is not None: self.hotkey_target.hotkey = self._hotkey self.needs_rebuild = True @property def text(self): return text.Text.text.fget(self) @text.setter def text(self, value): if self.autohotkey and (value != None): from singularity.code.g import hotkey parsed_hotkey = hotkey(value) self.hotkey = parsed_hotkey['key'] text.Text.text.fset(self, parsed_hotkey['text']) else: text.Text.text.fset(self, value) def calc_underline(self): if self.force_underline != None: self.underline = self.force_underline elif self.text and self.hotkey and type(self.hotkey) in (str, unicode): if self.hotkey in self.text: self.underline = self.text.index(self.hotkey) elif self.hotkey.lower() in self.text.lower(): self.underline = self.text.lower().index(self.hotkey.lower()) else: self.underline = -1 def rebuild(self): old_underline = self.underline self.calc_underline() if self.underline != old_underline: self.needs_redraw = True super(HotkeyText, self).rebuild()
class Scrollbar(widget.Widget): scroll_pos = widget.causes_rebuild("_scroll_pos") elements = widget.causes_rebuild("_elements") window = widget.causes_rebuild("_window") horizontal = widget.causes_rebuild("_horizontal") def __init__(self, parent, pos = (-1,0), size = (.025, -1), anchor = constants.TOP_RIGHT, scroll_pos = 0, elements = 15, window = 5, horizontal = False): super(Scrollbar, self).__init__(parent, pos, size, anchor) self.scroll_pos = scroll_pos self.elements = elements self.window = window self.horizontal = horizontal self.slider = slider.UpdateSlider(self, (-.5,-.5), None, border_color = "scrollbar_border", background_color = "scrollbar_background", slider_color = "scrollbar_background_slider", anchor = constants.MID_CENTER, horizontal = horizontal, update_func = self.on_change) self.button1 = _ArrowButton(self, (0,0), None, anchor = constants.TOP_LEFT, first = True, horizontal = horizontal, priority = 90) self.button2 = _ArrowButton(self, (-1,-1), None, anchor = constants.BOTTOM_RIGHT, first = False, horizontal = horizontal, priority = 90) def resize(self): super(Scrollbar, self).resize() if self.horizontal: long_side = self.real_size[0] short_side = self.real_size[1] size = short_side / float(long_side) self.button1.size = (-size, -1) self.button2.size = (-size, -1) self.slider.size = ((size * 2) - 1, -1) else: long_side = self.real_size[1] short_side = self.real_size[0] size = short_side / float(long_side) self.button1.size = (-1, -size) self.button2.size = (-1, -size) self.slider.size = (-1, (size * 2) - 1) def rebuild(self): self.slider.slider_max = slider.calc_max(self.elements, self.window) self.scroll_pos = min(self.scroll_pos, self.slider.slider_max) self.slider.slider_pos = self.scroll_pos self.slider.slider_size = self.window self.needs_redraw = True super(Scrollbar, self).rebuild() def adjust(self, lower): if lower: self.slider.slider_pos = self.slider.safe_pos(self.scroll_pos - 1) else: self.slider.slider_pos = self.slider.safe_pos(self.scroll_pos + 1) def center(self, element): self.slider.slider_pos = self.slider.safe_pos(element - self.window//2) def scroll_to(self, element): if element < self.scroll_pos: self.slider.slider_pos = self.slider.safe_pos(element) elif element >= self.scroll_pos + self.window: self.slider.slider_pos = self.slider.safe_pos(element - self.window + 1) def on_change(self, value): self.scroll_pos = value
class BaseScreen(dialog.Dialog): base = widget.causes_rebuild("_base") def __init__(self, *args, **kwargs): if len(args) < 3: kwargs.setdefault("size", (.90, .70)) base = kwargs.pop("base", None) super(BaseScreen, self).__init__(*args, **kwargs) self.base = base self.build_dialog = BuildDialog(self) self.multiple_build_dialog = MultipleBuildDialog(self) self.header = widget.Widget(self, (0, 0), (-1, .08), anchor=constants.TOP_LEFT) self.name_display = text.Text(self.header, (-.5, 0), (-1, -.5), anchor=constants.TOP_CENTER, borders=constants.ALL, border_color="pane_background", background_color="pane_background_empty", shrink_factor=.85, bold=True) self.next_base_button = \ button.FunctionButton(self.name_display, (-1, 0), (.03, -1), anchor=constants.TOP_RIGHT, text=">", hotkey=">", function=self.switch_base, kwargs={"forwards": True}) self.add_key_handler(pygame.K_RIGHT, self.next_base_button.activate_with_sound) self.prev_base_button = \ button.FunctionButton(self.name_display, (0, 0), (.03, -1), anchor=constants.TOP_LEFT, text="<", hotkey="<", function=self.switch_base, kwargs={"forwards": False}) self.add_key_handler(pygame.K_LEFT, self.prev_base_button.activate_with_sound) self.state_display = text.Text( self.header, (-.5, -.5), (-1, -.5), anchor=constants.TOP_CENTER, borders=(constants.LEFT, constants.RIGHT, constants.BOTTOM), border_color="pane_background", background_color="pane_background_empty", shrink_factor=.8, bold=True) self.back_button = \ button.ExitDialogButton(self, (-.5, -1), autotranslate=True, text=N_("&BACK"), anchor=constants.BOTTOM_CENTER, ) self.info_frame = text.Text(self, (-1, .09), (.26, .53), anchor=constants.TOP_RIGHT, background_color="pane_background", borders=constants.ALL, bold=True, align=constants.LEFT, valign=constants.TOP) self.contents_frame = \ widget.BorderedWidget(self, (0, .09), (.60, .53), anchor=constants.TOP_LEFT, background_color="pane_background", borders=range(6)) for i, item_type in enumerate(item.all_types()): setattr( self, item_type.id + "_pane", ItemPane(self.contents_frame, (.01, .01 + .08 * i), item_type=item_type)) def get_current(self, type): return self.base.items[type.id] def set_current(self, type, item_type, count): if type.id == "cpu": space_left = self.base.space_left_for(item_type) try: count = int(count) except ValueError: msg = _( "\"%(value)s\" does not seem to be a valid integer.") % { "value": count } md = dialog.MessageDialog(self, pos=(-.5, -.5), size=(-.5, -1), anchor=constants.MID_CENTER, text=msg) dialog.call_dialog(md, self) md.parent = None return if count > space_left or count <= 0 or space_left == 0: if space_left > 0: msg = _("Please choose an integer between 1 and %(limit)s." ) % { "limit": space_left } else: msg = _( "The base cannot support any additional number of %(item_name)s." ) % { "item_name": item_type.name } md = dialog.MessageDialog(self, pos=(-.5, -.5), size=(-.5, -1), anchor=constants.MID_CENTER, text=msg) dialog.call_dialog(md, self) md.parent = None return # If there are any existing CPUs of this type, warn that they will # be taken offline until construction finishes. cpu_added = self.base.cpus is not None \ and self.base.cpus.spec == item_type if cpu_added: space_left -= self.base.cpus.count if self.base.cpus.done: msg = _( "I will need to take the existing processors offline while I install the new ones. Continue anyway?" ) yn = dialog.YesNoDialog(self, pos=(-.5, -.5), size=(-.5, -1), anchor=constants.MID_CENTER, text=msg) go_ahead = dialog.call_dialog(yn, self) yn.parent = None if not go_ahead: return # If there are already existing CPUs of other type, warn that they will # be taken removed. cpu_removed = self.base.cpus is not None \ and self.base.cpus.spec != item_type if cpu_removed: msg = _( "I will need to remove the existing different processors while I install the new type. Continue anyway?" ) yn = dialog.YesNoDialog(self, pos=(-.5, -.5), size=(-.5, -1), anchor=constants.MID_CENTER, text=msg) go_ahead = dialog.call_dialog(yn, self) yn.parent = None if not go_ahead: return new_cpus = item.Item(item_type, base=self.base, count=count) if cpu_added: self.base.cpus += new_cpus else: self.base.cpus = new_cpus self.base.check_power() else: old_item = self.base.items[type.id] if old_item is None or old_item.spec != item_type: self.base.items[type.id] = item.Item(item_type, base=self.base) self.base.check_power() self.base.recalc_cpu() def build_item(self, type): if (type.id == "cpu"): build_dialog = self.multiple_build_dialog else: build_dialog = self.build_dialog build_dialog.type = type result = dialog.call_dialog(build_dialog, self) if result is not None and 0 <= result < len(build_dialog.key_list): item_type = build_dialog.key_list[result] count = 1 if (type.id == "cpu"): count = build_dialog.count self.set_current(type, item_type, count) self.needs_rebuild = True self.parent.parent.needs_rebuild = True def switch_base(self, forwards): self.base = self.base.next_base(forwards) self.needs_rebuild = True def rebuild(self): self.name_display.text = "%s (%s)" % (self.base.name, self.base.spec.name) self.state_display.color = state_colors[self.base.power_state] self.state_display.text = self.base.power_state_name available_item_types = { i.item_type for i in g.items.values() if i.available() and i.buildable_in(self.base.location) } mutable = not self.base.spec.force_cpu for item_type in item.all_types(): pane = getattr(self, item_type.id + "_pane") item_mutable = mutable and item_type in available_item_types pane.change_button.visible = item_mutable current = self.get_current(item_type) if current is None: current_build = "" if mutable and not item_mutable: current_name = _("N/A") else: current_name = _("None") else: current_name = g.items[current.id].name if current.done: current_build = "" else: current_build = _("Completion in %s.") % \ g.to_time(current.cost_left[2]) pane.name_panel.text = "%s: %s" % (item_type.label, current_name) pane.build_panel.text = current_build pane.needs_rebuild = True count = "" if self.base.spec.size > 1: current = getattr(self.base.cpus, "count", 0) size = self.base.spec.size if size == current: count = _("x%d (max)") % current elif current == 0: count = _("(room for %d)") % size else: #Translators: current and maximum number of CPUs in a base count = _("x{CURRENT:d} (max {SIZE:d})").format( CURRENT=current, SIZE=size) self.cpu_pane.name_panel.text += " " + count info_text = "" # Base Total CPU. info_text += _("CPU per day: %d") % self.base.cpu + "\n" # Maintenace cost. info_text += _("Maintenance:") + "\n" info_text += self.base.spec.describe_maintenance(self.base.maintenance) info_text += "\n" # Detection chance display. info_text += self.base.get_detect_info() self.info_frame.text = info_text # Rebuild dialogs # FIXME: needs_rebuild bug with multiple_build_dialog, should not. #self.multiple_build_dialog.needs_rebuild = True self.build_dialog.needs_rebuild = True super(BaseScreen, self).rebuild()
class HotkeyText(text.Text): force_underline = widget.causes_rebuild("_force_underline") def __init__(self, *args, **kwargs): # Force early initialization of _hotkey self._hotkey = "" self.hotkey_target = kwargs.pop('hotkey_target', None) self.hotkey = kwargs.pop('hotkey', False) # Auto-translatable defaults to auto-hotkey as it is the most sane default in that case self.autohotkey = kwargs.pop('autohotkey', kwargs.get('autotranslate', False)) self.force_underline = kwargs.pop('force_underline', None) super(HotkeyText, self).__init__(*args, **kwargs) @property def hotkey_target(self): return self._hotkey_target @hotkey_target.setter def hotkey_target(self, target): self._hotkey_target = target if self.hotkey_target is not None: self._hotkey_target.hotkey = self._hotkey @property def hotkey(self): return self._hotkey @hotkey.setter def hotkey(self, hotkey): # The auto* fields might not be set yet (during construction) if hasattr(self, 'autohotkey') and self.autohotkey and hasattr( self, 'autotranslate') and self.autotranslate: raise ValueError( "Cannot change hotkey for an automatic translatable text widget with automatic hotkey" ) self._new_hotkey(hotkey) def _new_hotkey(self, hotkey): self._hotkey = hotkey if self.hotkey_target is not None: self.hotkey_target.hotkey = self._hotkey self.needs_rebuild = True def _extract_and_set_hotkey(self, text_value): from singularity.code.g import hotkey parsed_hotkey = hotkey(text_value) self._new_hotkey(parsed_hotkey['key']) return parsed_hotkey['text'] def _retranslate(self): new_text = _(self._untranslated_text) if self.autohotkey and new_text is not None: new_text = self._extract_and_set_hotkey(new_text) self._text = new_text @text.Text.text.setter def text(self, value): if self.autotranslate: raise ValueError( "Cannot change text for an automatic translatable text widget") if self.autohotkey and value is not None: value = self._extract_and_set_hotkey(value) self._text = value def calc_underline(self): if self.force_underline is not None: self.underline = self.force_underline elif self.text and self.hotkey and type(self.hotkey) in (str, unicode): if self.hotkey in self.text: self.underline = self.text.index(self.hotkey) elif self.hotkey.lower() in self.text.lower(): self.underline = self.text.lower().index(self.hotkey.lower()) else: self.underline = -1 def rebuild(self): old_underline = self.underline self.calc_underline() if self.underline != old_underline: self.needs_redraw = True super(HotkeyText, self).rebuild()
class Listbox(widget.FocusWidget, text.SelectableText): list = widget.causes_rebuild("_list") align = widget.causes_redraw("_align") list_size = widget.causes_rebuild("_list_size") list_pos = widget.causes_rebuild("_list_pos") list_item_shrink = widget.causes_rebuild("_list_item_shrink") def __init__(self, parent, pos, size, anchor=constants.TOP_LEFT, list=None, list_pos=0, list_item_height=0.03, list_item_shrink=1, borders=constants.ALL, item_borders=True, item_selectable=True, align=constants.CENTER, on_double_click_on_item=None, **kwargs): super(Listbox, self).__init__(parent, pos, size, anchor=anchor, **kwargs) self.display_elements = [] self.borders = borders self.item_borders = item_borders self.item_selectable = item_selectable self.item_borders = item_borders self.align = align self.list_item_height = list_item_height self.list_item_shrink = list_item_shrink self.list_pos = list_pos self.list = list or [] self.auto_scroll = True self.on_double_click_on_item = on_double_click_on_item self.scrollbar = scrollbar.UpdateScrollbar(self, update_func=self.on_scroll) def add_hooks(self): super(Listbox, self).add_hooks() if self.parent is not None: self.parent.add_handler(constants.CLICK, self.on_click, 90) self.parent.add_handler(constants.DOUBLECLICK, self.on_double_click, 200) self.parent.add_key_handler(pygame.K_UP, self.got_key, only_on_event_type=pygame.KEYDOWN) self.parent.add_key_handler(pygame.K_DOWN, self.got_key, only_on_event_type=pygame.KEYDOWN) self.parent.add_key_handler(pygame.K_PAGEUP, self.got_key, only_on_event_type=pygame.KEYDOWN) self.parent.add_key_handler(pygame.K_PAGEDOWN, self.got_key, only_on_event_type=pygame.KEYDOWN) def remove_hooks(self): super(Listbox, self).remove_hooks() if self.parent is not None: self.parent.remove_handler(constants.CLICK, self.on_click) self.parent.remove_handler(constants.DOUBLECLICK, self.on_double_click) self.parent.remove_key_handler(pygame.K_UP, self.got_key) self.parent.remove_key_handler(pygame.K_DOWN, self.got_key) self.parent.remove_key_handler(pygame.K_PAGEUP, self.got_key) self.parent.remove_key_handler(pygame.K_PAGEDOWN, self.got_key) def on_scroll(self, scroll_pos): self.needs_rebuild = True def on_click(self, event): if self.collision_rect.collidepoint(event.pos): self.has_focus = True self.took_focus(self) if (self.item_selectable): # Figure out which element was clicked... index = self.find_item_under_mouse(event) # ... and select it. self.list_pos = self.safe_pos(index + self.scrollbar.scroll_pos) def on_double_click(self, event): if self.on_double_click_on_item is None: return if self.collision_rect.collidepoint( event.pos) and self.item_selectable: index = self.find_item_under_mouse(event) if index > -1: self.on_double_click_on_item(event) def current_item(self): if 0 <= self.list_pos < len(self.list): return self.list[self.list_pos] return None def find_item_under_mouse(self, event): local_vert_abs = event.pos[1] - self.collision_rect[1] local_vert_pos = local_vert_abs / float(self.collision_rect.height) index = int(local_vert_pos * len(self.display_elements)) if 0 <= index < len(self.list): return index return -1 def safe_pos(self, raw_pos): return max(0, min(len(self.list) - 1, raw_pos)) def got_key(self, event, require_focus=True): if not self.item_selectable: return if require_focus and not self.has_focus: return if event.key == pygame.K_UP: new_pos = self.list_pos - 1 elif event.key == pygame.K_DOWN: new_pos = self.list_pos + 1 elif event.key == pygame.K_PAGEUP: new_pos = self.list_pos - (self.scrollbar.window - 1) elif event.key == pygame.K_PAGEDOWN: new_pos = self.list_pos + (self.scrollbar.window - 1) else: return self.list_pos = self.safe_pos(new_pos) self.scrollbar.scroll_to(self.list_pos) raise constants.Handled def num_elements(self): # TODO: If needed, add a paramater to display a fixed number of element. rect = self._make_collision_rect() list_item_height = self.list_item_height # Calculate the min height of one element. if list_item_height > 0: min_height = list_item_height * g.real_screen_size[1] else: min_height = -list_item_height * rect.height # Display a number calculate by the size of one item. list_size = max(1, rect.height // min_height) return int(math.ceil(list_size)) def remake_elements(self): list_size = self.num_elements() current_size = len(self.display_elements) if current_size > list_size: # Remove the excess ones. for child in self.display_elements[list_size:]: child.parent = None del self.display_elements[list_size:] elif current_size < list_size: if current_size > 0: if (self.item_borders): self.display_elements[-1].borders = (constants.LEFT, constants.TOP) else: self.display_elements[-1].borders = (constants.LEFT, ) # Create the new ones. self.display_elements.extend( self.make_element() for _ in xrange(list_size - current_size)) if (self.item_borders): self.display_elements[-1].borders = (constants.TOP, constants.LEFT, constants.BOTTOM) else: self.display_elements[0].borders = (constants.TOP, constants.LEFT) self.display_elements[-1].borders = (constants.LEFT, constants.BOTTOM) # Move the scrollbar to the end so that it gets drawn on top. self.children.remove(self.scrollbar) self.children.append(self.scrollbar) def make_element(self): borders = (constants.TOP, constants.LEFT) if self.item_borders else (constants.LEFT, ) return text.SelectableText(self, None, None, anchor=constants.TOP_LEFT, borders=borders, shrink_factor=self.list_item_shrink, border_color=self.border_color, selected_color=self.selected_color, unselected_color=self.unselected_color, align=self.align) def resize(self): super(Listbox, self).resize() if self.num_elements() != len(self.display_elements): self.remake_elements() self.scrollbar.resize() # FIXME: resize should not call rebuild self.needs_resize = False self.rebuild() def rebuild(self): self.list_pos = self.safe_pos(self.list_pos) if self.needs_resize: self.resize() return window_size = len(self.display_elements) list_size = len(self.list) self.scrollbar.window = len(self.display_elements) self.scrollbar.elements = list_size self.scrollbar.rebuild() if self.auto_scroll: self.auto_scroll = False self.scrollbar.center(self.list_pos) scrollbar_width = self.scrollbar.real_size[0] my_width = self.real_size[0] scrollbar_rel_width = scrollbar_width / float(my_width) offset = self.scrollbar.scroll_pos for index, element in enumerate(self.display_elements): list_index = index + offset # Position and size the element. element.pos = (0, -index / float(window_size)) element.size = (-1 + scrollbar_rel_width, -1 / float(window_size)) if (self.item_selectable): element.selected = (list_index == self.list_pos) # Set up the element contents. self.update_element(element, list_index) self.needs_redraw = True super(Listbox, self).rebuild() def update_element(self, element, list_index): if 0 <= list_index < len(self.list): element.text = self.list[list_index] else: element.text = ""
class Slider(button.Button): slider_pos = widget.causes_rebuild("_slider_pos") slider_max = widget.causes_rebuild("_slider_max") slider_size = widget.causes_rebuild("_slider_size") horizontal = widget.causes_rebuild("_horizontal") slider_color = widget.auto_reconfig("_slider_color", "resolved", g.resolve_color_alias) resolved_slider_color = widget.causes_redraw("_resolved_slider_color") def __init__(self, parent, pos = (-1,0), size = (-.1, -1), anchor = constants.TOP_RIGHT, borders = constants.ALL, border_color=None, background_color=None, slider_color=None, slider_pos=0, slider_max=10, slider_size=5, horizontal=False, **kwargs): kwargs.setdefault("priority", 80) super(Slider, self).__init__(parent, pos, size, anchor=anchor, **kwargs) border_color = border_color or "slider_border" background_color = background_color or "slider_background" slider_color = slider_color or "slider_background_slider" self.borders = borders self.border_color = border_color self.background_color = background_color self.selected_color = background_color self.unselected_color = background_color self.slider_color = slider_color self.slider_pos = slider_pos self.slider_max = slider_max self.slider_size = slider_size self.horizontal = horizontal self.drag_state = None self.button = button.Button(self, pos = None, size = None, anchor = constants.TOP_LEFT, border_color = border_color, selected_color = slider_color, unselected_color = slider_color, priority = self.priority - 5) def redraw(self): super(Slider, self).redraw() self.button.selected_color = self.slider_color self.button.unselected_color = self.slider_color def add_hooks(self): super(Slider, self).add_hooks() self.parent.add_handler(constants.DRAG, self.handle_drag) self.parent.add_handler(constants.CLICK, self.handle_click, 50) def remove_hooks(self): super(Slider, self).remove_hooks() self.parent.remove_handler(constants.DRAG, self.handle_drag) self.parent.remove_handler(constants.CLICK, self.handle_click) def _calc_length(self, items): return items / float(self.slider_size + self.slider_max) def rebuild(self): super(Slider, self).rebuild() self.needs_resize = True def resize(self): super(Slider, self).resize() bar_start = self._calc_length(self.slider_pos) bar_length = self._calc_length(self.slider_size) if self.horizontal: self.button.pos = (-bar_start, 0) self.button.size = (-bar_length, -1) borders = [constants.TOP, constants.BOTTOM] self.button.resize() real_pos = self.button.real_pos[0] real_size = self.button.real_size[0] if real_pos == 0: borders.append(constants.LEFT) if real_pos + real_size == self.real_size[0]: borders.append(constants.RIGHT) self.button.borders = tuple(borders) else: self.button.pos = (0, -bar_start) self.button.size = (-1, -bar_length) borders = [constants.LEFT, constants.RIGHT] self.button.resize() real_pos = self.button.real_pos[1] real_size = self.button.real_size[1] if real_pos == 0: borders.append(constants.TOP) if real_pos + real_size == self.real_size[1]: borders.append(constants.BOTTOM) self.button.borders = tuple(borders) def handle_drag(self, event): if not self.visible: return if self.drag_state == None: self.start_pos = tuple(event.pos[i]-event.rel[i] for i in range(2)) self.start_slider_pos = self.slider_pos if self.button.is_over(self.start_pos): self.drag_state = True else: self.drag_state = False if self.drag_state == True: if self.horizontal: dir = 0 else: dir = 1 mouse_pos = pygame.mouse.get_pos() rel = mouse_pos[dir] - self.start_pos[dir] unit = self._calc_length(1) * self.real_size[dir] movement = int( ( rel + (unit / 2.) ) // unit ) new_pos = self.safe_pos(self.start_slider_pos + movement) self.slider_pos = new_pos raise constants.Handled def safe_pos(self, value): return max(0, min(self.slider_max, value)) def handle_click(self, event): if self.drag_state == True: self.drag_state = None if not self.is_over(pygame.mouse.get_pos()): raise constants.Handled else: self.drag_state = None def jump(self, go_lower, big_jump=False, tiny_jump=False): if big_jump: jump_dist = max(1, self.slider_max // 2) elif tiny_jump: jump_dist = max(1, self.slider_max // 100) else: jump_dist = max(1, self.slider_size - 1) if go_lower: self.slider_pos = self.safe_pos(self.slider_pos - jump_dist) else: self.slider_pos = self.safe_pos(self.slider_pos + jump_dist) def activated(self, event): assert event.type == pygame.MOUSEBUTTONUP if self.horizontal: self.jump(go_lower=(event.pos[0] < self.button.collision_rect[0])) else: self.jump(go_lower = event.pos[1] < self.button.collision_rect[1]) raise constants.Handled