Esempio n. 1
0
    def __init__(self,
                 contents=None,
                 border=1,
                 step_size=None,
                 scroll_horizontal="auto",
                 scroll_vertical="auto",
                 **kwargs):
        Table.__init__(self,
                       rows=2,
                       cols=2,
                       padding=[border, 0, 0, border],
                       **kwargs)

        self.viewport = Viewport(x_align=0, y_align=0)
        self.interactive, self.can_focus = True, True

        if step_size:
            self.step_size = step_siz

        #: with of the surrounding border in pixels
        self.border = border

        #: visibility of the horizontal scroll bar. True for always, False for never and "auto" for auto
        self.scroll_horizontal = scroll_horizontal

        #: visibility of the vertical scroll bar. True for always, False for never and "auto" for auto
        self.scroll_vertical = scroll_vertical

        #even if we are don't need the scrollbar, do we reserve space for it?
        self.reserve_space_vertical = False
        self.reserve_space_horizontal = False

        #: vertical scroll bar widget
        self.vscroll = ScrollBar()

        #: horizontal scroll bar widget
        self.hscroll = ScrollBar(horizontal=True)

        self.attach(self.viewport, 0, 1, 0, 1)
        self.attach(self.vscroll, 1, 2, 0, 1)
        self.attach(self.hscroll, 0, 1, 1, 2)

        if contents:
            if isinstance(contents, graphics.Sprite):
                contents = [contents]

            for sprite in contents:
                self.add_child(sprite)

        self.connect("on-mouse-scroll", self.__on_mouse_scroll)
        for bar in (self.vscroll, self.hscroll):
            self.connect_child(bar, "on-scroll", self.on_scroll)
            self.connect_child(bar, "on-scroll-step", self.on_scroll_step)
            self.connect_child(bar, "on-scroll-page", self.on_scroll_page)
Esempio n. 2
0
    def __init__(self, column, row, on_update_state_action, parent_viewport,
                 logger):
        def on_check(button):
            self.on_change_state(TRUE)
            self.on_update_state_action(TRUE)

        def on_uncheck(button):
            self.on_change_state(FALSE)
            self.on_update_state_action(FALSE)

        self.logger = logger
        self.column, self.row = column, row
        self.parent_viewport = parent_viewport
        self.viewport = Viewport()
        USER_DB_CURSOR.execute('SELECT current_locale FROM i18n')
        self.current_locale = USER_DB_CURSOR.fetchone()[0]
        self.on_update_state_action = on_update_state_action
        self.checked_checkbox_button, self.unchecked_checkbox_button = create_two_state_button(
            CheckedCheckboxButton(on_click_action=on_uncheck,
                                  parent_viewport=self.viewport),
            UncheckedCheckboxButton(on_click_action=on_check,
                                    parent_viewport=self.viewport))
        self.buttons = [
            self.checked_checkbox_button, self.unchecked_checkbox_button
        ]
        self.screen_resolution = (0, 0)
        self.description_label = None
        self.is_activated = False
        self.opacity = 0
        self.on_window_resize_handlers = [
            self.on_window_resize,
        ]
Esempio n. 3
0
    def __init__(self, stage_number, on_buy_stage_action, parent_viewport):
        def on_buy_shop_stage(button):
            button.on_deactivate(instant=True)
            self.on_buy_stage_action(self.stage_number)

        self.logger = getLogger(
            f'root.app.game.map.shop.view.cell.{stage_number}')
        self.is_activated = False
        self.stage_number = stage_number
        USER_DB_CURSOR.execute('SELECT current_locale FROM i18n')
        self.current_locale = USER_DB_CURSOR.fetchone()[0]
        self.on_buy_stage_action = on_buy_stage_action
        self.data = []
        self.screen_resolution = (0, 0)
        self.parent_viewport = parent_viewport
        self.viewport = Viewport()
        self.locked_label = ShopStageLockedLabel(parent_viewport=self.viewport)
        self.level_placeholder_label = ShopStageLevelPlaceholderLabel(
            parent_viewport=self.viewport)
        self.previous_stage_placeholder_label = ShopStagePreviousStagePlaceholderLabel(
            parent_viewport=self.viewport)
        self.hourly_profit_description_label = ShopStageHourlyProfitDescriptionLabel(
            parent_viewport=self.viewport)
        self.hourly_profit_value_label = ShopStageHourlyProfitValueLabel(
            parent_viewport=self.viewport)
        self.storage_capacity_description_label = ShopStageStorageCapacityDescriptionLabel(
            parent_viewport=self.viewport)
        self.storage_capacity_value_label = ShopStageStorageCapacityValueLabel(
            parent_viewport=self.viewport)
        self.exp_bonus_description_label = ShopStageExpBonusDescriptionLabel(
            parent_viewport=self.viewport)
        self.exp_bonus_value_label = ShopStageExpBonusValueLabel(
            parent_viewport=self.viewport)
        self.price_label = ShopStagePriceLabel(parent_viewport=self.viewport)
        self.under_construction_label = ShopStageUnderConstructionLabel(
            parent_viewport=self.viewport)
        self.build_button = BuildShopStageButton(
            on_click_action=on_buy_shop_stage, parent_viewport=self.viewport)
        self.buttons = [
            self.build_button,
        ]
        USER_DB_CURSOR.execute('''SELECT money FROM game_progress''')
        self.money = USER_DB_CURSOR.fetchone()[0]
        self.opacity = 0
        self.on_window_resize_handlers = [
            self.on_window_resize, self.locked_label.on_window_resize,
            self.level_placeholder_label.on_window_resize,
            self.previous_stage_placeholder_label.on_window_resize,
            self.hourly_profit_description_label.on_window_resize,
            self.hourly_profit_value_label.on_window_resize,
            self.storage_capacity_description_label.on_window_resize,
            self.storage_capacity_value_label.on_window_resize,
            self.exp_bonus_description_label.on_window_resize,
            self.exp_bonus_value_label.on_window_resize,
            self.price_label.on_window_resize,
            self.under_construction_label.on_window_resize
        ]
Esempio n. 4
0
    def __init__(self, column, row, possible_values_list,
                 on_update_state_action, parent_viewport, logger):
        def on_increment(button):
            self.choice_state += 1
            self.temp_value_label.on_update_args(
                self.possible_values_list[self.choice_state])
            self.on_update_state_action(self.choice_state)
            if self.choice_state >= len(self.possible_values_list) - 1:
                button.on_disable()
                button.state = 'normal'

            if self.choice_state > 0:
                button.paired_button.on_activate(instant=True)

        def on_decrement(button):
            self.choice_state -= 1
            self.temp_value_label.on_update_args(
                self.possible_values_list[self.choice_state])
            self.on_update_state_action(self.choice_state)
            if self.choice_state <= 0:
                button.on_disable()
                button.state = 'normal'

            if self.choice_state < len(self.possible_values_list) - 1:
                button.paired_button.on_activate(instant=True)

        self.logger = logger
        self.column, self.row = column, row
        self.parent_viewport = parent_viewport
        self.viewport = Viewport()
        USER_DB_CURSOR.execute('SELECT current_locale FROM i18n')
        self.current_locale = USER_DB_CURSOR.fetchone()[0]
        self.on_update_state_action = on_update_state_action
        self.screen_resolution = (0, 0)
        self.description_label = None
        self.temp_value_label = None
        self.choice_state = None
        self.possible_values_list = possible_values_list
        self.increment_button, self.decrement_button = create_two_state_button(
            IncrementButton(on_click_action=on_increment,
                            parent_viewport=self.viewport),
            DecrementButton(on_click_action=on_decrement,
                            parent_viewport=self.viewport))
        self.buttons = [self.increment_button, self.decrement_button]
        self.is_activated = False
        self.opacity = 0
        self.on_window_resize_handlers = [
            self.on_window_resize,
        ]
Esempio n. 5
0
    def __init__(self, contents=None, border=1, step_size=None,
                 scroll_horizontal="auto", scroll_vertical="auto",
                 **kwargs):
        Table.__init__(self, rows=2, cols=2, padding=[border, 0, 0, border], **kwargs)

        self.viewport = Viewport(x_align=0, y_align=0)
        self.interactive, self.can_focus = True, True

        if step_size:
            self.step_size = step_siz

        #: with of the surrounding border in pixels
        self.border = border

        #: visibility of the horizontal scroll bar. True for always, False for never and "auto" for auto
        self.scroll_horizontal = scroll_horizontal

        #: visibility of the vertical scroll bar. True for always, False for never and "auto" for auto
        self.scroll_vertical = scroll_vertical

        #even if we are don't need the scrollbar, do we reserve space for it?
        self.reserve_space_vertical = False
        self.reserve_space_horizontal = False


        #: vertical scroll bar widget
        self.vscroll = ScrollBar()

        #: horizontal scroll bar widget
        self.hscroll = ScrollBar(horizontal = True)

        self.attach(self.viewport, 0, 1, 0, 1)
        self.attach(self.vscroll, 1, 2, 0, 1)
        self.attach(self.hscroll, 0, 1, 1, 2)


        if contents:
            if isinstance(contents, graphics.Sprite):
                contents = [contents]

            for sprite in contents:
                self.add_child(sprite)

        self.connect("on-mouse-scroll", self.__on_mouse_scroll)
        for bar in (self.vscroll, self.hscroll):
            self.connect_child(bar, "on-scroll", self.on_scroll)
            self.connect_child(bar, "on-scroll-step", self.on_scroll_step)
            self.connect_child(bar, "on-scroll-page", self.on_scroll_page)
Esempio n. 6
0
    def __init__(
            self, map_id, on_buy_map_action, on_switch_map_action, on_set_money_target_action,
            on_reset_money_target_action, parent_viewport, logger
    ):
        def on_set_money_target(button):
            pass

        def on_reset_money_target(button):
            pass

        def on_buy_map(button):
            button.on_deactivate()
            self.on_buy_map_action(self.map_id)

        def on_switch_map(button):
            self.on_switch_map_action(self.map_id)

        self.logger = logger
        self.is_activated = False
        self.map_id = map_id
        self.parent_viewport = parent_viewport
        self.viewport = Viewport()
        USER_DB_CURSOR.execute('SELECT current_locale FROM i18n')
        self.current_locale = USER_DB_CURSOR.fetchone()[0]
        self.on_buy_map_action = on_buy_map_action
        self.on_switch_map_action = on_switch_map_action
        self.on_set_money_target_action = on_set_money_target_action
        self.on_reset_money_target_action = on_reset_money_target_action
        self.screen_resolution = (0, 0)
        USER_DB_CURSOR.execute('''SELECT level, money FROM game_progress''')
        self.level, self.money = USER_DB_CURSOR.fetchone()
        self.money_target_activated = False
        self.opacity = 0
        self.build_map_button = BuildMapButton(on_click_action=on_buy_map, parent_viewport=self.viewport)
        self.switch_map_button = SwitchMapButton(on_click_action=on_switch_map, parent_viewport=self.viewport)
        self.buttons = [self.build_map_button, self.switch_map_button]
        self.data = MAP_SWITCHER_STATE_MATRIX[self.map_id]
        self.locked_label = MapSwitcherCellLockedLabel(parent_viewport=self.viewport)
        self.level_placeholder_label = MapSwitcherLevelPlaceholderLabel(parent_viewport=self.viewport)
        self.level_placeholder_label.on_update_args((self.data[MAP_LEVEL_REQUIRED], ))
        self.unlock_available_label = MapSwitcherUnlockAvailableLabel(parent_viewport=self.viewport)
        self.unlock_available_label.on_update_args((self.data[MAP_PRICE], ))
        self.title_label = None
        self.icon_labels = []
        self.on_window_resize_handlers = [
            self.on_window_resize, self.locked_label.on_window_resize, self.level_placeholder_label.on_window_resize,
            self.unlock_available_label.on_window_resize
        ]
Esempio n. 7
0
 def __init__(self, column, row, parent_viewport, logger):
     self.logger = logger
     self.column, self.row = column, row
     self.parent_viewport = parent_viewport
     self.viewport = Viewport()
     USER_DB_CURSOR.execute('SELECT current_locale FROM i18n')
     self.current_locale = USER_DB_CURSOR.fetchone()[0]
     self.description_label = None
     self.checkboxes = []
     self.buttons = []
     self.is_activated = False
     self.screen_resolution = (0, 0)
     self.opacity = 0
     self.on_window_resize_handlers = [
         self.on_window_resize,
     ]
Esempio n. 8
0
 def __init__(self, controller, logger, child_window=False):
     self.logger = logger
     self.child_window = child_window
     self.is_activated = False
     self.controller = controller
     self.viewport = Viewport()
     self.opacity = 0
     self.buttons = []
     self.game_progress_notifications = []
     self.malfunction_notifications = []
     self.on_mouse_press_handlers = []
     self.on_mouse_release_handlers = []
     self.on_mouse_motion_handlers = []
     self.on_mouse_drag_handlers = []
     self.on_mouse_leave_handlers = []
     self.on_mouse_scroll_handlers = []
     self.on_key_press_handlers = []
     self.on_text_handlers = []
     self.on_window_resize_handlers = [
         self.on_window_resize,
     ]
     self.on_window_activate_handlers = [
         self.on_window_activate,
     ]
     self.on_window_show_handlers = [
         self.on_window_show,
     ]
     self.on_window_deactivate_handlers = [
         self.on_window_deactivate,
     ]
     self.on_window_hide_handlers = [
         self.on_window_hide,
     ]
     self.screen_resolution = (0, 0)
     USER_DB_CURSOR.execute('SELECT current_locale, clock_24h FROM i18n')
     self.current_locale, self.clock_24h_enabled = USER_DB_CURSOR.fetchone()
     self.game_progress_notifications_enabled = False
     self.shader_sprite = None
     USER_DB_CURSOR.execute('SELECT * FROM notification_settings')
     self.level_up_notification_enabled, self.feature_unlocked_notification_enabled, \
         self.construction_completed_notification_enabled, self.enough_money_notification_enabled, \
         self.bonus_expired_notification_enabled, self.shop_storage_notification_enabled,\
         self.voice_not_found_notification_enabled = USER_DB_CURSOR.fetchone()
