Exemplo n.º 1
0
 def init_widget(self):
     super().init_widget()
     container = ABox(position=(20, 210))
     icon = Icon(name='build_all_bg')
     button = ImageButton(name='build_all_button')
     container.addChild(icon)
     container.addChild(button)
     self.widget.addChild(container)
     self.update_data()
Exemplo n.º 2
0
 def __init_gui(self):
     self.gui = ABox()
     self.label = Label(position=(10, 5))
     self.bg = TooltipBG()
     self.gui.addChildren(self.bg, self.label)
Exemplo n.º 3
0
class _Tooltip:
    """Base class for pychan widgets overloaded with tooltip functionality"""

    # Character count after which we start new line.
    CHARS_PER_LINE = 19
    # Find and replace horribly complicated elements that allow simple icons.
    icon_regexp = re.compile(r'\[\[Buildmenu((?: \d+\:\d+)+)\]\]')

    def init_tooltip(self):
        # the widget's parent's parent's ..... until the topmost
        self.topmost_widget = None
        self.gui = None
        self.bg = None
        self.label = None
        self.mapEvents({
            self.name + '/mouseEntered/tooltip':
            self.position_tooltip,
            self.name + '/mouseExited/tooltip':
            self.hide_tooltip,
            # Below causes frequent Segmentation Faults due to too many
            # self.position_tooltip() calls.
            # self.name + '/mouseMoved/tooltip' : self.position_tooltip,

            # TIP: the mousePressed event is especially useful when such as click
            # will trigger this tooltip's parent widget to be hidden (or destroyed),
            # which hides this tooltip first before hides the parent widget.
            # Otherwise the tooltip will show forever.
            self.name + '/mousePressed/tooltip':
            self.hide_tooltip,

            # TODO: not sure if below are useful or not
            # self.name + '/mouseReleased/tooltip' : self.position_tooltip,
            self.name + '/mouseDragged/tooltip':
            self.hide_tooltip
        })
        self.tooltip_shown = False
        self.cooldown = time.time()  # initial timer value

    def __init_gui(self):
        self.gui = ABox()
        self.label = Label(position=(10, 5))
        self.bg = TooltipBG()
        self.gui.addChildren(self.bg, self.label)

    def position_tooltip(self, event):
        """Calculates a nice position for the tooltip.
		@param event: mouse event from fife or tuple screenpoint
		"""
        if not self.helptext:
            return

        # TODO: think about nicer way of handling the polymorphism here,
        # e.g. a position_tooltip_event and a position_tooltip_tuple
        where = event  # fife forces this to be called event, but here it can also be a tuple
        if isinstance(where, tuple):
            x, y = where
        else:
            if where.getButton() == fife.MouseEvent.MIDDLE:
                return

            x, y = where.getX(), where.getY()

        if self.gui is None:
            self.__init_gui()

        widget_position = self.getAbsolutePos()

        # Sometimes, we get invalid events from pychan, it is probably related to changing the
        # gui when the mouse hovers on gui elements.
        # Random tests have given evidence to believe that pychan indicates invalid events
        # by setting the top container's position to 0, 0.
        # Since this position is currently unused, it can serve as invalid flag,
        # and dropping these events seems to lead to the desired placements
        def get_top(w):
            return get_top(w.parent) if w.parent else w

        top_pos = get_top(self).position
        if top_pos == (0, 0):
            return

        if not self.tooltip_shown:
            self.show_tooltip()
            #ExtScheduler().add_new_object(self.show_tooltip, self, run_in=0.3, loops=0)
            self.tooltip_shown = True

        screen_width = horizons.globals.fife.engine_settings.getScreenWidth()
        self.gui.y = widget_position[1] + y + 5
        offset = x + 10
        if (widget_position[0] + self.gui.size[0] + offset) > screen_width:
            # right screen edge, position to the left of cursor instead
            offset = x - self.gui.size[0] - 5
        self.gui.x = widget_position[0] + offset

    def show_tooltip(self):
        if not self.helptext:
            return
        if self.gui is None:
            self.__init_gui()

        # Compare and reset timer value if difference from current time shorter than X sec.
        if (time.time() - self.cooldown) < 1:
            return
        else:
            self.cooldown = time.time()

        #HACK: support icons in build menu
        # Code below exists for the sole purpose of build menu tooltips showing
        # resource icons. Even supporting that is a pain (as you will see),
        # so if you think you need icons in other tooltips, maybe reconsider.
        # [These unicode() calls brought to you by status icon tooltip code.]
        buildmenu_icons = self.icon_regexp.findall(str(self.helptext))
        # Remove the weird stuff before displaying text.
        replaced = self.icon_regexp.sub('', str(self.helptext))
        # Specification looks like [[Buildmenu 1:250 4:2 6:2]]
        if buildmenu_icons:
            hbox = HBox(position=(7, 5))
            for spec in buildmenu_icons[0].split():
                (res_id, amount) = spec.split(':')
                label = Label(text=amount + '  ')
                icon = Icon(image=get_res_icon_path(int(res_id)),
                            size=(16, 16),
                            scale=True)
                hbox.addChildren(icon, label)
            hbox.adaptLayout()
            # Now display the 16x16px "required resources" icons in the last line.
            self.gui.addChild(hbox)

        #HACK: wrap tooltip text
        # This looks better than splitting into several lines and joining them.
        # It works because replace_whitespace in `fill` defaults to True.
        replaced = replaced.replace(r'\n', self.CHARS_PER_LINE * ' ')
        replaced = replaced.replace('[br]', self.CHARS_PER_LINE * ' ')
        tooltip = textwrap.fill(replaced, self.CHARS_PER_LINE)

        # Finish up the actual tooltip (text, background panel amount, layout).
        # To display build menu icons, we need another empty (first) line.
        self.bg.amount = len(tooltip.splitlines()) - 1 + bool(buildmenu_icons)
        self.label.text = bool(buildmenu_icons) * '\n' + tooltip
        self.gui.adaptLayout()
        self.gui.show()

        # NOTE: the below code in this method is a hack to resolve #2227
        # cannot find a better way to fix it, cause in fife.pychan, it seems
        # if a widget gets hidden or removed, the children of that widget are not
        # hidden or removed properly (at least in Python code)

        # update topmost_widget every time the tooltip is shown
        # this is to dismiss the tooltip later, see _check_hover_alive
        target_widget = self
        while target_widget:
            self.topmost_widget = target_widget
            target_widget = target_widget.parent

        # add an event to constantly check whether the hovered widget is still there
        # if this is no longer there, dismiss the tooltip widget
        ExtScheduler().add_new_object(self._check_hover_alive,
                                      self,
                                      run_in=0.5,
                                      loops=-1)

    def _check_hover_alive(self):
        target_widget = self
        # traverse the widget chain again
        while target_widget:
            # none of ancestors of this widget gets removed,
            # just do nothing and let the tooltip shown
            if target_widget == self.topmost_widget:
                return
            # one ancestor of this widget is hidden
            if not target_widget.isVisible():
                self.hide_tooltip()
                return
            target_widget = target_widget.parent

        # if it comes to here, meaning one ancestor of this widget is removed
        self.hide_tooltip()

    def hide_tooltip(self, event=None):
        if self.gui is not None:
            self.gui.hide()
        # tooltip is hidden, no need to check any more
        ExtScheduler().rem_call(self, self._check_hover_alive)
        self.topmost_widget = None
        self.tooltip_shown = False