Esempio n. 9
0
 def __init__(self, column, row, on_update_state_action, parent_viewport,
              logger):
     self.logger = logger
     self.column, self.row = column, row
     self.parent_viewport = parent_viewport
     self.viewport = Viewport()
     USER_DB_CURSOR.execute('SELECT current_locale FROM i18n')
     self.current_locale = USER_DB_CURSOR.fetchone()[0]
     self.on_update_state_action = on_update_state_action
     self.screen_resolution = (0, 0)
     self.description_label = None
     self.knob = None
     self.is_activated = False
     self.opacity = 0
     self.on_window_resize_handlers = [
         self.on_window_resize,
     ]
     self.on_mouse_motion_handlers = []
     self.on_mouse_press_handlers = []
     self.on_mouse_release_handlers = []
     self.on_mouse_drag_handlers = []
Esempio n. 10
0
    def __init__(self, labels = None, tab_position="top", tab_spacing = 0,
                 scroll_position = None, show_scroll = "auto", scroll_selects_tab = True, **kwargs):
        Box.__init__(self, horizontal=False, spacing=0, **kwargs)

        #: list of tabs in the order of appearance
        self.tabs = []

        #: list of pages in the order of appearance
        self.pages = []

        #: container of the pages
        self.pages_container = self.pages_container_class(padding=1)

        #: container of tabs. useful if you want to adjust padding/placement
        self.tabs_container = Group(fill=False, spacing=tab_spacing)
        self.tabs_container.on_mouse_over = lambda button: False  # ignore select-on-drag

        # viewport so that tabs don't go out of their area
        self._tabs_viewport = Viewport()
        self._tabs_viewport.get_min_size = self._tabs_viewport_get_min_size
        self._tabs_viewport.resize_children = self._tabs_viewport_resize_children
        self._tabs_viewport.add_child(self.tabs_container)

        #: wether scroll buttons should select next/previos tab or show the
        #: next/previos tab out of the view
        self.scroll_selects_tab = scroll_selects_tab

        #: container for custom content before tabs
        self.before_tabs = HBox(expand=False)

        #: container for custom content after tabs
        self.after_tabs = HBox(expand=False)

        #: button to scroll tabs back
        self.tabs_back = self.scroll_buttons_class("left",
                                                   expand=False,
                                                   visible=False,
                                                   enabled=False,
                                                   repeat_down_delay = 150)
        self.tabs_back.connect("on-mouse-down", self.on_back_press)

        #: button to scroll tabs forward
        self.tabs_forward = self.scroll_buttons_class("right",
                                                      expand=False,
                                                      visible=False,
                                                      enabled=False,
                                                      repeat_down_delay = 150)
        self.tabs_forward.connect("on-mouse-down", self.on_forward_press)


        #: the wrapping container that holds also the scroll buttons and everyting
        self.tabbox = self.tabbox_class(expand = False, expand_vert = False)
        self.tabbox.get_min_size = self.tabbox_get_min_size
        self.tabbox.get_height_for_width_size = self.tabbox_get_height_for_width_size


        self.tabbox.add_child(self.before_tabs, self.tabs_back,
                              self._tabs_viewport,
                              self.tabs_forward, self.after_tabs)

        #: current page
        self.current_page = 0

        #: tab position: top, right, bottom, left and combinations: "top-right", "left-bottom", etc.
        self.tab_position = tab_position


        for label in labels or []:
            self.add_page(label)

        #: where to place the scroll buttons on tab overflow. one of "start"
        #: (both at the start), "end" (both at the end) or "around" (on left
        #: and right of the tabs)
        self.scroll_position = scroll_position

        #: determines when to show scroll buttons. True for always, False for
        #: never, "auto" for auto appearing and disappearing, and
        #: "auto_invisible" for going transparent instead of disappearing
        #: (the latter avoids tab toggle)
        self.show_scroll = show_scroll
Esempio n. 11
0
class ScrollArea(Table):
    """Container that will display scroll bars (either horizontal or vertical or
    both) if the space occupied by child is larger than the available.
    """

    #: scroll step size in pixels
    step_size = 30

    def __init__(self, contents=None, border=1, step_size=None,
                 scroll_horizontal="auto", scroll_vertical="auto",
                 **kwargs):
        Table.__init__(self, rows=2, cols=2, padding=[border, 0, 0, border], **kwargs)

        self.viewport = Viewport(x_align=0, y_align=0)
        self.interactive, self.can_focus = True, True

        if step_size:
            self.step_size = step_siz

        #: with of the surrounding border in pixels
        self.border = border

        #: visibility of the horizontal scroll bar. True for always, False for never and "auto" for auto
        self.scroll_horizontal = scroll_horizontal

        #: visibility of the vertical scroll bar. True for always, False for never and "auto" for auto
        self.scroll_vertical = scroll_vertical

        #even if we are don't need the scrollbar, do we reserve space for it?
        self.reserve_space_vertical = False
        self.reserve_space_horizontal = False


        #: vertical scroll bar widget
        self.vscroll = ScrollBar()

        #: horizontal scroll bar widget
        self.hscroll = ScrollBar(horizontal = True)

        self.attach(self.viewport, 0, 1, 0, 1)
        self.attach(self.vscroll, 1, 2, 0, 1)
        self.attach(self.hscroll, 0, 1, 1, 2)


        if contents:
            if isinstance(contents, graphics.Sprite):
                contents = [contents]

            for sprite in contents:
                self.add_child(sprite)

        self.connect("on-mouse-scroll", self.__on_mouse_scroll)
        for bar in (self.vscroll, self.hscroll):
            self.connect_child(bar, "on-scroll", self.on_scroll)
            self.connect_child(bar, "on-scroll-step", self.on_scroll_step)
            self.connect_child(bar, "on-scroll-page", self.on_scroll_page)


    def __setattr__(self, name, val):
        Table.__setattr__(self, name, val)
        if name in ("scroll_horizontal", "scroll_vertical"):
            self.queue_resize()

    def add_child(self, *sprites):
        for sprite in sprites:
            if sprite in (self.viewport, self.vscroll, self.hscroll):
                Table.add_child(self, sprite)
            else:
                self.viewport.add_child(*sprites)

    def get_min_size(self):
        return self.min_width or 0, self.min_height or 0

    def resize_children(self):
        # give viewport all our space
        w, h = self.viewport.alloc_w, self.viewport.alloc_w
        self.viewport.alloc_w = self.width - self.horizontal_padding
        self.viewport.alloc_h = self.height - self.vertical_padding

        # then check if it fits
        area_w, area_h = self.viewport.get_container_size()
        hvis = self.scroll_horizontal is True or (self.scroll_horizontal == "auto" and self.width < area_w)
        if hvis:
            if self.reserve_space_horizontal:
                self.hscroll.opacity = 1
            else:
                self.hscroll.visible = True
        else:
            if self.reserve_space_horizontal:
                self.hscroll.opacity = 0
            else:
                self.hscroll.visible = False
        vvis = self.scroll_vertical is True or (self.scroll_vertical == "auto" and self.height < area_h)
        if vvis:
            if self.reserve_space_vertical:
                self.vscroll.opacity = 1
            else:
                self.vscroll.visible = True
        else:
            if self.reserve_space_vertical:
                self.vscroll.opacity = 0
            else:
                self.vscroll.visible = False

        Table.resize_children(self)


        if self.viewport.child:
            self.scroll_x(self.viewport.child.x)
            self.scroll_y(self.viewport.child.y)


    def _scroll_y(self, y):
        # these are split into two to avoid echoes
        # check if we have anything to scroll
        area_h = self.viewport.get_container_size()[1]
        viewport_h = self.viewport.height

        if y < 0:
            y = max(y, viewport_h - area_h)
        y = min(y, 0)
        self.viewport.child.y = y

    def scroll_y(self, y):
        """scroll to y position"""
        self._scroll_y(y)
        self._update_sliders()

    def _scroll_x(self, x):
        area_w = self.viewport.get_container_size()[0]
        viewport_w = self.viewport.width
        if not viewport_w:
            return

        # when window grows pull in the viewport if it's out of the bounds
        if x < 0:
            x = max(x, viewport_w - area_w)
        x = min(x, 0)

        self.viewport.child.x = x

    def scroll_x(self, x):
        """scroll to x position"""
        self._scroll_x(x)
        self._update_sliders()


    def _update_sliders(self):
        area_w, area_h = self.viewport.get_container_size()
        area_w = area_w or 1 # avoid division by zero
        area_h = area_h or 1

        if self.vscroll.visible:
            v_aspect = min(float(self.viewport.height) / area_h, 1)
            self.vscroll.size = min(float(self.viewport.height) / area_h, 1)

            if v_aspect == 1:
                self.vscroll.offset = 0
            else:
                self.vscroll.offset = -1 * self.viewport.child.y / (area_h * (1 - v_aspect))

        if self.hscroll.visible:
            h_aspect = min(float(self.viewport.width) / area_w, 1)
            self.hscroll.size = min(float(self.viewport.width) / area_w, 1)
            if h_aspect == 1:
                self.hscroll.offset = 0
            else:
                self.hscroll.offset = -1 * self.viewport.child.x / (area_w * (1 - h_aspect))


    """events"""
    def __on_mouse_scroll(self, sprite, event):
        direction  = 1 if event.direction == gdk.ScrollDirection.DOWN else -1
        self.scroll_y(self.viewport.child.y - self.step_size * direction)

    def on_scroll(self, bar, offset):
        area_w, area_h = self.viewport.get_container_size()
        viewport_w, viewport_h = self.viewport.width, self.viewport.height

        if bar == self.vscroll:
            aspect = float(area_h - viewport_h) / area_h
            self._scroll_y(-1 * (area_h * aspect) * offset)
        else:
            aspect = float(area_w - viewport_w) / area_w
            self._scroll_x(-1 * (area_w * aspect) * offset)

    def on_scroll_step(self, bar, direction):
        if bar == self.vscroll:
            self.scroll_y(self.viewport.child.y - self.step_size * direction)
        else:
            self.scroll_x(self.viewport.child.x - self.step_size * direction)

    def on_scroll_page(self, bar, direction):
        if bar == self.vscroll:
            self.scroll_y(self.viewport.child.y - (self.viewport.height + self.step_size) * direction)
        else:
            self.scroll_x(self.viewport.child.y - (self.viewport.width + self.step_size) * direction)


    def do_render(self):
        if self.border:
            self.graphics.rectangle(0.5, 0.5, self.width, self.height)
            self.graphics.set_line_style(width=self.border)
            stroke_color = "#333" if self.focused else "#999"
            self.graphics.fill_stroke("#fff", stroke_color)
        else:
            self.graphics.rectangle(0, 0, self.width, self.height)
            self.graphics.fill("#fff")
Esempio n. 12
0
class ScrollArea(Table):
    """Container that will display scroll bars (either horizontal or vertical or
    both) if the space occupied by child is larger than the available.
    """

    #: scroll step size in pixels
    step_size = 30

    def __init__(self,
                 contents=None,
                 border=1,
                 step_size=None,
                 scroll_horizontal="auto",
                 scroll_vertical="auto",
                 **kwargs):
        Table.__init__(self,
                       rows=2,
                       cols=2,
                       padding=[border, 0, 0, border],
                       **kwargs)

        self.viewport = Viewport(x_align=0, y_align=0)
        self.interactive, self.can_focus = True, True

        if step_size:
            self.step_size = step_siz

        #: with of the surrounding border in pixels
        self.border = border

        #: visibility of the horizontal scroll bar. True for always, False for never and "auto" for auto
        self.scroll_horizontal = scroll_horizontal

        #: visibility of the vertical scroll bar. True for always, False for never and "auto" for auto
        self.scroll_vertical = scroll_vertical

        #even if we are don't need the scrollbar, do we reserve space for it?
        self.reserve_space_vertical = False
        self.reserve_space_horizontal = False

        #: vertical scroll bar widget
        self.vscroll = ScrollBar()

        #: horizontal scroll bar widget
        self.hscroll = ScrollBar(horizontal=True)

        self.attach(self.viewport, 0, 1, 0, 1)
        self.attach(self.vscroll, 1, 2, 0, 1)
        self.attach(self.hscroll, 0, 1, 1, 2)

        if contents:
            if isinstance(contents, graphics.Sprite):
                contents = [contents]

            for sprite in contents:
                self.add_child(sprite)

        self.connect("on-mouse-scroll", self.__on_mouse_scroll)
        for bar in (self.vscroll, self.hscroll):
            self.connect_child(bar, "on-scroll", self.on_scroll)
            self.connect_child(bar, "on-scroll-step", self.on_scroll_step)
            self.connect_child(bar, "on-scroll-page", self.on_scroll_page)

    def __setattr__(self, name, val):
        Table.__setattr__(self, name, val)
        if name in ("scroll_horizontal", "scroll_vertical"):
            self.queue_resize()

    def add_child(self, *sprites):
        for sprite in sprites:
            if sprite in (self.viewport, self.vscroll, self.hscroll):
                Table.add_child(self, sprite)
            else:
                self.viewport.add_child(*sprites)

    def get_min_size(self):
        return self.min_width or 0, self.min_height or 0

    def resize_children(self):
        # give viewport all our space
        w, h = self.viewport.alloc_w, self.viewport.alloc_w
        self.viewport.alloc_w = self.width - self.horizontal_padding
        self.viewport.alloc_h = self.height - self.vertical_padding

        # then check if it fits
        area_w, area_h = self.viewport.get_container_size()
        hvis = self.scroll_horizontal is True or (
            self.scroll_horizontal == "auto" and self.width < area_w)
        if hvis:
            if self.reserve_space_horizontal:
                self.hscroll.opacity = 1
            else:
                self.hscroll.visible = True
        else:
            if self.reserve_space_horizontal:
                self.hscroll.opacity = 0
            else:
                self.hscroll.visible = False
        vvis = self.scroll_vertical is True or (self.scroll_vertical == "auto"
                                                and self.height < area_h)
        if vvis:
            if self.reserve_space_vertical:
                self.vscroll.opacity = 1
            else:
                self.vscroll.visible = True
        else:
            if self.reserve_space_vertical:
                self.vscroll.opacity = 0
            else:
                self.vscroll.visible = False

        Table.resize_children(self)

        if self.viewport.child:
            self.scroll_x(self.viewport.child.x)
            self.scroll_y(self.viewport.child.y)

    def _scroll_y(self, y):
        # these are split into two to avoid echoes
        # check if we have anything to scroll
        area_h = self.viewport.get_container_size()[1]
        viewport_h = self.viewport.height

        if y < 0:
            y = max(y, viewport_h - area_h)
        y = min(y, 0)
        self.viewport.child.y = y

    def scroll_y(self, y):
        """scroll to y position"""
        self._scroll_y(y)
        self._update_sliders()

    def _scroll_x(self, x):
        area_w = self.viewport.get_container_size()[0]
        viewport_w = self.viewport.width
        if not viewport_w:
            return

        # when window grows pull in the viewport if it's out of the bounds
        if x < 0:
            x = max(x, viewport_w - area_w)
        x = min(x, 0)

        self.viewport.child.x = x

    def scroll_x(self, x):
        """scroll to x position"""
        self._scroll_x(x)
        self._update_sliders()

    def _update_sliders(self):
        area_w, area_h = self.viewport.get_container_size()
        area_w = area_w or 1  # avoid division by zero
        area_h = area_h or 1

        if self.vscroll.visible:
            v_aspect = min(float(self.viewport.height) / area_h, 1)
            self.vscroll.size = min(float(self.viewport.height) / area_h, 1)

            if v_aspect == 1:
                self.vscroll.offset = 0
            else:
                self.vscroll.offset = -1 * self.viewport.child.y / (
                    area_h * (1 - v_aspect))

        if self.hscroll.visible:
            h_aspect = min(float(self.viewport.width) / area_w, 1)
            self.hscroll.size = min(float(self.viewport.width) / area_w, 1)
            if h_aspect == 1:
                self.hscroll.offset = 0
            else:
                self.hscroll.offset = -1 * self.viewport.child.x / (
                    area_w * (1 - h_aspect))

    """events"""

    def __on_mouse_scroll(self, sprite, event):
        direction = 1 if event.direction == gdk.ScrollDirection.DOWN else -1
        self.scroll_y(self.viewport.child.y - self.step_size * direction)

    def on_scroll(self, bar, offset):
        area_w, area_h = self.viewport.get_container_size()
        viewport_w, viewport_h = self.viewport.width, self.viewport.height

        if bar == self.vscroll:
            aspect = float(area_h - viewport_h) / area_h
            self._scroll_y(-1 * (area_h * aspect) * offset)
        else:
            aspect = float(area_w - viewport_w) / area_w
            self._scroll_x(-1 * (area_w * aspect) * offset)

    def on_scroll_step(self, bar, direction):
        if bar == self.vscroll:
            self.scroll_y(self.viewport.child.y - self.step_size * direction)
        else:
            self.scroll_x(self.viewport.child.x - self.step_size * direction)

    def on_scroll_page(self, bar, direction):
        if bar == self.vscroll:
            self.scroll_y(self.viewport.child.y -
                          (self.viewport.height + self.step_size) * direction)
        else:
            self.scroll_x(self.viewport.child.y -
                          (self.viewport.width + self.step_size) * direction)

    def do_render(self):
        if self.border:
            self.graphics.rectangle(0.5, 0.5, self.width, self.height)
            self.graphics.set_line_style(width=self.border)
            stroke_color = "#333" if self.focused else "#999"
            self.graphics.fill_stroke("#fff", stroke_color)
        else:
            self.graphics.rectangle(0, 0, self.width, self.height)
            self.graphics.fill("#fff")
Esempio n. 13
0
    def __init__(self, text="", draw_border = True,  valid_chars = None,
                 validate_on_type = True, single_paragraph = True,
                 text_formatter = None, alignment = None,
                 font_desc = None, **kwargs):
        Bin.__init__(self, **kwargs)

        self.display_label = graphics.Label(color=self.color)

        self.viewport = Viewport(self.display_label)
        self.viewport.connect("on-render", self.__on_viewport_render)

        self.add_child(self.viewport)

        self.can_focus = True

        self.interactive = True

        self.editable = True

        #: current cursor position
        self.cursor_position = None

        #: start position of the selection
        self.selection_start = 0

        #: end position of the selection
        self.selection_end = 0

        if font_desc is not None:
            self.font_desc = font_desc
        self.display_label.font_desc = self.font_desc

        #: text alignment in the entry
        self.alignment = alignment

        #: if True, a border will be drawn around the input element
        self.draw_border = draw_border

        #self.connect("on-key-press", self.__on_key_press)
        self.connect("on-mouse-down", self.__on_mouse_down)
        self.connect("on-double-click", self.__on_double_click)
        self.connect("on-triple-click", self.__on_triple_click)
        self.connect("on-blur", self.__on_blur)
        self.connect("on-focus", self.__on_focus)

        self.connect_after("on-render", self.__on_render)

        self._scene_mouse_move = None
        self._scene_mouse_up = None
        self._selection_start_position = None
        self._letter_positions = []

        #: a string, function or regexp or valid chars for the input
        #: in case of function, it will receive the string to be tested
        #: as input and expects to receive back a boolean of whether the string
        #: is valid or not
        self.valid_chars = valid_chars

        #: should the content be validate right when typing and invalid version prohibited
        self.validate_on_type = validate_on_type

        #: function to style the entry text - change color and such
        #: the function receives one param - the text, and must return
        #: processed text back. will be using original text if the function
        #: does not return anything.
        #: Note: this function can change only the style, not the actual content
        #: as the latter will mess up text selection because of off-sync between
        #: the label value and what is displayed
        self.text_formatter = text_formatter if text_formatter else self.text_formatter


        #: should the text input support multiple lines
        self.single_paragraph = single_paragraph

        self.update_text(text)
        self._last_good_value = text # last known good value of the input
Esempio n. 14
0
    def __init__(self, construction_type, row, on_buy_construction_action,
                 on_set_money_target_action, on_reset_money_target_action,
                 parent_viewport):
        def on_set_money_target(button):
            button.paired_button.opacity = button.opacity
            button.on_deactivate(instant=True)
            button.paired_button.on_activate()
            self.on_set_money_target_action(self.construction_type, self.row,
                                            self.entity_number)

        def on_reset_money_target(button):
            button.paired_button.opacity = button.opacity
            button.on_deactivate(instant=True)
            button.paired_button.on_activate()
            self.on_reset_money_target_action()

        def on_buy_construction(button):
            button.on_deactivate(instant=True)
            self.enable_money_target_button.on_deactivate(instant=True)
            self.disable_money_target_button.on_deactivate(instant=True)
            self.on_buy_construction_action(self.construction_type, self.row,
                                            self.entity_number)

        self.logger = getLogger(
            f'root.app.game.map.constructor.view.cell.{construction_type}.{row}'
        )
        self.is_activated = False
        self.parent_viewport = parent_viewport
        self.viewport = Viewport()
        self.construction_type, self.row = construction_type, row
        USER_DB_CURSOR.execute('SELECT current_locale FROM i18n')
        self.current_locale = USER_DB_CURSOR.fetchone()[0]
        self.on_buy_construction_action = on_buy_construction_action
        self.on_set_money_target_action = on_set_money_target_action
        self.on_reset_money_target_action = on_reset_money_target_action
        self.entity_number = None
        self.data = []
        self.screen_resolution = (0, 0)
        self.locked_label = ConstructorLockedLabel(
            parent_viewport=self.viewport)
        self.title_label = None
        self.level_required_label = ConstructorLevelPlaceholderLabel(
            parent_viewport=self.viewport)
        self.previous_entity_required_label = None
        self.environment_required_label = None
        self.unlock_available_label = None
        self.under_construction_description_label = UnderConstructionDescriptionLabel(
            parent_viewport=self.viewport)
        self.enable_money_target_button, self.disable_money_target_button = create_two_state_button(
            SetMoneyTargetButton(on_click_action=on_set_money_target,
                                 parent_viewport=self.viewport),
            ResetMoneyTargetButton(on_click_action=on_reset_money_target,
                                   parent_viewport=self.viewport))
        self.build_button = BuildConstructionButton(
            on_click_action=on_buy_construction, parent_viewport=self.viewport)
        self.buttons = [
            self.enable_money_target_button, self.disable_money_target_button,
            self.build_button
        ]
        USER_DB_CURSOR.execute('''SELECT money FROM game_progress''')
        self.money = USER_DB_CURSOR.fetchone()[0]
        self.money_target_activated = False
        self.opacity = 0
        self.on_window_resize_handlers = [
            self.on_window_resize, self.locked_label.on_window_resize,
            self.level_required_label.on_window_resize,
            self.under_construction_description_label.on_window_resize
        ]