Exemplo n.º 4
0
	def __init_gui(self):
		self.gui = ABox()
		self.label = Label(position=(10, 5))
		self.bg = TooltipBG()
		self.gui.addChildren(self.bg, self.label)
Exemplo n.º 5
0
class _Tooltip:
	"""Base class for pychan widgets overloaded with tooltip functionality"""

	# Character count after which we start new line.
	CHARS_PER_LINE = 19
	# Find and replace horribly complicated elements that allow simple icons.
	icon_regexp = re.compile(r'\[\[Buildmenu((?: \d+\:\d+)+)\]\]')

	def init_tooltip(self):
		# the widget's parent's parent's ..... until the topmost
		self.topmost_widget = None
		self.gui = None
		self.bg = None
		self.label = None
		self.mapEvents({
			self.name + '/mouseEntered/tooltip': self.position_tooltip,
			self.name + '/mouseExited/tooltip': self.hide_tooltip,
			# Below causes frequent Segmentation Faults due to too many
			# self.position_tooltip() calls.
			# self.name + '/mouseMoved/tooltip' : self.position_tooltip,

			# TIP: the mousePressed event is especially useful when such as click
			# will trigger this tooltip's parent widget to be hidden (or destroyed),
			# which hides this tooltip first before hides the parent widget.
			# Otherwise the tooltip will show forever.
			self.name + '/mousePressed/tooltip': self.hide_tooltip,

			# TODO: not sure if below are useful or not
			# self.name + '/mouseReleased/tooltip' : self.position_tooltip,
			self.name + '/mouseDragged/tooltip': self.hide_tooltip
			})
		self.tooltip_shown = False
		self.cooldown = time.time()		# initial timer value

	def __init_gui(self):
		self.gui = ABox()
		self.label = Label(position=(10, 5))
		self.bg = TooltipBG()
		self.gui.addChildren(self.bg, self.label)

	def position_tooltip(self, event):
		"""Calculates a nice position for the tooltip.
		@param event: mouse event from fife or tuple screenpoint
		"""
		if not self.helptext:
			return

		# TODO: think about nicer way of handling the polymorphism here,
		# e.g. a position_tooltip_event and a position_tooltip_tuple
		where = event # fife forces this to be called event, but here it can also be a tuple
		if isinstance(where, tuple):
			x, y = where
		else:
			if where.getButton() == fife.MouseEvent.MIDDLE:
				return

			x, y = where.getX(), where.getY()

		if self.gui is None:
			self.__init_gui()

		widget_position = self.getAbsolutePos()

		# Sometimes, we get invalid events from pychan, it is probably related to changing the
		# gui when the mouse hovers on gui elements.
		# Random tests have given evidence to believe that pychan indicates invalid events
		# by setting the top container's position to 0, 0.
		# Since this position is currently unused, it can serve as invalid flag,
		# and dropping these events seems to lead to the desired placements
		def get_top(w):
			return get_top(w.parent) if w.parent else w
		top_pos = get_top(self).position
		if top_pos == (0, 0):
			return

		if not self.tooltip_shown:
			self.show_tooltip()
			#ExtScheduler().add_new_object(self.show_tooltip, self, run_in=0.3, loops=0)
			self.tooltip_shown = True

		screen_width = horizons.globals.fife.engine_settings.getScreenWidth()

		if not isinstance(self, Icon):
			# exclude building statusicons (eg low productivity) and minimap

			# If the a button spawn a tooltip and the cursor hovers over that
			# tooltip the tooltip will disappear and the button becomes
			# unclickable. (see issue #2776: https://git.io/vxRrn)
			# This is a workaround for that problem, by making tooltips for
			# buttons always display below the button.
			# There is a small chance that it still happens if you move the
			# cursor very fast, but that seems unlikely to be such a major
			# problem as it used to be.
			# The underlying problem is that the cursor can not be repositioned
			# on every mousemove because that causes frequent segfaults.
			# If that problem would be fixed this workaround wouldn't be
			# neccessary anymore.

			ypos = widget_position[1] + self.height + 10

			xpos = int(widget_position[0])
			xpos = min(xpos, screen_width - self.gui.size[0] - 5)

		else:

			ypos = widget_position[1] + y + 5
			offset = x + 10
			if (widget_position[0] + self.gui.size[0] + offset) > screen_width:
				# right screen edge, position to the left of cursor instead
				offset = x - self.gui.size[0] - 5
			xpos = widget_position[0] + offset

		self.gui.x = xpos
		self.gui.y = ypos

	def show_tooltip(self):
		if not self.helptext:
			return
		if self.gui is None:
			self.__init_gui()

		# Compare and reset timer value if difference from current time shorter than X sec.
		if (time.time() - self.cooldown) < .1:
			return
		else:
			self.cooldown = time.time()

		#HACK: support icons in build menu
		# Code below exists for the sole purpose of build menu tooltips showing
		# resource icons. Even supporting that is a pain (as you will see),
		# so if you think you need icons in other tooltips, maybe reconsider.
		# [These unicode() calls brought to you by status icon tooltip code.]
		buildmenu_icons = self.icon_regexp.findall(str(self.helptext))
		# Remove the weird stuff before displaying text.
		replaced = self.icon_regexp.sub('', str(self.helptext))
		# Specification looks like [[Buildmenu 1:250 4:2 6:2]]
		if buildmenu_icons:
			hbox = HBox(position=(7, 5))
			for spec in buildmenu_icons[0].split():
				(res_id, amount) = spec.split(':')
				label = Label(text=amount + '  ')
				icon = Icon(image=get_res_icon_path(int(res_id)), size=(16, 16),
				            scale=True)
				hbox.addChildren(icon, label)
			hbox.adaptLayout()
			# Now display the 16x16px "required resources" icons in the last line.
			self.gui.addChild(hbox)

		#HACK: wrap tooltip text
		# This looks better than splitting into several lines and joining them.
		# It works because replace_whitespace in `fill` defaults to True.
		replaced = replaced.replace(r'\n', self.CHARS_PER_LINE * ' ')
		replaced = replaced.replace('[br]', self.CHARS_PER_LINE * ' ')
		tooltip = textwrap.fill(replaced, self.CHARS_PER_LINE)

		# Finish up the actual tooltip (text, background panel amount, layout).
		# To display build menu icons, we need another empty (first) line.
		self.bg.amount = len(tooltip.splitlines()) - 1 + bool(buildmenu_icons)
		self.label.text = bool(buildmenu_icons) * '\n' + tooltip
		self.gui.adaptLayout()
		self.gui.show()

		# NOTE: the below code in this method is a hack to resolve #2227
		# cannot find a better way to fix it, cause in fife.pychan, it seems
		# if a widget gets hidden or removed, the children of that widget are not
		# hidden or removed properly (at least in Python code)

		# update topmost_widget every time the tooltip is shown
		# this is to dismiss the tooltip later, see _check_hover_alive
		target_widget = self
		while target_widget:
			self.topmost_widget = target_widget
			target_widget = target_widget.parent

		# add an event to constantly check whether the hovered widget is still there
		# if this is no longer there, dismiss the tooltip widget
		ExtScheduler().add_new_object(self._check_hover_alive, self, run_in=0.5, loops=-1)

	def _check_hover_alive(self):
		target_widget = self
		# traverse the widget chain again
		while target_widget:
			# none of ancestors of this widget gets removed,
			# just do nothing and let the tooltip shown
			if target_widget == self.topmost_widget:
				return
			# one ancestor of this widget is hidden
			if not target_widget.isVisible():
				self.hide_tooltip()
				return
			target_widget = target_widget.parent

		# if it comes to here, meaning one ancestor of this widget is removed
		self.hide_tooltip()

	def hide_tooltip(self, event=None):
		if self.gui is not None:
			self.gui.hide()
		# tooltip is hidden, no need to check any more
		ExtScheduler().rem_call(self, self._check_hover_alive)
		self.topmost_widget = None
		self.tooltip_shown = False