Esempio n. 15
0
class Entry(Bin):
    """A text entry field"""
    __gsignals__ = {
        "on-change": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                      (gobject.TYPE_PYOBJECT, )),
        "on-position-change": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                               (gobject.TYPE_PYOBJECT, )),
    }

    padding = 5

    mouse_cursor = gdk.CursorType.XTERM

    font_desc = "Sans Serif 10"  #: pango.FontDescription to use for the label

    color = "#000"  #: font color
    cursor_color = "#000"  #: cursor color
    selection_color = "#A8C2E0"  #: fill color of the selection region

    def __init__(self,
                 text="",
                 draw_border=True,
                 valid_chars=None,
                 validate_on_type=True,
                 single_paragraph=True,
                 text_formatter=None,
                 alignment=None,
                 font_desc=None,
                 **kwargs):
        Bin.__init__(self, **kwargs)

        self.display_label = graphics.Label(color=self.color)

        self.viewport = Viewport(self.display_label)
        self.viewport.connect("on-render", self.__on_viewport_render)

        self.add_child(self.viewport)

        self.can_focus = True

        self.interactive = True

        self.editable = True

        #: current cursor position
        self.cursor_position = None

        #: start position of the selection
        self.selection_start = 0

        #: end position of the selection
        self.selection_end = 0

        if font_desc is not None:
            self.font_desc = font_desc
        self.display_label.font_desc = self.font_desc

        #: text alignment in the entry
        self.alignment = alignment

        #: if True, a border will be drawn around the input element
        self.draw_border = draw_border

        #self.connect("on-key-press", self.__on_key_press)
        self.connect("on-mouse-down", self.__on_mouse_down)
        self.connect("on-double-click", self.__on_double_click)
        self.connect("on-triple-click", self.__on_triple_click)
        self.connect("on-blur", self.__on_blur)
        self.connect("on-focus", self.__on_focus)

        self.connect_after("on-render", self.__on_render)

        self._scene_mouse_move = None
        self._scene_mouse_up = None
        self._selection_start_position = None
        self._letter_positions = []

        #: a string, function or regexp or valid chars for the input
        #: in case of function, it will receive the string to be tested
        #: as input and expects to receive back a boolean of whether the string
        #: is valid or not
        self.valid_chars = valid_chars

        #: should the content be validate right when typing and invalid version prohibited
        self.validate_on_type = validate_on_type

        #: function to style the entry text - change color and such
        #: the function receives one param - the text, and must return
        #: processed text back. will be using original text if the function
        #: does not return anything.
        #: Note: this function can change only the style, not the actual content
        #: as the latter will mess up text selection because of off-sync between
        #: the label value and what is displayed
        self.text_formatter = text_formatter if text_formatter else self.text_formatter

        #: should the text input support multiple lines
        self.single_paragraph = single_paragraph

        self.update_text(text)
        self._last_good_value = text  # last known good value of the input

    def __setattr__(self, name, val):
        if name == "cursor_position" and not self.editable:
            val = None

        if self.__dict__.get(name, "hamster_graphics_no_value_really") == val:
            return

        if name == "text":
            val = val or ""
            if getattr(self, "text_formatter", None):
                markup = self.text_formatter(
                    val.replace("&",
                                "&amp;").replace("<",
                                                 "&lt;").replace(">", "&gt;"))

            if markup:
                self.display_label.markup = markup
            else:
                self.display_label.text = val

        Bin.__setattr__(self, name, val)

        if name == "text":
            self.emit("on-change", val)

        if name in ("font_desc", "alignment", "single_paragraph", "color"):
            setattr(self.display_label, name, val)

        elif name == "alloc_w" and getattr(self, "overflow",
                                           False) != False and hasattr(
                                               self, "display_label"):
            self.display_label.width = val - self.horizontal_padding

        elif name == "overflow" and val != False and hasattr(
                self, "display_label"):
            if val in (pango.WrapMode.WORD, pango.WrapMode.WORD_CHAR,
                       pango.WrapMode.CHAR):
                self.display_label.wrap = val
                self.display_label.ellipsize = None
            elif val in (pango.EllipsizeMode.START, pango.EllipsizeMode.END):
                self.display_label.wrap = None
                self.display_label.ellipsize = val

        if name == "cursor_position":
            self.emit("on-position-change", val)

    def get_min_size(self):
        return self.min_width or 0, max(
            self.min_height, self.display_label.height + self.vertical_padding)

    def text_formatter(self, text):
        return None

    def test_value(self, text):
        if not self.valid_chars:
            return True
        elif isinstance(self.valid_chars, basestring):
            return set(text) - set(self.valid_chars) == set([])
        elif hasattr(self.valid_chars, '__call__'):
            return self.valid_chars(text)
        else:
            return False

    def get_height_for_width_size(self):
        return self.get_min_size()

    def update_text(self, text):
        """updates the text field value and the last good known value,
        respecting the valid_chars and validate_on_type flags"""
        text = text or ""

        if self.test_value(text):
            self.text = text
            if self.validate_on_type:
                self._last_good_value = text
        elif not self.validate_on_type:
            self.text = text
        else:
            return False

        self.viewport.height = self.display_label.height
        if self.cursor_position is None:
            self.cursor_position = len(text)

        return True

    def _index_to_pos(self, index):
        """give coordinates for the position in text. maps to the
        display_label's pango function"""
        ext = self.display_label._test_layout.index_to_pos(index)
        extents = [
            e / pango.SCALE for e in (ext.x, ext.y, ext.width, ext.height)
        ]
        return extents

    def _xy_to_index(self, x, y):
        """from coordinates caluculate position in text. maps to the
        display_label's pango function"""
        x = x - self.display_label.x - self.viewport.x
        index = self.display_label._test_layout.xy_to_index(
            int(x * pango.SCALE), int(y * pango.SCALE))
        return index[0] + index[1]

    def __on_focus(self, sprite):
        self._last_good_value = self.text

    def __on_blur(self, sprite):
        self._edit_done()

    def _edit_done(self):
        if self.test_value(self.text):
            self._last_good_value = self.text
        else:
            self.update_text(self._last_good_value)

    def __on_mouse_down(self, sprite, event):
        i = self._xy_to_index(event.x, event.y)

        self.selection_start = self.selection_end = self.cursor_position = i
        self._selection_start_position = self.selection_start

        scene = self.get_scene()
        if not self._scene_mouse_up:
            self._scene_mouse_up = scene.connect("on-mouse-up",
                                                 self._on_scene_mouse_up)
            self._scene_mouse_move = scene.connect("on-mouse-move",
                                                   self._on_scene_mouse_move)

    def __on_double_click(self, sprite, event):
        # find the word
        cursor = self.cursor_position
        self.selection_start = self.text.rfind(" ", 0, cursor) + 1

        end = self.text.find(" ", cursor)
        self.cursor_position = self.selection_end = end if end > 0 else len(
            self.text)

    def __on_triple_click(self, sprite, event):
        self.selection_start = 0
        self.cursor_position = self.selection_end = len(self.text)

    def _on_scene_mouse_up(self, scene, event):
        scene.disconnect(self._scene_mouse_up)
        scene.disconnect(self._scene_mouse_move)
        self._scene_mouse_up = self._scene_mouse_move = None

    def _on_scene_mouse_move(self, scene, event):
        if self.focused == False:
            return

        # now try to derive cursor position
        x, y = self.display_label.from_scene_coords(event.x, event.y)
        x = x + self.display_label.x + self.viewport.x
        i = self._xy_to_index(x, y)

        self.cursor_position = i
        if self.cursor_position < self._selection_start_position:
            self.selection_start = self.cursor_position
            self.selection_end = self._selection_start_position
        else:
            self.selection_start = self._selection_start_position
            self.selection_end = self.cursor_position

    def _get_iter(self, index=0):
        """returns iterator that has been run till the specified index"""
        iter = self.display_label._test_layout.get_iter()
        for i in range(index):
            iter.next_char()
        return iter

    def _do_key_press(self, event):
        """responding to key events"""
        key = event.keyval
        shift = event.state & gdk.ModifierType.SHIFT_MASK
        control = event.state & gdk.ModifierType.CONTROL_MASK

        if not self.editable:
            return

        def emit_and_return():
            self.emit("on-key-press", event)
            return

        if self.single_paragraph and key == gdk.KEY_Return:
            self._edit_done()
            return emit_and_return()

        self._letter_positions = []

        if key == gdk.KEY_Left:
            if shift and self.cursor_position == 0:
                return emit_and_return()

            if control:
                self.cursor_position = self.text[:self.cursor_position].rstrip(
                ).rfind(" ") + 1
            else:
                self.cursor_position -= 1

            if shift:
                if self.cursor_position < self.selection_start:
                    self.selection_start = self.cursor_position
                else:
                    self.selection_end = self.cursor_position
            elif self.selection_start != self.selection_end:
                self.cursor_position = self.selection_start

        elif key == gdk.KEY_Right:
            if shift and self.cursor_position == len(self.text):
                return emit_and_return()

            if control:
                prev_pos = self.cursor_position
                self.cursor_position = self.text[self.cursor_position:].lstrip(
                ).find(" ")
                if self.cursor_position == -1:
                    self.cursor_position = len(self.text)
                else:
                    self.cursor_position += prev_pos + 1
            else:
                self.cursor_position += 1

            if shift:
                if self.cursor_position > self.selection_end:
                    self.selection_end = self.cursor_position
                else:
                    self.selection_start = self.cursor_position
            elif self.selection_start != self.selection_end:
                self.cursor_position = self.selection_end

        elif key == gdk.KEY_Up and self.single_paragraph == False:
            iter = self._get_iter(self.cursor_position)

            if str(iter.get_line_readonly()) != str(
                    self.display_label._test_layout.get_line_readonly(0)):
                char_pos = iter.get_char_extents().x
                char_line = str(iter.get_line_readonly())

                # now we run again to run until previous line
                prev_iter, iter = self._get_iter(), self._get_iter()
                prev_line = None
                while str(iter.get_line_readonly()) != char_line:
                    if str(prev_line) != str(iter.get_line_readonly()):
                        prev_iter = iter.copy()
                        prev_line = iter.get_line_readonly()
                    iter.next_char()

                index = prev_iter.get_line_readonly().x_to_index(
                    char_pos - prev_iter.get_char_extents().x)
                index = index[1] + index[2]

                self.cursor_position = index

                if shift:
                    if self.cursor_position < self.selection_start:
                        self.selection_start = self.cursor_position
                    else:
                        self.selection_end = self.cursor_position
                elif self.selection_start != self.selection_end:
                    self.cursor_position = self.selection_start

        elif key == gdk.KEY_Down and self.single_paragraph == False:
            iter = self._get_iter(self.cursor_position)
            char_pos = iter.get_char_extents().x

            if iter.next_line():
                index = iter.get_line_readonly().x_to_index(
                    char_pos - iter.get_char_extents().x)
                index = index[1] + index[2]
                self.cursor_position = index

                if shift:
                    if self.cursor_position > self.selection_end:
                        self.selection_end = self.cursor_position
                    else:
                        self.selection_start = self.cursor_position
                elif self.selection_start != self.selection_end:
                    self.cursor_position = self.selection_end

        elif key == gdk.KEY_Home:
            if self.single_paragraph or control:
                self.cursor_position = 0
                if shift:
                    self.selection_end = self.selection_start
                    self.selection_start = self.cursor_position
            else:
                iter = self._get_iter(self.cursor_position)
                line = str(iter.get_line_readonly())

                # find the start of the line
                iter = self._get_iter()
                while str(iter.get_line_readonly()) != line:
                    iter.next_char()
                self.cursor_position = iter.get_index()

                if shift:
                    start_iter = self._get_iter(self.selection_start)
                    end_iter = self._get_iter(self.selection_end)
                    if str(start_iter.get_line_readonly()) == str(
                            end_iter.get_line_readonly()):
                        self.selection_end = self.selection_start
                        self.selection_start = self.cursor_position
                    else:
                        if self.cursor_position < self.selection_start:
                            self.selection_start = self.cursor_position
                        else:
                            self.selection_end = self.cursor_position

        elif key == gdk.KEY_End:
            if self.single_paragraph or control:
                self.cursor_position = len(self.text)
                if shift:
                    self.selection_start = self.selection_end
                    self.selection_end = self.cursor_position
            else:
                iter = self._get_iter(self.cursor_position)

                #find the end of the line
                line = str(iter.get_line_readonly())
                prev_iter = None

                while str(iter.get_line_readonly()) == line:
                    prev_iter = iter.copy()
                    moved = iter.next_char()
                    if not moved:
                        prev_iter = iter
                        break

                self.cursor_position = prev_iter.get_index()

                if shift:
                    start_iter = self._get_iter(self.selection_start)
                    end_iter = self._get_iter(self.selection_end)
                    if str(start_iter.get_line_readonly()) == str(
                            end_iter.get_line_readonly()):
                        self.selection_start = self.selection_end
                        self.selection_end = self.cursor_position
                    else:
                        if self.cursor_position > self.selection_end:
                            self.selection_end = self.cursor_position
                        else:
                            self.selection_start = self.cursor_position

        elif key == gdk.KEY_BackSpace:
            if self.selection_start != self.selection_end:
                if not self.update_text(self.text[:self.selection_start] +
                                        self.text[self.selection_end:]):
                    return emit_and_return()
            elif self.cursor_position > 0:
                if not self.update_text(self.text[:self.cursor_position - 1] +
                                        self.text[self.cursor_position:]):
                    return emit_and_return()
                self.cursor_position -= 1

        elif key == gdk.KEY_Delete:
            if self.selection_start != self.selection_end:
                if not self.update_text(self.text[:self.selection_start] +
                                        self.text[self.selection_end:]):
                    return emit_and_return()
            elif self.cursor_position < len(self.text):
                if not self.update_text(self.text[:self.cursor_position] +
                                        self.text[self.cursor_position + 1:]):
                    return emit_and_return()
        elif key == gdk.KEY_Escape:
            return emit_and_return()

        #prevent garbage from common save file mneumonic
        elif control and key in (gdk.KEY_s, gdk.KEY_S):
            return emit_and_return()

        # copying and pasting
        elif control and key in (gdk.KEY_c, gdk.KEY_C):  # copy
            clipboard = gtk.Clipboard()
            clipboard.set_text(
                self.text[self.selection_start:self.selection_end])
            return emit_and_return()

        elif control and key in (gdk.KEY_x, gdk.KEY_X):  # cut
            text = self.text[self.selection_start:self.selection_end]
            if self.update_text(self.text[:self.selection_start] +
                                self.text[self.selection_end:]):
                clipboard = gtk.Clipboard()
                clipboard.set_text(text)

        elif control and key in (gdk.KEY_v, gdk.KEY_V):  # paste
            clipboard = gtk.Clipboard()
            clipboard.request_text(self._on_clipboard_text)
            return emit_and_return()

        elif control and key in (gdk.KEY_a, gdk.KEY_A):  # select all
            self.selection_start = 0
            self.cursor_position = self.selection_end = len(self.text)
            return emit_and_return()

        # normal letters
        elif event.string:
            if self.update_text(self.text[:self.selection_start] +
                                event.string + self.text[self.selection_end:]):
                self.cursor_position = self.selection_start + 1
                self.selection_start = self.selection_end = self.cursor_position
            return emit_and_return()
        else:  # in case of anything else just go home
            return emit_and_return()

        self.cursor_position = min(max(0, self.cursor_position),
                                   len(self.text))
        self.selection_start = min(max(0, self.selection_start),
                                   len(self.text))
        self.selection_end = min(max(0, self.selection_end), len(self.text))

        if shift == False:
            self.selection_start = self.selection_end = self.cursor_position

        return emit_and_return()

    def _on_clipboard_text(self, clipboard, text, data):
        if self.update_text(self.text[:self.selection_start] + text +
                            self.text[self.selection_end:]):
            self.selection_start = self.selection_end = self.cursor_position = self.selection_start + len(
                text)

    def do_render(self):
        self.graphics.set_line_style(width=1)
        self.graphics.rectangle(0.5, -1.5, self.width, self.height + 2, 3)
        if self.draw_border:
            self.graphics.fill_preserve("#fff")
            self.graphics.stroke("#aaa")

    def __on_render(self, sprite):
        self.graphics.rectangle(0, 0, self.width, self.height)
        self.graphics.new_path()

        if self.cursor_position is not None:
            cur_x, cur_y, cur_w, cur_h = self._index_to_pos(
                self.cursor_position)
            if self.display_label.x + cur_x > self.viewport.width:
                self.display_label.x = min(
                    0, self.viewport.width -
                    cur_x)  # cursor visible at the right side
            elif self.display_label.x + cur_x < 0:
                self.display_label.x -= (self.display_label.x + cur_x
                                         )  # cursor visible at the left side
            elif self.display_label.x < 0 and self.display_label.x + self.display_label.width < self.viewport.width:
                self.display_label.x = min(
                    self.viewport.width - self.display_label.width, 0)

        # align the label within the entry
        if self.display_label.width < self.viewport.width:
            if self.alignment == pango.Alignment.RIGHT:
                self.display_label.x = self.viewport.width - self.display_label.width
            elif self.alignment == pango.Alignment.CENTER:
                self.display_label.x = (self.viewport.width -
                                        self.display_label.width) / 2

        #if self.single_paragraph:
        #    self.display_label.y = (self.viewport.height - self.display_label.height) / 2.0
        self.viewport._sprite_dirty = True  # so that we get the cursor drawn

    def __on_viewport_render(self, viewport):
        if self.focused == False:
            return

        if self.cursor_position is None:
            return

        cur_x, cur_y, cur_w, cur_h = self._index_to_pos(self.cursor_position)
        cur_x = cur_x + self.display_label.x
        cur_y += self.display_label.y

        viewport.graphics.move_to(cur_x + 0.5, cur_y)
        viewport.graphics.line_to(cur_x + 0.5, cur_y + cur_h)
        viewport.graphics.stroke(self.cursor_color)

        if self.selection_start == self.selection_end:
            return  # all done!

        start_x, start_y, start_w, start_h = self._index_to_pos(
            self.selection_start)
        end_x, end_y, end_w, end_h = self._index_to_pos(self.selection_end)

        iter = self._get_iter(self.selection_start)

        char_exts = iter.get_char_extents()
        cur_x, cur_y = char_exts.x / pango.SCALE, char_exts.y / pango.SCALE + self.display_label.y

        cur_line = None
        for i in range(self.selection_end - self.selection_start):
            prev_iter = pango.LayoutIter.copy(iter)
            iter.next_char()

            line = iter.get_line_readonly()
            if str(cur_line) != str(
                    line):  # can't compare layout lines for some reason
                exts = prev_iter.get_char_extents()
                char_exts = [
                    ext / pango.SCALE
                    for ext in (exts.x, exts.y, exts.width, exts.height)
                ]
                viewport.graphics.rectangle(
                    cur_x + self.display_label.x, cur_y,
                    char_exts[0] + char_exts[2] - cur_x, char_exts[3])

                char_exts = iter.get_char_extents()
                cur_x, cur_y = char_exts.x / pango.SCALE, char_exts.y / pango.SCALE + self.display_label.y

            cur_line = line

        exts = iter.get_char_extents()
        char_exts = [
            ext / pango.SCALE
            for ext in (exts.x, exts.y, exts.width, exts.height)
        ]

        viewport.graphics.rectangle(
            cur_x + self.display_label.x, cur_y, char_exts[0] - cur_x,
            self.display_label.y + char_exts[1] - cur_y + char_exts[3])
        viewport.graphics.fill(self.selection_color)
Esempio n. 16
0
class Notebook(Box):
    """Container that allows grouping children in tab pages"""

    __gsignals__ = {
        "on-tab-change": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
    }

    #: class to use for constructing the overflow buttons that appear on overflow
    scroll_buttons_class = ScrollButton

    #: class for the wrapping container
    tabbox_class = HBox

    #: class for the pages container
    pages_container_class = Container


    def __init__(self, labels = None, tab_position="top", tab_spacing = 0,
                 scroll_position = None, show_scroll = "auto", scroll_selects_tab = True, **kwargs):
        Box.__init__(self, horizontal=False, spacing=0, **kwargs)

        #: list of tabs in the order of appearance
        self.tabs = []

        #: list of pages in the order of appearance
        self.pages = []

        #: container of the pages
        self.pages_container = self.pages_container_class(padding=1)

        #: container of tabs. useful if you want to adjust padding/placement
        self.tabs_container = Group(fill=False, spacing=tab_spacing)
        self.tabs_container.on_mouse_over = lambda button: False  # ignore select-on-drag

        # viewport so that tabs don't go out of their area
        self._tabs_viewport = Viewport()
        self._tabs_viewport.get_min_size = self._tabs_viewport_get_min_size
        self._tabs_viewport.resize_children = self._tabs_viewport_resize_children
        self._tabs_viewport.add_child(self.tabs_container)

        #: wether scroll buttons should select next/previos tab or show the
        #: next/previos tab out of the view
        self.scroll_selects_tab = scroll_selects_tab

        #: container for custom content before tabs
        self.before_tabs = HBox(expand=False)

        #: container for custom content after tabs
        self.after_tabs = HBox(expand=False)

        #: button to scroll tabs back
        self.tabs_back = self.scroll_buttons_class("left",
                                                   expand=False,
                                                   visible=False,
                                                   enabled=False,
                                                   repeat_down_delay = 150)
        self.tabs_back.connect("on-mouse-down", self.on_back_press)

        #: button to scroll tabs forward
        self.tabs_forward = self.scroll_buttons_class("right",
                                                      expand=False,
                                                      visible=False,
                                                      enabled=False,
                                                      repeat_down_delay = 150)
        self.tabs_forward.connect("on-mouse-down", self.on_forward_press)


        #: the wrapping container that holds also the scroll buttons and everyting
        self.tabbox = self.tabbox_class(expand = False, expand_vert = False)
        self.tabbox.get_min_size = self.tabbox_get_min_size
        self.tabbox.get_height_for_width_size = self.tabbox_get_height_for_width_size


        self.tabbox.add_child(self.before_tabs, self.tabs_back,
                              self._tabs_viewport,
                              self.tabs_forward, self.after_tabs)

        #: current page
        self.current_page = 0

        #: tab position: top, right, bottom, left and combinations: "top-right", "left-bottom", etc.
        self.tab_position = tab_position


        for label in labels or []:
            self.add_page(label)

        #: where to place the scroll buttons on tab overflow. one of "start"
        #: (both at the start), "end" (both at the end) or "around" (on left
        #: and right of the tabs)
        self.scroll_position = scroll_position

        #: determines when to show scroll buttons. True for always, False for
        #: never, "auto" for auto appearing and disappearing, and
        #: "auto_invisible" for going transparent instead of disappearing
        #: (the latter avoids tab toggle)
        self.show_scroll = show_scroll



    def __setattr__(self, name, val):
        if name == "tab_spacing":
            self.tabs_container.spacing = val
        else:
            if name == "current_page":
                val = self.find_page(val)

            if self.__dict__.get(name, "hamster_graphics_no_value_really") == val:
                return
            Box.__setattr__(self, name, val)

            if name == "tab_position" and hasattr(self, "tabs_container"):
                self.tabs_container.x = 0
                self._position_contents()

            elif name == "scroll_position":
                # reorder sprites based on scroll position
                if val == "start":
                    sprites = [self.before_tabs, self.tabs_back, self.tabs_forward, self._tabs_viewport, self.after_tabs]
                elif val == "end":
                    sprites = [self.before_tabs, self._tabs_viewport, self.tabs_back, self.tabs_forward, self.after_tabs]
                else:
                    sprites = [self.before_tabs, self.tabs_back, self._tabs_viewport, self.tabs_forward, self.after_tabs]
                self.tabbox.sprites = sprites
            elif name == "current_page":
                self._select_current_page()


    def add_page(self, tab, contents = None, index = None):
        """inserts a new page with the given label
        will perform insert if index is specified. otherwise will append the
        new tab to the end.
        tab can be either a string or a widget. if it is a string, a
        ui.NootebookTab will be created.

        Returns: added page and tab
        """
        if isinstance(tab, basestring):
            tab = NotebookTab(tab)

        tab.attachment = "bottom" if self.tab_position.startswith("bottom") else "top"
        self.tabs_container.connect_child(tab, "on-mouse-down", self.on_tab_down)

        page = Container(contents, visible=False)
        page.tab = tab
        self.pages_container.connect_child(page, "on-render", self.on_page_render)

        if index is None:
            self.tabs.append(tab)
            self.pages.append(page)
            self.tabs_container.add_child(tab)
            self.pages_container.add_child(page)
        else:
            self.tabs.insert(index, tab)
            self.pages.insert(index, page)
            self.tabs_container.insert(index, tab)
            self.pages_container.insert(index, tab)


        self.current_page = self.current_page or page
        self._position_contents()

        if self.get_scene():
            self.tabs_container.queue_resize()
            self.tabbox.resize_children()

        return page, tab


    def remove_page(self, page):
        """remove given page. can also pass in the page index"""
        page = self.find_page(page)
        if not page:
            return

        idx = self.pages.index(page)

        self.pages_container.remove_child(page)
        del self.pages[idx]

        self.tabs_container.remove_child(self.tabs[idx])
        del self.tabs[idx]

        if page == self.current_page:
            self.current_page = idx

        self.tabs_container.resize_children()
        self._position_contents()


    def find_page(self, page):
        """find page by index, tab label or tab object"""
        if not self.pages:
            return None

        if page in self.pages:
            return page
        elif isinstance(page, int):
            page = min(len(self.pages)-1, max(page, 0))
            return self.pages[page]
        elif isinstance(page, basestring) or isinstance(page, NotebookTab):
            for i, tab in enumerate(self.tabs):
                if tab == page or tab.label == page:
                    found_page = self.pages[i]
                    return found_page
        return None

    def _select_current_page(self):
        self.emit("on-tab-change", self.current_page)

        if not self.current_page:
            return

        self.tabs[self.pages.index(self.current_page)].toggle()
        for page in self.pages:
            page.visible = page == self.current_page

        self.current_page.grab_focus()


    def scroll_to_tab(self, tab):
        """scroll the tab list so that the specified tab is visible
        you can pass in the tab object, index or label"""
        if isinstance(tab, int):
            tab = self.tabs[tab]

        if isinstance(tab, basestring):
            for target_tab in self.tabs:
                if target_tab.label == tab:
                    tab = target_tab
                    break

        if self.tabs_container.x + tab.x < 0:
            self.tabs_container.x = -tab.x
        elif self.tabs_container.x + tab.x + tab.width > self._tabs_viewport.width:
            self.tabs_container.x = -(tab.x + tab.width - self._tabs_viewport.width) - 1
        self._position_tabs()


    """resizing and positioning"""
    def resize_children(self):
        Box.resize_children(self)

        pos = self.tab_position
        horizontal = pos.startswith("right") or pos.startswith("left")
        if horizontal:
            self.tabbox.alloc_w, self.tabbox.alloc_h = self.tabbox.alloc_h, self.tabbox.alloc_w

        if pos.startswith("right"):
            self.tabbox.x += self.tabbox.height
        elif pos.startswith("left"):
            self.tabbox.y += self.tabbox.width


        # show/hide thes croll buttons
        # doing it here to avoid recursion as changing visibility calls parent resize
        self.tabs_back.visible = self.tabs_forward.visible = self.show_scroll in (True, "auto_invisible")
        self.tabbox.resize_children()
        self.tabs_container.resize_children()


        if self.show_scroll == "auto_invisible":
            self.tabs_back.visible = self.tabs_forward.visible = True
            if self.tabs_container.width < self._tabs_viewport.width:
                self.tabs_back.opacity = self.tabs_forward.opacity = 0
            else:
                self.tabs_back.opacity = self.tabs_forward.opacity = 1

        else:
            self.tabs_back.opacity = self.tabs_forward.opacity = 1
            self.tabs_back.visible = self.tabs_forward.visible = self.show_scroll is True or \
                                                                (self.show_scroll == "auto" and \
                                                                 self.tabs_container.width > self._tabs_viewport.width)

        self.tabbox.resize_children()
        self._position_tabs()



    def tabbox_get_min_size(self):
        w, h = HBox.get_min_size(self.tabbox)
        return h, h

    def tabbox_get_height_for_width_size(self):
        w, h = HBox.get_min_size(self.tabbox)

        if self.tab_position.startswith("right") or self.tab_position.startswith("left"):
            w, h = h, w

        return w, h

    def _tabs_viewport_get_min_size(self):
        # viewport has no demands on size, so we ask the tabs container
        # when positioned on top, tell that we need at least the height
        # when on the side tell that we need at least the width
        w, h = self.tabs_container.get_min_size()
        return 50, h


    def _tabs_viewport_resize_children(self):
        # allow x_align to take effect only if tabs fit.
        x = max(self.tabs_container.x, self._tabs_viewport.width - self.tabs_container.width - 1)

        Bin.resize_children(self._tabs_viewport)

        if self.tabs_container.width > self._tabs_viewport.width:
            self.tabs_container.x = x

        self._position_tabs()


    """utilities"""
    def _position_tabs(self):
        if self.scroll_selects_tab and self.current_page:
            tab = self.current_page.tab
            if self.tabs_container.x + tab.x + tab.width > self._tabs_viewport.width:
                self.tabs_container.x = -(tab.x + tab.width - self._tabs_viewport.width)
            elif self.tabs_container.x + tab.x < 0:
                self.tabs_container.x = -tab.x


        # find first good tab if we all don't fit
        if self.tabs_container.width > self._tabs_viewport.width:
            for tab in self.tabs:
                if tab.x + self.tabs_container.x >= 0:
                    self.tabs_container.x = -tab.x
                    break

        # update opacity so we are not showing partial tabs
        for tab in self.tabs:
            if self.tabs_container.x + tab.x < 0 or self.tabs_container.x + tab.x + tab.width > self._tabs_viewport.width:
                tab.opacity = 0
            else:
                tab.opacity = 1


        # set scroll buttons clickable
        if self.scroll_selects_tab:
            self.tabs_back.enabled = self.current_page and self.pages.index(self.current_page) > 0
            self.tabs_forward.enabled = self.current_page and self.pages.index(self.current_page) < len(self.pages) - 1
        else:
            self.tabs_back.enabled = self.tabs_container.x  < -self.tabs_container.padding_left
            self.tabs_forward.enabled = self.tabs_container.x + self.tabs_container.width > self._tabs_viewport.width


    def _position_contents(self):
        attachment, alignment = self.tab_position or "top", "left"
        if "-" in self.tab_position:
            attachment, alignment = self.tab_position.split("-")

        self.orient_horizontal = attachment in ("right", "left")

        if alignment == "center":
            self.tabs_container.x_align = 0.5
        elif alignment in ("right", "bottom"):
            self.tabs_container.x_align = 1
        else:
            self.tabs_container.x_align = 0

        # on left side the rotation is upside down
        if attachment == "left":
            self.tabs_container.x_align = 1 - self.tabs_container.x_align

        if attachment == "bottom":
            self.tabs_container.y_align = 0
        else:
            self.tabs_container.y_align = 1

        for tab in self.tabs:
            tab.attachment = attachment

        self.clear()
        if attachment == "right":
            self.add_child(self.pages_container, self.tabbox)
            self.tabbox.rotation = math.pi / 2
        elif attachment == "left":
            self.add_child(self.tabbox, self.pages_container)
            self.tabbox.rotation = -math.pi / 2
        elif attachment == "bottom":
            self.add_child(self.pages_container, self.tabbox)
            self.tabbox.rotation = 0
        else: # defaults to top
            self.add_child(self.tabbox, self.pages_container)
            self.tabbox.rotation = 0


        for tab in self.tabs:
            tab.pivot_x = tab.width / 2
            tab.pivot_y = tab.height / 2

            tab.container.pivot_x = tab.container.width / 2
            tab.container.pivot_y = tab.container.height / 2
            if attachment == "bottom":
                tab.rotation = math.pi
                tab.container.rotation = math.pi
            else:
                tab.rotation = 0
                tab.container.rotation = 0


            if tab.force_vertical_image and tab.image:
                tab.image.pivot_x = tab.image.width / 2
                tab.image.pivot_y = tab.image.height / 2

                if attachment == "right":
                    tab.image.rotation = -math.pi / 2
                elif attachment == "left":
                    tab.image.rotation = math.pi / 2
                else:
                    tab.image.rotation = 0

        self.queue_resize()


    """mouse events"""
    def on_back_press(self, button, event):
        if self.scroll_selects_tab:
            if self.pages.index(self.current_page) > 0:
                self.current_page = self.pages.index(self.current_page) - 1
        else:
            # find the first elem before 0:
            for tab in reversed(self.tabs):
                if self.tabs_container.x + tab.x < 0:
                    self.tabs_container.x = -tab.x
                    break
        self._position_tabs()


    def on_forward_press(self, button, event):
        if self.scroll_selects_tab:
            if self.pages.index(self.current_page) < len(self.pages):
                self.current_page = self.pages.index(self.current_page) + 1
        else:
            if self.tabs_container.x + self.tabs_container.width > self._tabs_viewport.width:
                # find the first which doesn't fit:
                found = None
                for tab in self.tabs:
                    if self.tabs_container.x + tab.x + tab.width > self._tabs_viewport.width:
                        found = True
                        break

                if found:
                    self.tabs_container.x = -(tab.x + tab.width - self._tabs_viewport.width) - 1
            else:
                self.tabs_container.x = -(self.tabs_container.width - self._tabs_viewport.width)
        self._position_tabs()

    def on_tab_down(self, tab, event):
        self.current_page = tab


    """rendering"""
    def on_page_render(self, page):
        page.graphics.rectangle(0, 0, page.width, page.height)
        page.graphics.clip()

    def do_render(self):
        self.graphics.set_line_style(width = 1)

        x, y, w, h = (self.pages_container.x + 0.5,
                      self.pages_container.y + 0.5,
                      self.pages_container.width-1,
                      self.pages_container.height-1)

        self.graphics.rectangle(x, y, w, h)
        self.graphics.fill_stroke("#fafafa", "#999")
Esempio n. 17
0
    def __init__(self,
                 labels=None,
                 tab_position="top",
                 tab_spacing=0,
                 scroll_position=None,
                 show_scroll="auto",
                 scroll_selects_tab=True,
                 **kwargs):
        Box.__init__(self, horizontal=False, spacing=0, **kwargs)

        #: list of tabs in the order of appearance
        self.tabs = []

        #: list of pages in the order of appearance
        self.pages = []

        #: container of the pages
        self.pages_container = self.pages_container_class(padding=1)

        #: container of tabs. useful if you want to adjust padding/placement
        self.tabs_container = Group(fill=False, spacing=tab_spacing)
        self.tabs_container.on_mouse_over = lambda button: False  # ignore select-on-drag

        # viewport so that tabs don't go out of their area
        self._tabs_viewport = Viewport()
        self._tabs_viewport.get_min_size = self._tabs_viewport_get_min_size
        self._tabs_viewport.resize_children = self._tabs_viewport_resize_children
        self._tabs_viewport.add_child(self.tabs_container)

        #: wether scroll buttons should select next/previos tab or show the
        #: next/previos tab out of the view
        self.scroll_selects_tab = scroll_selects_tab

        #: container for custom content before tabs
        self.before_tabs = HBox(expand=False)

        #: container for custom content after tabs
        self.after_tabs = HBox(expand=False)

        #: button to scroll tabs back
        self.tabs_back = self.scroll_buttons_class("left",
                                                   expand=False,
                                                   visible=False,
                                                   enabled=False,
                                                   repeat_down_delay=150)
        self.tabs_back.connect("on-mouse-down", self.on_back_press)

        #: button to scroll tabs forward
        self.tabs_forward = self.scroll_buttons_class("right",
                                                      expand=False,
                                                      visible=False,
                                                      enabled=False,
                                                      repeat_down_delay=150)
        self.tabs_forward.connect("on-mouse-down", self.on_forward_press)

        #: the wrapping container that holds also the scroll buttons and everyting
        self.tabbox = self.tabbox_class(expand=False, expand_vert=False)
        self.tabbox.get_min_size = self.tabbox_get_min_size
        self.tabbox.get_height_for_width_size = self.tabbox_get_height_for_width_size

        self.tabbox.add_child(self.before_tabs, self.tabs_back,
                              self._tabs_viewport, self.tabs_forward,
                              self.after_tabs)

        #: current page
        self.current_page = 0

        #: tab position: top, right, bottom, left and combinations: "top-right", "left-bottom", etc.
        self.tab_position = tab_position

        for label in labels or []:
            self.add_page(label)

        #: where to place the scroll buttons on tab overflow. one of "start"
        #: (both at the start), "end" (both at the end) or "around" (on left
        #: and right of the tabs)
        self.scroll_position = scroll_position

        #: determines when to show scroll buttons. True for always, False for
        #: never, "auto" for auto appearing and disappearing, and
        #: "auto_invisible" for going transparent instead of disappearing
        #: (the latter avoids tab toggle)
        self.show_scroll = show_scroll
Esempio n. 18
0
class Notebook(Box):
    """Container that allows grouping children in tab pages"""

    __gsignals__ = {
        "on-tab-change": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                          (gobject.TYPE_PYOBJECT, )),
    }

    #: class to use for constructing the overflow buttons that appear on overflow
    scroll_buttons_class = ScrollButton

    #: class for the wrapping container
    tabbox_class = HBox

    #: class for the pages container
    pages_container_class = Container

    def __init__(self,
                 labels=None,
                 tab_position="top",
                 tab_spacing=0,
                 scroll_position=None,
                 show_scroll="auto",
                 scroll_selects_tab=True,
                 **kwargs):
        Box.__init__(self, horizontal=False, spacing=0, **kwargs)

        #: list of tabs in the order of appearance
        self.tabs = []

        #: list of pages in the order of appearance
        self.pages = []

        #: container of the pages
        self.pages_container = self.pages_container_class(padding=1)

        #: container of tabs. useful if you want to adjust padding/placement
        self.tabs_container = Group(fill=False, spacing=tab_spacing)
        self.tabs_container.on_mouse_over = lambda button: False  # ignore select-on-drag

        # viewport so that tabs don't go out of their area
        self._tabs_viewport = Viewport()
        self._tabs_viewport.get_min_size = self._tabs_viewport_get_min_size
        self._tabs_viewport.resize_children = self._tabs_viewport_resize_children
        self._tabs_viewport.add_child(self.tabs_container)

        #: wether scroll buttons should select next/previos tab or show the
        #: next/previos tab out of the view
        self.scroll_selects_tab = scroll_selects_tab

        #: container for custom content before tabs
        self.before_tabs = HBox(expand=False)

        #: container for custom content after tabs
        self.after_tabs = HBox(expand=False)

        #: button to scroll tabs back
        self.tabs_back = self.scroll_buttons_class("left",
                                                   expand=False,
                                                   visible=False,
                                                   enabled=False,
                                                   repeat_down_delay=150)
        self.tabs_back.connect("on-mouse-down", self.on_back_press)

        #: button to scroll tabs forward
        self.tabs_forward = self.scroll_buttons_class("right",
                                                      expand=False,
                                                      visible=False,
                                                      enabled=False,
                                                      repeat_down_delay=150)
        self.tabs_forward.connect("on-mouse-down", self.on_forward_press)

        #: the wrapping container that holds also the scroll buttons and everyting
        self.tabbox = self.tabbox_class(expand=False, expand_vert=False)
        self.tabbox.get_min_size = self.tabbox_get_min_size
        self.tabbox.get_height_for_width_size = self.tabbox_get_height_for_width_size

        self.tabbox.add_child(self.before_tabs, self.tabs_back,
                              self._tabs_viewport, self.tabs_forward,
                              self.after_tabs)

        #: current page
        self.current_page = 0

        #: tab position: top, right, bottom, left and combinations: "top-right", "left-bottom", etc.
        self.tab_position = tab_position

        for label in labels or []:
            self.add_page(label)

        #: where to place the scroll buttons on tab overflow. one of "start"
        #: (both at the start), "end" (both at the end) or "around" (on left
        #: and right of the tabs)
        self.scroll_position = scroll_position

        #: determines when to show scroll buttons. True for always, False for
        #: never, "auto" for auto appearing and disappearing, and
        #: "auto_invisible" for going transparent instead of disappearing
        #: (the latter avoids tab toggle)
        self.show_scroll = show_scroll

    def __setattr__(self, name, val):
        if name == "tab_spacing":
            self.tabs_container.spacing = val
        else:
            if name == "current_page":
                val = self.find_page(val)

            if self.__dict__.get(name,
                                 "hamster_graphics_no_value_really") == val:
                return
            Box.__setattr__(self, name, val)

            if name == "tab_position" and hasattr(self, "tabs_container"):
                self.tabs_container.x = 0
                self._position_contents()

            elif name == "scroll_position":
                # reorder sprites based on scroll position
                if val == "start":
                    sprites = [
                        self.before_tabs, self.tabs_back, self.tabs_forward,
                        self._tabs_viewport, self.after_tabs
                    ]
                elif val == "end":
                    sprites = [
                        self.before_tabs, self._tabs_viewport, self.tabs_back,
                        self.tabs_forward, self.after_tabs
                    ]
                else:
                    sprites = [
                        self.before_tabs, self.tabs_back, self._tabs_viewport,
                        self.tabs_forward, self.after_tabs
                    ]
                self.tabbox.sprites = sprites
            elif name == "current_page":
                self._select_current_page()

    def add_page(self, tab, contents=None, index=None):
        """inserts a new page with the given label
        will perform insert if index is specified. otherwise will append the
        new tab to the end.
        tab can be either a string or a widget. if it is a string, a
        ui.NootebookTab will be created.

        Returns: added page and tab
        """
        if isinstance(tab, basestring):
            tab = NotebookTab(tab)

        tab.attachment = "bottom" if self.tab_position.startswith(
            "bottom") else "top"
        self.tabs_container.connect_child(tab, "on-mouse-down",
                                          self.on_tab_down)

        page = Container(contents, visible=False)
        page.tab = tab
        self.pages_container.connect_child(page, "on-render",
                                           self.on_page_render)

        if index is None:
            self.tabs.append(tab)
            self.pages.append(page)
            self.tabs_container.add_child(tab)
            self.pages_container.add_child(page)
        else:
            self.tabs.insert(index, tab)
            self.pages.insert(index, page)
            self.tabs_container.insert(index, tab)
            self.pages_container.insert(index, tab)

        self.current_page = self.current_page or page
        self._position_contents()

        if self.get_scene():
            self.tabs_container.queue_resize()
            self.tabbox.resize_children()

        return page, tab

    def remove_page(self, page):
        """remove given page. can also pass in the page index"""
        page = self.find_page(page)
        if not page:
            return

        idx = self.pages.index(page)

        self.pages_container.remove_child(page)
        del self.pages[idx]

        self.tabs_container.remove_child(self.tabs[idx])
        del self.tabs[idx]

        if page == self.current_page:
            self.current_page = idx

        self.tabs_container.resize_children()
        self._position_contents()

    def find_page(self, page):
        """find page by index, tab label or tab object"""
        if not self.pages:
            return None

        if page in self.pages:
            return page
        elif isinstance(page, int):
            page = min(len(self.pages) - 1, max(page, 0))
            return self.pages[page]
        elif isinstance(page, basestring) or isinstance(page, NotebookTab):
            for i, tab in enumerate(self.tabs):
                if tab == page or tab.label == page:
                    found_page = self.pages[i]
                    return found_page
        return None

    def _select_current_page(self):
        self.emit("on-tab-change", self.current_page)

        if not self.current_page:
            return

        self.tabs[self.pages.index(self.current_page)].toggle()
        for page in self.pages:
            page.visible = page == self.current_page

        self.current_page.grab_focus()

    def scroll_to_tab(self, tab):
        """scroll the tab list so that the specified tab is visible
        you can pass in the tab object, index or label"""
        if isinstance(tab, int):
            tab = self.tabs[tab]

        if isinstance(tab, basestring):
            for target_tab in self.tabs:
                if target_tab.label == tab:
                    tab = target_tab
                    break

        if self.tabs_container.x + tab.x < 0:
            self.tabs_container.x = -tab.x
        elif self.tabs_container.x + tab.x + tab.width > self._tabs_viewport.width:
            self.tabs_container.x = -(tab.x + tab.width -
                                      self._tabs_viewport.width) - 1
        self._position_tabs()

    """resizing and positioning"""

    def resize_children(self):
        Box.resize_children(self)

        pos = self.tab_position
        horizontal = pos.startswith("right") or pos.startswith("left")
        if horizontal:
            self.tabbox.alloc_w, self.tabbox.alloc_h = self.tabbox.alloc_h, self.tabbox.alloc_w

        if pos.startswith("right"):
            self.tabbox.x += self.tabbox.height
        elif pos.startswith("left"):
            self.tabbox.y += self.tabbox.width

        # show/hide thes croll buttons
        # doing it here to avoid recursion as changing visibility calls parent resize
        self.tabs_back.visible = self.tabs_forward.visible = self.show_scroll in (
            True, "auto_invisible")
        self.tabbox.resize_children()
        self.tabs_container.resize_children()

        if self.show_scroll == "auto_invisible":
            self.tabs_back.visible = self.tabs_forward.visible = True
            if self.tabs_container.width < self._tabs_viewport.width:
                self.tabs_back.opacity = self.tabs_forward.opacity = 0
            else:
                self.tabs_back.opacity = self.tabs_forward.opacity = 1

        else:
            self.tabs_back.opacity = self.tabs_forward.opacity = 1
            self.tabs_back.visible = self.tabs_forward.visible = self.show_scroll is True or \
                                                                (self.show_scroll == "auto" and \
                                                                 self.tabs_container.width > self._tabs_viewport.width)

        self.tabbox.resize_children()
        self._position_tabs()

    def tabbox_get_min_size(self):
        w, h = HBox.get_min_size(self.tabbox)
        return h, h

    def tabbox_get_height_for_width_size(self):
        w, h = HBox.get_min_size(self.tabbox)

        if self.tab_position.startswith(
                "right") or self.tab_position.startswith("left"):
            w, h = h, w

        return w, h

    def _tabs_viewport_get_min_size(self):
        # viewport has no demands on size, so we ask the tabs container
        # when positioned on top, tell that we need at least the height
        # when on the side tell that we need at least the width
        w, h = self.tabs_container.get_min_size()
        return 50, h

    def _tabs_viewport_resize_children(self):
        # allow x_align to take effect only if tabs fit.
        x = max(self.tabs_container.x,
                self._tabs_viewport.width - self.tabs_container.width - 1)

        Bin.resize_children(self._tabs_viewport)

        if self.tabs_container.width > self._tabs_viewport.width:
            self.tabs_container.x = x

        self._position_tabs()

    """utilities"""

    def _position_tabs(self):
        if self.scroll_selects_tab and self.current_page:
            tab = self.current_page.tab
            if self.tabs_container.x + tab.x + tab.width > self._tabs_viewport.width:
                self.tabs_container.x = -(tab.x + tab.width -
                                          self._tabs_viewport.width)
            elif self.tabs_container.x + tab.x < 0:
                self.tabs_container.x = -tab.x

        # find first good tab if we all don't fit
        if self.tabs_container.width > self._tabs_viewport.width:
            for tab in self.tabs:
                if tab.x + self.tabs_container.x >= 0:
                    self.tabs_container.x = -tab.x
                    break

        # update opacity so we are not showing partial tabs
        for tab in self.tabs:
            if self.tabs_container.x + tab.x < 0 or self.tabs_container.x + tab.x + tab.width > self._tabs_viewport.width:
                tab.opacity = 0
            else:
                tab.opacity = 1

        # set scroll buttons clickable
        if self.scroll_selects_tab:
            self.tabs_back.enabled = self.current_page and self.pages.index(
                self.current_page) > 0
            self.tabs_forward.enabled = self.current_page and self.pages.index(
                self.current_page) < len(self.pages) - 1
        else:
            self.tabs_back.enabled = self.tabs_container.x < -self.tabs_container.padding_left
            self.tabs_forward.enabled = self.tabs_container.x + self.tabs_container.width > self._tabs_viewport.width

    def _position_contents(self):
        attachment, alignment = self.tab_position or "top", "left"
        if "-" in self.tab_position:
            attachment, alignment = self.tab_position.split("-")

        self.orient_horizontal = attachment in ("right", "left")

        if alignment == "center":
            self.tabs_container.x_align = 0.5
        elif alignment in ("right", "bottom"):
            self.tabs_container.x_align = 1
        else:
            self.tabs_container.x_align = 0

        # on left side the rotation is upside down
        if attachment == "left":
            self.tabs_container.x_align = 1 - self.tabs_container.x_align

        if attachment == "bottom":
            self.tabs_container.y_align = 0
        else:
            self.tabs_container.y_align = 1

        for tab in self.tabs:
            tab.attachment = attachment

        self.clear()
        if attachment == "right":
            self.add_child(self.pages_container, self.tabbox)
            self.tabbox.rotation = math.pi / 2
        elif attachment == "left":
            self.add_child(self.tabbox, self.pages_container)
            self.tabbox.rotation = -math.pi / 2
        elif attachment == "bottom":
            self.add_child(self.pages_container, self.tabbox)
            self.tabbox.rotation = 0
        else:  # defaults to top
            self.add_child(self.tabbox, self.pages_container)
            self.tabbox.rotation = 0

        for tab in self.tabs:
            tab.pivot_x = tab.width / 2
            tab.pivot_y = tab.height / 2

            tab.container.pivot_x = tab.container.width / 2
            tab.container.pivot_y = tab.container.height / 2
            if attachment == "bottom":
                tab.rotation = math.pi
                tab.container.rotation = math.pi
            else:
                tab.rotation = 0
                tab.container.rotation = 0

            if tab.force_vertical_image and tab.image:
                tab.image.pivot_x = tab.image.width / 2
                tab.image.pivot_y = tab.image.height / 2

                if attachment == "right":
                    tab.image.rotation = -math.pi / 2
                elif attachment == "left":
                    tab.image.rotation = math.pi / 2
                else:
                    tab.image.rotation = 0

        self.queue_resize()

    """mouse events"""

    def on_back_press(self, button, event):
        if self.scroll_selects_tab:
            if self.pages.index(self.current_page) > 0:
                self.current_page = self.pages.index(self.current_page) - 1
        else:
            # find the first elem before 0:
            for tab in reversed(self.tabs):
                if self.tabs_container.x + tab.x < 0:
                    self.tabs_container.x = -tab.x
                    break
        self._position_tabs()

    def on_forward_press(self, button, event):
        if self.scroll_selects_tab:
            if self.pages.index(self.current_page) < len(self.pages):
                self.current_page = self.pages.index(self.current_page) + 1
        else:
            if self.tabs_container.x + self.tabs_container.width > self._tabs_viewport.width:
                # find the first which doesn't fit:
                found = None
                for tab in self.tabs:
                    if self.tabs_container.x + tab.x + tab.width > self._tabs_viewport.width:
                        found = True
                        break

                if found:
                    self.tabs_container.x = -(tab.x + tab.width -
                                              self._tabs_viewport.width) - 1
            else:
                self.tabs_container.x = -(self.tabs_container.width -
                                          self._tabs_viewport.width)
        self._position_tabs()

    def on_tab_down(self, tab, event):
        self.current_page = tab

    """rendering"""

    def on_page_render(self, page):
        page.graphics.rectangle(0, 0, page.width, page.height)
        page.graphics.clip()

    def do_render(self):
        self.graphics.set_line_style(width=1)

        x, y, w, h = (self.pages_container.x + 0.5,
                      self.pages_container.y + 0.5,
                      self.pages_container.width - 1,
                      self.pages_container.height - 1)

        self.graphics.rectangle(x, y, w, h)
        self.graphics.fill_stroke("#fafafa", "#999")
Esempio n. 19
0
    def __init__(self,
                 text="",
                 draw_border=True,
                 valid_chars=None,
                 validate_on_type=True,
                 single_paragraph=True,
                 text_formatter=None,
                 alignment=None,
                 font_desc=None,
                 **kwargs):
        Bin.__init__(self, **kwargs)

        self.display_label = graphics.Label(color=self.color)

        self.viewport = Viewport(self.display_label)
        self.viewport.connect("on-render", self.__on_viewport_render)

        self.add_child(self.viewport)

        self.can_focus = True

        self.interactive = True

        self.editable = True

        #: current cursor position
        self.cursor_position = None

        #: start position of the selection
        self.selection_start = 0

        #: end position of the selection
        self.selection_end = 0

        if font_desc is not None:
            self.font_desc = font_desc
        self.display_label.font_desc = self.font_desc

        #: text alignment in the entry
        self.alignment = alignment

        #: if True, a border will be drawn around the input element
        self.draw_border = draw_border

        #self.connect("on-key-press", self.__on_key_press)
        self.connect("on-mouse-down", self.__on_mouse_down)
        self.connect("on-double-click", self.__on_double_click)
        self.connect("on-triple-click", self.__on_triple_click)
        self.connect("on-blur", self.__on_blur)
        self.connect("on-focus", self.__on_focus)

        self.connect_after("on-render", self.__on_render)

        self._scene_mouse_move = None
        self._scene_mouse_up = None
        self._selection_start_position = None
        self._letter_positions = []

        #: a string, function or regexp or valid chars for the input
        #: in case of function, it will receive the string to be tested
        #: as input and expects to receive back a boolean of whether the string
        #: is valid or not
        self.valid_chars = valid_chars

        #: should the content be validate right when typing and invalid version prohibited
        self.validate_on_type = validate_on_type

        #: function to style the entry text - change color and such
        #: the function receives one param - the text, and must return
        #: processed text back. will be using original text if the function
        #: does not return anything.
        #: Note: this function can change only the style, not the actual content
        #: as the latter will mess up text selection because of off-sync between
        #: the label value and what is displayed
        self.text_formatter = text_formatter if text_formatter else self.text_formatter

        #: should the text input support multiple lines
        self.single_paragraph = single_paragraph

        self.update_text(text)
        self._last_good_value = text  # last known good value of the input
Esempio n. 20
0
class Entry(Bin):
    """A text entry field"""
    __gsignals__ = {
        "on-change": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
        "on-position-change": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
    }

    padding = 5

    mouse_cursor = gdk.CursorType.XTERM

    font_desc = "Sans Serif 10" #: pango.FontDescription to use for the label

    color = "#000" #: font color
    cursor_color = "#000" #: cursor color
    selection_color = "#A8C2E0" #: fill color of the selection region

    def __init__(self, text="", draw_border = True,  valid_chars = None,
                 validate_on_type = True, single_paragraph = True,
                 text_formatter = None, alignment = None,
                 font_desc = None, **kwargs):
        Bin.__init__(self, **kwargs)

        self.display_label = graphics.Label(color=self.color)

        self.viewport = Viewport(self.display_label)
        self.viewport.connect("on-render", self.__on_viewport_render)

        self.add_child(self.viewport)

        self.can_focus = True

        self.interactive = True

        self.editable = True

        #: current cursor position
        self.cursor_position = None

        #: start position of the selection
        self.selection_start = 0

        #: end position of the selection
        self.selection_end = 0

        if font_desc is not None:
            self.font_desc = font_desc
        self.display_label.font_desc = self.font_desc

        #: text alignment in the entry
        self.alignment = alignment

        #: if True, a border will be drawn around the input element
        self.draw_border = draw_border

        #self.connect("on-key-press", self.__on_key_press)
        self.connect("on-mouse-down", self.__on_mouse_down)
        self.connect("on-double-click", self.__on_double_click)
        self.connect("on-triple-click", self.__on_triple_click)
        self.connect("on-blur", self.__on_blur)
        self.connect("on-focus", self.__on_focus)

        self.connect_after("on-render", self.__on_render)

        self._scene_mouse_move = None
        self._scene_mouse_up = None
        self._selection_start_position = None
        self._letter_positions = []

        #: a string, function or regexp or valid chars for the input
        #: in case of function, it will receive the string to be tested
        #: as input and expects to receive back a boolean of whether the string
        #: is valid or not
        self.valid_chars = valid_chars

        #: should the content be validate right when typing and invalid version prohibited
        self.validate_on_type = validate_on_type

        #: function to style the entry text - change color and such
        #: the function receives one param - the text, and must return
        #: processed text back. will be using original text if the function
        #: does not return anything.
        #: Note: this function can change only the style, not the actual content
        #: as the latter will mess up text selection because of off-sync between
        #: the label value and what is displayed
        self.text_formatter = text_formatter if text_formatter else self.text_formatter


        #: should the text input support multiple lines
        self.single_paragraph = single_paragraph

        self.update_text(text)
        self._last_good_value = text # last known good value of the input


    def __setattr__(self, name, val):
        if name == "cursor_position" and not self.editable:
            val = None

        if self.__dict__.get(name, "hamster_graphics_no_value_really") == val:
            return

        if name == "text":
            val = val or ""
            if getattr(self, "text_formatter", None):
                markup = self.text_formatter(val.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"))

            if markup:
                self.display_label.markup = markup
            else:
                self.display_label.text = val

        Bin.__setattr__(self, name, val)

        if name == "text":
            self.emit("on-change", val)

        if name in("font_desc", "alignment", "single_paragraph", "color"):
            setattr(self.display_label, name, val)

        elif name == "alloc_w" and getattr(self, "overflow", False) != False and hasattr(self, "display_label"):
            self.display_label.width = val - self.horizontal_padding

        elif name == "overflow" and val != False and hasattr(self, "display_label"):
            if val in (pango.WrapMode.WORD, pango.WrapMode.WORD_CHAR, pango.WrapMode.CHAR):
                self.display_label.wrap = val
                self.display_label.ellipsize = None
            elif val in (pango.EllipsizeMode.START, pango.EllipsizeMode.END):
                self.display_label.wrap = None
                self.display_label.ellipsize = val

        if name == "cursor_position":
            self.emit("on-position-change", val)

    def get_min_size(self):
        return self.min_width or 0, max(self.min_height, self.display_label.height + self.vertical_padding)

    def text_formatter(self, text):
        return None

    def test_value(self, text):
        if not self.valid_chars:
            return True
        elif isinstance(self.valid_chars, basestring):
            return set(text) - set(self.valid_chars) == set([])
        elif hasattr(self.valid_chars, '__call__'):
            return self.valid_chars(text)
        else:
            return False

    def get_height_for_width_size(self):
        return self.get_min_size()


    def update_text(self, text):
        """updates the text field value and the last good known value,
        respecting the valid_chars and validate_on_type flags"""
        text = text or ""

        if self.test_value(text):
            self.text = text
            if self.validate_on_type:
                self._last_good_value = text
        elif not self.validate_on_type:
            self.text = text
        else:
            return False

        self.viewport.height = self.display_label.height
        if self.cursor_position is None:
            self.cursor_position = len(text)

        return True


    def _index_to_pos(self, index):
        """give coordinates for the position in text. maps to the
        display_label's pango function"""
        ext = self.display_label._test_layout.index_to_pos(index)
        extents = [e / pango.SCALE for e in (ext.x, ext.y, ext.width, ext.height)]
        return extents

    def _xy_to_index(self, x, y):
        """from coordinates caluculate position in text. maps to the
        display_label's pango function"""
        x = x - self.display_label.x - self.viewport.x
        index = self.display_label._test_layout.xy_to_index(int(x*pango.SCALE), int(y*pango.SCALE))
        return index[0] + index[1]


    def __on_focus(self, sprite):
        self._last_good_value = self.text

    def __on_blur(self, sprite):
        self._edit_done()

    def _edit_done(self):
        if self.test_value(self.text):
            self._last_good_value = self.text
        else:
            self.update_text(self._last_good_value)

    def __on_mouse_down(self, sprite, event):
        i = self._xy_to_index(event.x, event.y)

        self.selection_start = self.selection_end = self.cursor_position = i
        self._selection_start_position = self.selection_start

        scene = self.get_scene()
        if not self._scene_mouse_up:
            self._scene_mouse_up = scene.connect("on-mouse-up", self._on_scene_mouse_up)
            self._scene_mouse_move = scene.connect("on-mouse-move", self._on_scene_mouse_move)

    def __on_double_click(self, sprite, event):
        # find the word
        cursor = self.cursor_position
        self.selection_start = self.text.rfind(" ", 0, cursor) + 1

        end = self.text.find(" ", cursor)
        self.cursor_position = self.selection_end = end if end > 0 else len(self.text)


    def __on_triple_click(self, sprite, event):
        self.selection_start = 0
        self.cursor_position = self.selection_end = len(self.text)

    def _on_scene_mouse_up(self, scene, event):
        scene.disconnect(self._scene_mouse_up)
        scene.disconnect(self._scene_mouse_move)
        self._scene_mouse_up = self._scene_mouse_move = None

    def _on_scene_mouse_move(self, scene, event):
        if self.focused == False:
            return

        # now try to derive cursor position
        x, y = self.display_label.from_scene_coords(event.x, event.y)
        x = x + self.display_label.x + self.viewport.x
        i = self._xy_to_index(x, y)

        self.cursor_position = i
        if self.cursor_position < self._selection_start_position:
            self.selection_start = self.cursor_position
            self.selection_end = self._selection_start_position
        else:
            self.selection_start = self._selection_start_position
            self.selection_end = self.cursor_position

    def _get_iter(self, index = 0):
        """returns iterator that has been run till the specified index"""
        iter = self.display_label._test_layout.get_iter()
        for i in range(index):
            iter.next_char()
        return iter

    def _do_key_press(self, event):
        """responding to key events"""
        key = event.keyval
        shift = event.state & gdk.ModifierType.SHIFT_MASK
        control = event.state & gdk.ModifierType.CONTROL_MASK

        if not self.editable:
            return

        def emit_and_return():
            self.emit("on-key-press", event)
            return


        if self.single_paragraph and key == gdk.KEY_Return:
            self._edit_done()
            return emit_and_return()

        self._letter_positions = []

        if key == gdk.KEY_Left:
            if shift and self.cursor_position == 0:
                return emit_and_return()

            if control:
                self.cursor_position = self.text[:self.cursor_position].rstrip().rfind(" ") + 1
            else:
                self.cursor_position -= 1

            if shift:
                if self.cursor_position < self.selection_start:
                    self.selection_start = self.cursor_position
                else:
                    self.selection_end = self.cursor_position
            elif self.selection_start != self.selection_end:
                self.cursor_position = self.selection_start

        elif key == gdk.KEY_Right:
            if shift and self.cursor_position == len(self.text):
                return emit_and_return()

            if control:
                prev_pos = self.cursor_position
                self.cursor_position = self.text[self.cursor_position:].lstrip().find(" ")
                if self.cursor_position == -1:
                    self.cursor_position = len(self.text)
                else:
                    self.cursor_position += prev_pos + 1
            else:
                self.cursor_position += 1

            if shift:
                if self.cursor_position > self.selection_end:
                    self.selection_end = self.cursor_position
                else:
                    self.selection_start = self.cursor_position
            elif self.selection_start != self.selection_end:
                self.cursor_position = self.selection_end

        elif key == gdk.KEY_Up and self.single_paragraph == False:
            iter = self._get_iter(self.cursor_position)

            if str(iter.get_line_readonly()) != str(self.display_label._test_layout.get_line_readonly(0)):
                char_pos = iter.get_char_extents().x
                char_line = str(iter.get_line_readonly())

                # now we run again to run until previous line
                prev_iter, iter = self._get_iter(), self._get_iter()
                prev_line = None
                while str(iter.get_line_readonly()) != char_line:
                    if str(prev_line) != str(iter.get_line_readonly()):
                        prev_iter = iter.copy()
                        prev_line = iter.get_line_readonly()
                    iter.next_char()

                index = prev_iter.get_line_readonly().x_to_index(char_pos - prev_iter.get_char_extents().x)
                index = index[1] + index[2]

                self.cursor_position = index

                if shift:
                    if self.cursor_position < self.selection_start:
                        self.selection_start = self.cursor_position
                    else:
                        self.selection_end = self.cursor_position
                elif self.selection_start != self.selection_end:
                    self.cursor_position = self.selection_start


        elif key == gdk.KEY_Down and self.single_paragraph == False:
            iter = self._get_iter(self.cursor_position)
            char_pos = iter.get_char_extents().x

            if iter.next_line():
                index = iter.get_line_readonly().x_to_index(char_pos - iter.get_char_extents().x)
                index = index[1] + index[2]
                self.cursor_position = index

                if shift:
                    if self.cursor_position > self.selection_end:
                        self.selection_end = self.cursor_position
                    else:
                        self.selection_start = self.cursor_position
                elif self.selection_start != self.selection_end:
                    self.cursor_position = self.selection_end


        elif key == gdk.KEY_Home:
            if self.single_paragraph or control:
                self.cursor_position = 0
                if shift:
                    self.selection_end = self.selection_start
                    self.selection_start = self.cursor_position
            else:
                iter = self._get_iter(self.cursor_position)
                line = str(iter.get_line_readonly())

                # find the start of the line
                iter = self._get_iter()
                while str(iter.get_line_readonly()) != line:
                    iter.next_char()
                self.cursor_position = iter.get_index()

                if shift:
                    start_iter = self._get_iter(self.selection_start)
                    end_iter = self._get_iter(self.selection_end)
                    if str(start_iter.get_line_readonly()) == str(end_iter.get_line_readonly()):
                        self.selection_end = self.selection_start
                        self.selection_start = self.cursor_position
                    else:
                        if self.cursor_position < self.selection_start:
                            self.selection_start = self.cursor_position
                        else:
                            self.selection_end = self.cursor_position

        elif key == gdk.KEY_End:
            if self.single_paragraph or control:
                self.cursor_position = len(self.text)
                if shift:
                    self.selection_start = self.selection_end
                    self.selection_end = self.cursor_position
            else:
                iter = self._get_iter(self.cursor_position)

                #find the end of the line
                line = str(iter.get_line_readonly())
                prev_iter = None

                while str(iter.get_line_readonly()) == line:
                    prev_iter = iter.copy()
                    moved = iter.next_char()
                    if not moved:
                        prev_iter = iter
                        break

                self.cursor_position = prev_iter.get_index()

                if shift:
                    start_iter = self._get_iter(self.selection_start)
                    end_iter = self._get_iter(self.selection_end)
                    if str(start_iter.get_line_readonly()) == str(end_iter.get_line_readonly()):
                        self.selection_start = self.selection_end
                        self.selection_end = self.cursor_position
                    else:
                        if self.cursor_position > self.selection_end:
                            self.selection_end = self.cursor_position
                        else:
                            self.selection_start = self.cursor_position


        elif key == gdk.KEY_BackSpace:
            if self.selection_start != self.selection_end:
                if not self.update_text(self.text[:self.selection_start] + self.text[self.selection_end:]):
                    return emit_and_return()
            elif self.cursor_position > 0:
                if not self.update_text(self.text[:self.cursor_position-1] + self.text[self.cursor_position:]):
                    return emit_and_return()
                self.cursor_position -= 1

        elif key == gdk.KEY_Delete:
            if self.selection_start != self.selection_end:
                if not self.update_text(self.text[:self.selection_start] + self.text[self.selection_end:]):
                    return emit_and_return()
            elif self.cursor_position < len(self.text):
                if not self.update_text(self.text[:self.cursor_position] + self.text[self.cursor_position+1:]):
                    return emit_and_return()
        elif key == gdk.KEY_Escape:
            return emit_and_return()

        #prevent garbage from common save file mneumonic
        elif control and key in (gdk.KEY_s, gdk.KEY_S):
            return emit_and_return()

        # copying and pasting
        elif control and key in (gdk.KEY_c, gdk.KEY_C): # copy
            clipboard = gtk.Clipboard()
            clipboard.set_text(self.text[self.selection_start:self.selection_end])
            return emit_and_return()

        elif control and key in (gdk.KEY_x, gdk.KEY_X): # cut
            text = self.text[self.selection_start:self.selection_end]
            if self.update_text(self.text[:self.selection_start] + self.text[self.selection_end:]):
                clipboard = gtk.Clipboard()
                clipboard.set_text(text)

        elif control and key in (gdk.KEY_v, gdk.KEY_V): # paste
            clipboard = gtk.Clipboard()
            clipboard.request_text(self._on_clipboard_text)
            return emit_and_return()

        elif control and key in (gdk.KEY_a, gdk.KEY_A): # select all
            self.selection_start = 0
            self.cursor_position = self.selection_end = len(self.text)
            return emit_and_return()

        # normal letters
        elif event.string:
            if self.update_text(self.text[:self.selection_start] + event.string + self.text[self.selection_end:]):
                self.cursor_position = self.selection_start + 1
                self.selection_start = self.selection_end = self.cursor_position
            return emit_and_return()
        else: # in case of anything else just go home
            return emit_and_return()


        self.cursor_position = min(max(0, self.cursor_position), len(self.text))
        self.selection_start = min(max(0, self.selection_start), len(self.text))
        self.selection_end = min(max(0, self.selection_end), len(self.text))

        if shift == False:
            self.selection_start = self.selection_end = self.cursor_position

        return emit_and_return()

    def _on_clipboard_text(self, clipboard, text, data):
        if self.update_text(self.text[:self.selection_start] + text + self.text[self.selection_end:]):
            self.selection_start = self.selection_end = self.cursor_position = self.selection_start + len(text)

    def do_render(self):
        self.graphics.set_line_style(width=1)
        self.graphics.rectangle(0.5, -1.5, self.width, self.height + 2, 3)
        if self.draw_border:
            self.graphics.fill_preserve("#fff")
            self.graphics.stroke("#aaa")


    def __on_render(self, sprite):
        self.graphics.rectangle(0, 0, self.width, self.height)
        self.graphics.new_path()

        if self.cursor_position is not None:
            cur_x, cur_y, cur_w, cur_h = self._index_to_pos(self.cursor_position)
            if self.display_label.x + cur_x > self.viewport.width:
                self.display_label.x = min(0, self.viewport.width - cur_x) # cursor visible at the right side
            elif self.display_label.x + cur_x < 0:
                self.display_label.x -= (self.display_label.x + cur_x) # cursor visible at the left side
            elif self.display_label.x < 0 and self.display_label.x + self.display_label.width < self.viewport.width:
                self.display_label.x = min(self.viewport.width - self.display_label.width, 0)

        # align the label within the entry
        if self.display_label.width < self.viewport.width:
            if self.alignment == pango.Alignment.RIGHT:
                self.display_label.x = self.viewport.width - self.display_label.width
            elif self.alignment == pango.Alignment.CENTER:
                self.display_label.x = (self.viewport.width - self.display_label.width) / 2

        #if self.single_paragraph:
        #    self.display_label.y = (self.viewport.height - self.display_label.height) / 2.0
        self.viewport._sprite_dirty = True # so that we get the cursor drawn


    def __on_viewport_render(self, viewport):
        if self.focused == False:
            return

        if self.cursor_position is None:
            return

        cur_x, cur_y, cur_w, cur_h = self._index_to_pos(self.cursor_position)
        cur_x = cur_x + self.display_label.x
        cur_y += self.display_label.y

        viewport.graphics.move_to(cur_x + 0.5, cur_y)
        viewport.graphics.line_to(cur_x + 0.5, cur_y + cur_h)
        viewport.graphics.stroke(self.cursor_color)

        if self.selection_start == self.selection_end:
            return # all done!


        start_x, start_y, start_w, start_h = self._index_to_pos(self.selection_start)
        end_x, end_y, end_w, end_h = self._index_to_pos(self.selection_end)


        iter = self._get_iter(self.selection_start)

        char_exts = iter.get_char_extents()
        cur_x, cur_y = char_exts.x / pango.SCALE, char_exts.y / pango.SCALE + self.display_label.y

        cur_line = None
        for i in range(self.selection_end - self.selection_start):
            prev_iter = pango.LayoutIter.copy(iter)
            iter.next_char()

            line = iter.get_line_readonly()
            if str(cur_line) != str(line): # can't compare layout lines for some reason
                exts = prev_iter.get_char_extents()
                char_exts = [ext / pango.SCALE for ext in (exts.x, exts.y, exts.width, exts.height)]
                viewport.graphics.rectangle(cur_x + self.display_label.x, cur_y,
                                            char_exts[0] + char_exts[2] - cur_x, char_exts[3])

                char_exts = iter.get_char_extents()
                cur_x, cur_y = char_exts.x / pango.SCALE, char_exts.y / pango.SCALE + self.display_label.y

            cur_line = line

        exts = iter.get_char_extents()
        char_exts = [ext / pango.SCALE for ext in  (exts.x, exts.y, exts.width, exts.height)]

        viewport.graphics.rectangle(cur_x + self.display_label.x, cur_y,
                                    char_exts[0] - cur_x, self.display_label.y + char_exts[1] - cur_y + char_exts[3])
        viewport.graphics.fill(self.selection_color)