Beispiel #1
0
class GraphBase(object):
	"""
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
	name = 'Unknown'
	"""The name of the graph provider."""
	name_human = 'Unknown'
	"""The human readable name of the graph provider used for UI identification."""
	graph_title = 'Unknown'
	"""The title that will be given to the graph."""
	is_available = True
	def __init__(self, application, size_request=None, style_context=None):
		"""
		:param tuple size_request: The size to set for the canvas.
		"""
		self.application = application
		self.style_context = style_context
		self.config = application.config
		"""A reference to the King Phisher client configuration."""
		self.figure, _ = pyplot.subplots()
		self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE))
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		self.minimum_size = (380, 200)
		"""An absolute minimum size for the canvas."""
		if size_request is not None:
			self.resize(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.application.get_active_window())

		self.popup_menu = managers.MenuManager()
		self.popup_menu.append('Export', self.signal_activate_popup_menu_export)
		self.popup_menu.append('Refresh', self.signal_activate_popup_refresh)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append_item(menu_item)

		self.navigation_toolbar.hide()
		self._legend = None

	@property
	def rpc(self):
		return self.application.rpc

	@staticmethod
	def _ax_hide_ticks(ax):
		for tick in ax.yaxis.get_major_ticks():
			tick.tick1On = False
			tick.tick2On = False

	@staticmethod
	def _ax_set_spine_color(ax, spine_color):
		for pos in ('top', 'right', 'bottom', 'left'):
			ax.spines[pos].set_color(spine_color)

	def add_legend_patch(self, legend_rows, fontsize=None):
		if self._legend is not None:
			self._legend.remove()
			self._legend = None
		fontsize = fontsize or self.fontsize_scale
		legend_bbox = self.figure.legend(
			tuple(patches.Patch(color=patch_color) for patch_color, _ in legend_rows),
			tuple(label for _, label in legend_rows),
			borderaxespad=1.25,
			fontsize=fontsize,
			frameon=True,
			handlelength=1.5,
			handletextpad=0.75,
			labelspacing=0.3,
			loc='lower right'
		)
		legend_bbox.legendPatch.set_linewidth(0)
		self._legend = legend_bbox

	def get_color(self, color_name, default):
		"""
		Get a color by its style name such as 'fg' for foreground. If the
		specified color does not exist, default will be returned. The underlying
		logic for this function is provided by
		:py:func:`~.gui_utilities.gtk_style_context_get_color`.

		:param str color_name: The style name of the color.
		:param default: The default color to return if the specified one was not found.
		:return: The desired color if it was found.
		:rtype: tuple
		"""
		color_name = 'theme_color_graph_' + color_name
		sc_color = gui_utilities.gtk_style_context_get_color(self.style_context, color_name, default)
		return (sc_color.red, sc_color.green, sc_color.blue)

	def make_window(self):
		"""
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
		if self.manager is None:
			self.manager = FigureManager(self.canvas, 0)
		self.navigation_toolbar.destroy()
		self.navigation_toolbar = self.manager.toolbar
		self._menu_item_show_toolbar.set_active(True)
		window = self.manager.window
		window.set_transient_for(self.application.get_active_window())
		window.set_title(self.graph_title)
		return window

	@property
	def fontsize_scale(self):
		scale = self.markersize_scale
		if scale < 5:
			fontsize = 'xx-small'
		elif scale < 7:
			fontsize = 'x-small'
		elif scale < 9:
			fontsize = 'small'
		else:
			fontsize = 'medium'
		return fontsize

	@property
	def markersize_scale(self):
		bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
		return bbox.width * self.figure.dpi * 0.01

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		self.popup_menu.menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = extras.FileChooserDialog('Export Graph', self.application.get_active_window())
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_path']
		self.figure.savefig(destination_file, dpi=200, facecolor=self.figure.get_facecolor(), format='png')

	def signal_activate_popup_refresh(self, event):
		self.refresh()

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def resize(self, width=0, height=0):
		"""
		Attempt to resize the canvas. Regardless of the parameters the canvas
		will never be resized to be smaller than :py:attr:`.minimum_size`.

		:param int width: The desired width of the canvas.
		:param int height: The desired height of the canvas.
		"""
		min_width, min_height = self.minimum_size
		width = max(width, min_width)
		height = max(height, min_height)
		self.canvas.set_size_request(width, height)
Beispiel #2
0
class CampaignGraph(object):
	"""
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
	name = 'Unknown'
	"""The name of the graph provider."""
	name_human = 'Unknown'
	"""The human readable name of the graph provider used for UI identification."""
	graph_title = 'Unknown'
	"""The title that will be given to the graph."""
	table_subscriptions = []
	"""A list of tables from which information is needed to produce the graph."""
	is_available = True
	def __init__(self, config, parent, size_request=None):
		"""
		:param dict config: The King Phisher client configuration.
		:param parent: The parent window for this object.
		:type parent: :py:class:`Gtk.Window`
		:param tuple size_request: The size to set for the canvas.
		"""
		self.config = config
		"""A reference to the King Phisher client configuration."""
		self.parent = parent
		"""The parent :py:class:`Gtk.Window` instance."""
		self.figure, _ = pyplot.subplots()
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()
		self.navigation_toolbar.hide()

	def _load_graph(self, info_cache):
		raise NotImplementedError()

	def _graph_bar_set_yparams(self, top_lim):
		min_value = top_lim + (top_lim * 0.075)
		if min_value <= 25:
			scale = 5
		else:
			scale = scale = 10 ** (len(str(int(min_value))) - 1)
		inc_scale = scale
		while scale <= min_value:
			scale += inc_scale
		top_lim = scale

		ax = self.axes[0]
		yticks = set((round(top_lim * 0.5), top_lim))
		ax.set_yticks(tuple(yticks))
		ax.set_ylim(top=top_lim)
		return

	def _graph_null_pie(self, title):
		ax = self.axes[0]
		ax.pie((100,), labels=(title,), colors=(MPL_COLOR_NULL,), autopct='%1.0f%%', shadow=True, startangle=90)
		ax.axis('equal')
		return

	def add_legend_patch(self, legend_rows, fontsize=None):
		handles = []
		if not fontsize:
			scale = self.markersize_scale
			if scale < 5:
				fontsize = 'xx-small'
			elif scale < 7:
				fontsize = 'x-small'
			elif scale < 9:
				fontsize = 'small'
			else:
				fontsize = 'medium'
		for row in legend_rows:
			handles.append(patches.Patch(color=row[0], label=row[1]))
		self.axes[0].legend(handles=handles, fontsize=fontsize, loc='lower right')

	def graph_bar(self, bars, color=None, xticklabels=None, ylabel=None):
		"""
		Create a standard bar graph with better defaults for the standard use
		cases.

		:param list bars: The values of the bars to graph.
		:param color: The color of the bars on the graph.
		:type color: list, str
		:param list xticklabels: The labels to use on the x-axis.
		:param str ylabel: The label to give to the y-axis.
		:return: The bars created using :py:mod:`matplotlib`
		:rtype: `matplotlib.container.BarContainer`
		"""
		color = color or MPL_COLOR_NULL
		width = 0.25
		ax = self.axes[0]
		self._graph_bar_set_yparams(max(bars) if bars else 0)
		bars = ax.bar(range(len(bars)), bars, width, color=color)
		ax.set_xticks([float(x) + (width / 2) for x in range(len(bars))])
		if xticklabels:
			ax.set_xticklabels(xticklabels, rotation=30)
			for col in bars:
				height = col.get_height()
				ax.text(col.get_x() + col.get_width() / 2.0, height, "{0:,}".format(height), ha='center', va='bottom')
		if ylabel:
			ax.set_ylabel(ylabel)
		self.figure.subplots_adjust(bottom=0.25)
		return bars

	def make_window(self):
		"""
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
		if self.manager == None:
			self.manager = FigureManager(self.canvas, 0)
		self.navigation_toolbar.destroy()
		self.navigation_toolbar = self.manager.toolbar
		self._menu_item_show_toolbar.set_active(True)
		window = self.manager.window
		window.set_transient_for(self.parent)
		window.set_title(self.graph_title)
		return window

	@property
	def markersize_scale(self):
		bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
		return max(bbox.width, bbox.width) * self.figure.dpi * 0.01

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = gui_utilities.UtilityFileChooser('Export Graph', self.parent)
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_path']
		self.figure.savefig(destination_file, format='png')

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def load_graph(self):
		"""Load the graph information via :py:meth:`.refresh`."""
		self.refresh()

	def refresh(self, info_cache=None, stop_event=None):
		"""
		Refresh the graph data by retrieving the information from the
		remote server.

		:param dict info_cache: An optional cache of data tables.
		:param stop_event: An optional object indicating that the operation should stop.
		:type stop_event: :py:class:`threading.Event`
		:return: A dictionary of cached tables from the server.
		:rtype: dict
		"""
		info_cache = (info_cache or {})
		if not self.parent.rpc:
			return info_cache
		for table in self.table_subscriptions:
			if stop_event and stop_event.is_set():
				return info_cache
			if not table in info_cache:
				info_cache[table] = tuple(self.parent.rpc.remote_table('campaign/' + table, self.config['campaign_id']))
		for ax in self.axes:
			ax.clear()
		self._load_graph(info_cache)
		self.axes[0].set_title(self.graph_title, y=1.03)
		self.canvas.draw()
		return info_cache
Beispiel #3
0
class GraphBase(object):
    """
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
    name = 'Unknown'
    """The name of the graph provider."""
    name_human = 'Unknown'
    """The human readable name of the graph provider used for UI identification."""
    graph_title = 'Unknown'
    """The title that will be given to the graph."""
    table_subscriptions = []
    """A list of tables from which information is needed to produce the graph."""
    is_available = True

    def __init__(self, application, size_request=None, style_context=None):
        """
		:param tuple size_request: The size to set for the canvas.
		"""
        self.application = application
        self.style_context = style_context
        self.config = application.config
        """A reference to the King Phisher client configuration."""
        self.figure, _ = pyplot.subplots()
        self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE))
        self.axes = self.figure.get_axes()
        self.canvas = FigureCanvas(self.figure)
        self.manager = None
        self.minimum_size = (380, 200)
        """An absolute minimum size for the canvas."""
        if size_request is not None:
            self.resize(*size_request)
        self.canvas.mpl_connect('button_press_event',
                                self.mpl_signal_canvas_button_pressed)
        self.canvas.show()
        self.navigation_toolbar = NavigationToolbar(
            self.canvas, self.application.get_active_window())
        self.popup_menu = Gtk.Menu.new()

        menu_item = Gtk.MenuItem.new_with_label('Export')
        menu_item.connect('activate', self.signal_activate_popup_menu_export)
        self.popup_menu.append(menu_item)

        menu_item = Gtk.MenuItem.new_with_label('Refresh')
        menu_item.connect('activate', self.signal_activate_popup_refresh)
        self.popup_menu.append(menu_item)

        menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
        menu_item.connect('toggled',
                          self.signal_toggled_popup_menu_show_toolbar)
        self._menu_item_show_toolbar = menu_item
        self.popup_menu.append(menu_item)
        self.popup_menu.show_all()
        self.navigation_toolbar.hide()
        self._legend = None

    @property
    def rpc(self):
        return self.application.rpc

    @staticmethod
    def _ax_hide_ticks(ax):
        for tick in ax.yaxis.get_major_ticks():
            tick.tick1On = False
            tick.tick2On = False

    @staticmethod
    def _ax_set_spine_color(ax, spine_color):
        for pos in ('top', 'right', 'bottom', 'left'):
            ax.spines[pos].set_color(spine_color)

    def add_legend_patch(self, legend_rows, fontsize=None):
        if self._legend is not None:
            self._legend.remove()
            self._legend = None
        fontsize = fontsize or self.fontsize_scale
        legend_bbox = self.figure.legend(tuple(
            patches.Patch(color=patch_color)
            for patch_color, _ in legend_rows),
                                         tuple(label
                                               for _, label in legend_rows),
                                         borderaxespad=1.25,
                                         fontsize=fontsize,
                                         frameon=True,
                                         handlelength=1.5,
                                         handletextpad=0.75,
                                         labelspacing=0.3,
                                         loc='lower right')
        legend_bbox.legendPatch.set_linewidth(0)
        self._legend = legend_bbox

    def get_color(self, color_name, default):
        """
		Get a color by its style name such as 'fg' for foreground. If the
		specified color does not exist, default will be returned. The underlying
		logic for this function is provided by
		:py:func:`~.gui_utilities.gtk_style_context_get_color`.

		:param str color_name: The style name of the color.
		:param default: The default color to return if the specified one was not found.
		:return: The desired color if it was found.
		:rtype: tuple
		"""
        color_name = 'theme_color_graph_' + color_name
        sc_color = gui_utilities.gtk_style_context_get_color(
            self.style_context, color_name, default)
        return (sc_color.red, sc_color.green, sc_color.blue)

    def make_window(self):
        """
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
        if self.manager is None:
            self.manager = FigureManager(self.canvas, 0)
        self.navigation_toolbar.destroy()
        self.navigation_toolbar = self.manager.toolbar
        self._menu_item_show_toolbar.set_active(True)
        window = self.manager.window
        window.set_transient_for(self.application.get_active_window())
        window.set_title(self.graph_title)
        return window

    @property
    def fontsize_scale(self):
        scale = self.markersize_scale
        if scale < 5:
            fontsize = 'xx-small'
        elif scale < 7:
            fontsize = 'x-small'
        elif scale < 9:
            fontsize = 'small'
        else:
            fontsize = 'medium'
        return fontsize

    @property
    def markersize_scale(self):
        bbox = self.axes[0].get_window_extent().transformed(
            self.figure.dpi_scale_trans.inverted())
        return bbox.width * self.figure.dpi * 0.01

    def mpl_signal_canvas_button_pressed(self, event):
        if event.button != 3:
            return
        self.popup_menu.popup(None, None, None, None, event.button,
                              Gtk.get_current_event_time())
        return True

    def signal_activate_popup_menu_export(self, action):
        dialog = extras.FileChooserDialog('Export Graph',
                                          self.application.get_active_window())
        file_name = self.config['campaign_name'] + '.png'
        response = dialog.run_quick_save(file_name)
        dialog.destroy()
        if not response:
            return
        destination_file = response['target_path']
        self.figure.savefig(destination_file,
                            dpi=200,
                            facecolor=self.figure.get_facecolor(),
                            format='png')

    def signal_activate_popup_refresh(self, event):
        self.refresh()

    def signal_toggled_popup_menu_show_toolbar(self, widget):
        if widget.get_property('active'):
            self.navigation_toolbar.show()
        else:
            self.navigation_toolbar.hide()

    def resize(self, width=0, height=0):
        """
		Attempt to resize the canvas. Regardless of the parameters the canvas
		will never be resized to be smaller than :py:attr:`.minimum_size`.

		:param int width: The desired width of the canvas.
		:param int height: The desired height of the canvas.
		"""
        min_width, min_height = self.minimum_size
        width = max(width, min_width)
        height = max(height, min_height)
        self.canvas.set_size_request(width, height)
Beispiel #4
0
class CampaignGraph(object):
	"""
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
	name = 'Unknown'
	"""The name of the graph provider."""
	name_human = 'Unknown'
	"""The human readable name of the graph provider used for UI identification."""
	graph_title = 'Unknown'
	"""The title that will be given to the graph."""
	table_subscriptions = []
	"""A list of tables from which information is needed to produce the graph."""
	is_available = True
	def __init__(self, application, size_request=None, style_context=None):
		"""
		:param tuple size_request: The size to set for the canvas.
		"""
		self.application = application
		self.style_context = style_context
		self.config = application.config
		"""A reference to the King Phisher client configuration."""
		self.figure, _ = pyplot.subplots()
		self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE))
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.application.get_active_window())
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()
		self.navigation_toolbar.hide()
		self._legend = None

	@property
	def rpc(self):
		return self.application.rpc

	@staticmethod
	def _ax_hide_ticks(ax):
		for tick in ax.yaxis.get_major_ticks():
			tick.tick1On = False
			tick.tick2On = False

	@staticmethod
	def _ax_set_spine_color(ax, spine_color):
		for pos in ('top', 'right', 'bottom', 'left'):
			ax.spines[pos].set_color(spine_color)

	def _load_graph(self, info_cache):
		raise NotImplementedError()

	def add_legend_patch(self, legend_rows, fontsize=None):
		if self._legend is not None:
			self._legend.remove()
			self._legend = None
		if fontsize is None:
			scale = self.markersize_scale
			if scale < 5:
				fontsize = 'xx-small'
			elif scale < 7:
				fontsize = 'x-small'
			elif scale < 9:
				fontsize = 'small'
			else:
				fontsize = 'medium'
		legend_bbox = self.figure.legend(
			tuple(patches.Patch(color=patch_color) for patch_color, _ in legend_rows),
			tuple(label for _, label in legend_rows),
			borderaxespad=1.25,
			fontsize=fontsize,
			frameon=True,
			handlelength=1.5,
			handletextpad=0.75,
			labelspacing=0.3,
			loc='lower right'
		)
		legend_bbox.legendPatch.set_linewidth(0)
		self._legend = legend_bbox

	def get_color(self, color_name, default):
		"""
		Get a color by its style name such as 'fg' for foreground. If the
		specified color does not exist, default will be returned. The underlying
		logic for this function is provided by
		:py:func:`~.gui_utilities.gtk_style_context_get_color`.

		:param str color_name: The style name of the color.
		:param default: The default color to return if the specified one was not found.
		:return: The desired color if it was found.
		:rtype: tuple
		"""
		color_name = 'theme_color_graph_' + color_name
		sc_color = gui_utilities.gtk_style_context_get_color(self.style_context, color_name, default)
		return (sc_color.red, sc_color.green, sc_color.blue)

	def make_window(self):
		"""
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
		if self.manager is None:
			self.manager = FigureManager(self.canvas, 0)
		self.navigation_toolbar.destroy()
		self.navigation_toolbar = self.manager.toolbar
		self._menu_item_show_toolbar.set_active(True)
		window = self.manager.window
		window.set_transient_for(self.application.get_active_window())
		window.set_title(self.graph_title)
		return window

	@property
	def markersize_scale(self):
		bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
		return bbox.width * self.figure.dpi * 0.01

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = gui_utilities.FileChooser('Export Graph', self.application.get_active_window())
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_path']
		self.figure.savefig(destination_file, format='png')

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def load_graph(self):
		"""Load the graph information via :py:meth:`.refresh`."""
		self.refresh()

	def refresh(self, info_cache=None, stop_event=None):
		"""
		Refresh the graph data by retrieving the information from the
		remote server.

		:param dict info_cache: An optional cache of data tables.
		:param stop_event: An optional object indicating that the operation should stop.
		:type stop_event: :py:class:`threading.Event`
		:return: A dictionary of cached tables from the server.
		:rtype: dict
		"""
		info_cache = (info_cache or {})
		if not self.rpc:
			return info_cache
		for table in self.table_subscriptions:
			if stop_event and stop_event.is_set():
				return info_cache
			if not table in info_cache:
				info_cache[table] = tuple(self.rpc.remote_table(table, query_filter={'campaign_id': self.config['campaign_id']}))
		for ax in self.axes:
			ax.clear()
		if self._legend is not None:
			self._legend.remove()
			self._legend = None
		self._load_graph(info_cache)
		self.figure.suptitle(
			self.graph_title,
			color=self.get_color('fg', ColorHexCode.BLACK),
			size=14,
			weight='bold',
			y=0.97
		)
		self.canvas.draw()
		return info_cache
class MainWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Numeric methods")
        self.set_border_width(10)
        self.set_size_request(872, 657)
        #self.set_resizable(resizable=False)
        self.pl = PlotGraph()

        # Layout grid
        self.grid = Gtk.Grid(column_homogeneous=True,
                             column_spacing=10,
                             row_spacing=10)
        self.add(self.grid)

        # All entries (equation, x0, y0, step, bound)
        # equation
        self.equation = Gtk.Entry(placeholder_text="write your equation")
        self.equation.set_size_request(357, 47)

        # x0
        self.x_entry = Gtk.Entry(placeholder_text="initial point x")
        self.x_entry.set_size_request(134, 37)

        # y0
        self.y_entry = Gtk.Entry(placeholder_text="initial point y")
        self.y_entry.set_size_request(134, 37)

        # grid
        self.step = Gtk.Entry(placeholder_text="step")
        self.step.set_size_request(134, 37)

        # grid bound
        self.grid_bound = Gtk.Entry(placeholder_text="step bound")
        self.grid_bound.set_size_request(134, 37)

        # bound
        self.bound = Gtk.Entry(placeholder_text="x bound")
        self.bound.set_size_request(134, 37)

        # flag to check which error should be plotted(local or global)
        self.error_flag = False  # when False it means local, when True it means global

        # creating button
        self.button = Gtk.Button(label="PLOT GRAPH")
        self.button.connect("clicked", self.draw)
        self.button.set_size_request(115, 47)

        self.canvas = FigureCanvas(self.pl.fig)
        self.canvas.set_size_request(800, 600)

        # check buttons (for choice of methods that would be plotted)
        ex_box = Gtk.Box()
        label_ex = Gtk.Label("Exact")
        self.exact_check = Gtk.CheckButton()
        self.exact_check.connect("toggled", self.toggled)
        ex_box.pack_start(label_ex, True, True, 0)
        ex_box.pack_start(self.exact_check, True, True, 0)

        label_eul = Gtk.Label("Euler")
        eul_box = Gtk.Box()
        self.eul_check = Gtk.CheckButton()
        self.eul_check.connect("toggled", self.toggled)
        eul_box.pack_start(label_eul, True, True, 0)
        eul_box.pack_start(self.eul_check, True, True, 0)

        label_imp = Gtk.Label("Improved Eul")
        imp_box = Gtk.Box()
        self.imp_check = Gtk.CheckButton()
        self.imp_check.connect("toggled", self.toggled)
        imp_box.pack_start(label_imp, True, True, 0)
        imp_box.pack_start(self.imp_check, True, True, 0)

        label_kutta = Gtk.Label("Runge-Kutta  ")
        kutta_box = Gtk.Box()
        self.kutta_check = Gtk.CheckButton()
        self.kutta_check.connect("toggled", self.toggled)
        kutta_box.pack_start(label_kutta, True, True, 0)
        kutta_box.pack_start(self.kutta_check, True, True, 0)

        # global error canvas
        self.gl_canvas = FigureCanvas(self.pl.fig_gl_err)
        self.gl_canvas.set_size_request(800, 600)

        self.toolbar = NavigationToolbar(self.canvas, Gtk)
        self.toolbox = Gtk.Box()
        self.toolbox.add(self.toolbar)

        radio_box = Gtk.Box()
        loc_button = Gtk.RadioButton.new_with_label_from_widget(None, "Local")
        loc_button.connect("toggled", self.radio_toggled, "local", self)
        radio_box.pack_start(loc_button, False, False, 10)

        glob_button = Gtk.RadioButton.new_from_widget(loc_button)
        glob_button.set_label("Global")
        glob_button.connect("toggled", self.radio_toggled, "global", self)
        radio_box.pack_start(glob_button, False, False, 10)

        # Adding entries and buttons to grid
        self.grid.attach(self.equation, 2, 0, 3, 1)
        self.grid.attach(self.button, 4, 2, 1, 1)
        self.grid.attach(self.x_entry, 2, 1, 1, 1)
        self.grid.attach(self.y_entry, 3, 1, 1, 1)
        self.grid.attach(self.step, 2, 2, 1, 1)
        self.grid.attach(self.bound, 4, 1, 1, 1)
        self.grid.attach(self.toolbox, 2, 3, 1, 1)
        self.grid.attach(self.canvas, 2, 4, 3, 4)
        self.grid.attach(self.gl_canvas, 5, 4, 3, 4)
        self.grid.attach(ex_box, 5, 0, 1, 1)
        self.grid.attach(eul_box, 5, 1, 1, 1)
        self.grid.attach(imp_box, 7, 0, 1, 1)
        self.grid.attach(kutta_box, 7, 1, 1, 1)
        self.grid.attach(self.grid_bound, 3, 2, 1, 1)
        self.grid.attach(radio_box, 6, 2, 1, 1)

    @staticmethod
    def toggled(check_butt):
        if check_butt.get_active():
            print("Active")

    @staticmethod
    def radio_toggled(button, name, activity):
        if button.get_active() and name == "global":
            activity.error_flag = True
        else:
            activity.error_flag = False

    def draw(self, wid):
        # draw the graph on ui, click listener on button 'plot graph'
        self.grid.remove_row(5)

        if self.exact_check.get_active():
            self.pl.ex_flag = True
        else:
            self.pl.ex_flag = False

        if self.kutta_check.get_active():
            self.pl.kutta_flag = True
        else:
            self.pl.kutta_flag = False

        if self.eul_check.get_active():
            self.pl.eul_flag = True
        else:
            self.pl.eul_flag = False

        if self.imp_check.get_active():
            self.pl.imp_flag = True
        else:
            self.pl.imp_flag = False

        # check if method is chosen
        if not self.pl.imp_flag and not self.pl.kutta_flag and not self.pl.eul_flag:
            dialog = Gtk.MessageDialog(
                self, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
                "You should choose at least one method")
            dialog.format_secondary_text("No methods are chosen")
            dialog.run()
            dialog.destroy()
            return

        # user input (some bad code style, need to create function for checking)
        try:
            x0 = float(self.x_entry.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong initial x")
            dialog.format_secondary_text("Your initial x is not a number")
            dialog.run()
            dialog.destroy()
            return

        try:
            y0 = float(self.y_entry.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong initial y")
            dialog.format_secondary_text("Your initial y is not a number")
            dialog.run()
            dialog.destroy()
            return

        try:
            bound = float(self.bound.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong x bound")
            dialog.format_secondary_text("Your x bound is not a number")
            dialog.run()
            dialog.destroy()
            return

        try:
            step = float(self.step.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong step")
            dialog.format_secondary_text("Your step is not a number")
            dialog.run()
            dialog.destroy()
            return

        try:
            step_bound = float(self.grid_bound.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong step bound")
            dialog.format_secondary_text("Your step bound is not a number")
            dialog.run()
            dialog.destroy()
            return

        equation = str(self.equation.get_text())

        self.pl.init_methods(x0, y0, bound, step, equation)
        self.pl.plot_graph()
        self.canvas = FigureCanvas(self.pl.fig)
        self.canvas.show()

        self.toolbar.canvas = self.canvas
        self.toolbar.show()

        if self.error_flag:
            self.pl.init_global(x0, y0, bound, step, step_bound, equation)
            self.pl.plot_global()
            self.gl_canvas = FigureCanvas(self.pl.fig_global)
            self.gl_canvas.show()
        else:
            self.pl.init_gl_error()
            self.pl.plot_gll_err()
            self.gl_canvas = FigureCanvas(self.pl.fig_gl_err)
            self.gl_canvas.show()

        self.grid.attach(self.canvas, 2, 4, 3, 4)
        self.grid.attach(self.gl_canvas, 5, 4, 3, 4)
        self.show_all()
class CampaignGraph(object):
	title = 'Unknown'
	_graph_id = None
	table_subscriptions = []
	def __init__(self, config, parent, size_request=None):
		self.config = config
		self.parent = parent
		self.figure, ax = pyplot.subplots()
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
		self.navigation_toolbar.hide()
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()

	@classmethod
	def get_graph_id(klass):
		return klass._graph_id

	def make_window(self):
		if self.manager == None:
			self.manager = FigureManager(self.canvas, 0)
		window = self.manager.window
		window.set_transient_for(self.parent)
		window.set_title(self.title)
		return window

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		pos_func = lambda m, d: (event.x, event.y, True)
		self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = gui_utilities.UtilityFileChooser('Export Graph', self.parent)
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_filename']
		self.figure.savefig(destination_file, format='png')

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def load_graph(self):
		self.refresh()

	def refresh(self, info_cache=None):
		info_cache = (info_cache or {})
		if not self.parent.rpc:
			return info_cache
		for table in self.table_subscriptions:
			if not table in info_cache:
				info_cache[table] = list(self.parent.rpc.remote_table('campaign/' + table, self.config['campaign_id']))
		map(lambda ax: ax.clear(), self.axes)
		self._load_graph(info_cache)
		self.canvas.draw()
		return info_cache
Beispiel #7
0
class CampaignGraph(object):
	"""
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
	name = 'Unknown'
	"""The name of the graph provider."""
	name_human = 'Unknown'
	"""The human readable name of the graph provider used for UI identification."""
	graph_title = 'Unknown'
	"""The title that will be given to the graph."""
	table_subscriptions = []
	"""A list of tables from which information is needed to produce the graph."""
	is_available = True
	def __init__(self, config, parent, size_request=None):
		"""
		:param dict config: The King Phisher client configuration.
		:param parent: The parent window for this object.
		:type parent: :py:class:`Gtk.Window`
		:param tuple size_request: The size to set for the canvas.
		"""
		self.config = config
		"""A reference to the King Phisher client configuration."""
		self.parent = parent
		"""The parent :py:class:`Gtk.Window` instance."""
		self.figure, _ = pyplot.subplots()
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()
		self.navigation_toolbar.hide()

	def _load_graph(self, info_cache):
		raise NotImplementedError()

	def _graph_bar_set_yparams(self, top_lim):
		min_value = top_lim + (top_lim * 0.075)
		if min_value <= 25:
			scale = 5
		else:
			scale = scale = 10 ** (len(str(int(min_value))) - 1)
		inc_scale = scale
		while scale <= min_value:
			scale += inc_scale
		top_lim = scale

		ax = self.axes[0]
		yticks = set((round(top_lim * 0.5), top_lim))
		ax.set_yticks(tuple(yticks))
		ax.set_ylim(top=top_lim)
		return

	def _graph_null_pie(self, title):
		ax = self.axes[0]
		ax.pie((100,), labels=(title,), colors=(MPL_COLOR_NULL,), autopct='%1.0f%%', shadow=True, startangle=90)
		ax.axis('equal')
		return

	def add_legend_patch(self, legend_rows, fontsize=None):
		handles = []
		if not fontsize:
			scale = self.markersize_scale
			if scale < 5:
				fontsize = 'xx-small'
			elif scale < 7:
				fontsize = 'x-small'
			elif scale < 9:
				fontsize = 'small'
			else:
				fontsize = 'medium'
		for row in legend_rows:
			handles.append(patches.Patch(color=row[0], label=row[1]))
		self.axes[0].legend(handles=handles, fontsize=fontsize, loc='lower right')

	def graph_bar(self, bars, color=None, xticklabels=None, ylabel=None):
		"""
		Create a standard bar graph with better defaults for the standard use
		cases.

		:param list bars: The values of the bars to graph.
		:param color: The color of the bars on the graph.
		:type color: list, str
		:param list xticklabels: The labels to use on the x-axis.
		:param str ylabel: The label to give to the y-axis.
		:return: The bars created using :py:mod:`matplotlib`
		:rtype: `matplotlib.container.BarContainer`
		"""
		color = color or MPL_COLOR_NULL
		width = 0.25
		ax = self.axes[0]
		self._graph_bar_set_yparams(max(bars) if bars else 0)
		bars = ax.bar(range(len(bars)), bars, width, color=color)
		ax.set_xticks([float(x) + (width / 2) for x in range(len(bars))])
		if xticklabels:
			ax.set_xticklabels(xticklabels, rotation=30)
			for col in bars:
				height = col.get_height()
				ax.text(col.get_x() + col.get_width() / 2.0, height, "{0:,}".format(height), ha='center', va='bottom')
		if ylabel:
			ax.set_ylabel(ylabel)
		self.figure.subplots_adjust(bottom=0.25)
		return bars

	def make_window(self):
		"""
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
		if self.manager == None:
			self.manager = FigureManager(self.canvas, 0)
		self.navigation_toolbar.destroy()
		self.navigation_toolbar = self.manager.toolbar
		self._menu_item_show_toolbar.set_active(True)
		window = self.manager.window
		window.set_transient_for(self.parent)
		window.set_title(self.graph_title)
		return window

	@property
	def markersize_scale(self):
		bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
		return max(bbox.width, bbox.width) * self.figure.dpi * 0.01

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = gui_utilities.FileChooser('Export Graph', self.parent)
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_path']
		self.figure.savefig(destination_file, format='png')

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def load_graph(self):
		"""Load the graph information via :py:meth:`.refresh`."""
		self.refresh()

	def refresh(self, info_cache=None, stop_event=None):
		"""
		Refresh the graph data by retrieving the information from the
		remote server.

		:param dict info_cache: An optional cache of data tables.
		:param stop_event: An optional object indicating that the operation should stop.
		:type stop_event: :py:class:`threading.Event`
		:return: A dictionary of cached tables from the server.
		:rtype: dict
		"""
		info_cache = (info_cache or {})
		if not self.parent.rpc:
			return info_cache
		for table in self.table_subscriptions:
			if stop_event and stop_event.is_set():
				return info_cache
			if not table in info_cache:
				info_cache[table] = tuple(self.parent.rpc.remote_table('campaign/' + table, self.config['campaign_id']))
		for ax in self.axes:
			ax.clear()
		self._load_graph(info_cache)
		self.axes[0].set_title(self.graph_title, y=1.03)
		self.canvas.draw()
		return info_cache
Beispiel #8
0
class CampaignGraph(object):
    """
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
    title = 'Unknown'
    """The title of the graph."""
    _graph_id = None
    table_subscriptions = []
    """A list of tables from which information is needed to produce the graph."""
    def __init__(self, config, parent, size_request=None):
        """
		:param dict config: The King Phisher client configuration.
		:param parent: The parent window for this object.
		:type parent: :py:class:`Gtk.Window`
		:param tuple size_request: The size to set for the canvas.
		"""
        self.config = config
        """A reference to the King Phisher client configuration."""
        self.parent = parent
        """The parent :py:class:`Gtk.Window` instance."""
        self.figure, ax = pyplot.subplots()
        self.axes = self.figure.get_axes()
        self.canvas = FigureCanvas(self.figure)
        self.manager = None
        if size_request:
            self.canvas.set_size_request(*size_request)
        self.canvas.mpl_connect('button_press_event',
                                self.mpl_signal_canvas_button_pressed)
        self.canvas.show()
        self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
        self.navigation_toolbar.hide()
        self.popup_menu = Gtk.Menu.new()

        menu_item = Gtk.MenuItem.new_with_label('Export')
        menu_item.connect('activate', self.signal_activate_popup_menu_export)
        self.popup_menu.append(menu_item)

        menu_item = Gtk.MenuItem.new_with_label('Refresh')
        menu_item.connect('activate', lambda action: self.refresh())
        self.popup_menu.append(menu_item)

        menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
        menu_item.connect('toggled',
                          self.signal_toggled_popup_menu_show_toolbar)
        self.popup_menu.append(menu_item)
        self.popup_menu.show_all()

    @classmethod
    def get_graph_id(klass):
        """
		The graph id of an exported :py:class:`.CampaignGraph`.

		:param klass: The class to return the graph id of.
		:type klass: :py:class:`.CampaignGraph`
		:return: The id of the graph.
		:rtype: int
		"""
        return klass._graph_id

    def make_window(self):
        """
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
        if self.manager == None:
            self.manager = FigureManager(self.canvas, 0)
        window = self.manager.window
        window.set_transient_for(self.parent)
        window.set_title(self.title)
        return window

    def mpl_signal_canvas_button_pressed(self, event):
        if event.button != 3:
            return
        self.popup_menu.popup(None, None, None, None, event.button,
                              Gtk.get_current_event_time())
        return True

    def signal_activate_popup_menu_export(self, action):
        dialog = gui_utilities.UtilityFileChooser('Export Graph', self.parent)
        file_name = self.config['campaign_name'] + '.png'
        response = dialog.run_quick_save(file_name)
        dialog.destroy()
        if not response:
            return
        destination_file = response['target_path']
        self.figure.savefig(destination_file, format='png')

    def signal_toggled_popup_menu_show_toolbar(self, widget):
        if widget.get_property('active'):
            self.navigation_toolbar.show()
        else:
            self.navigation_toolbar.hide()

    def load_graph(self):
        """Load the graph information via :py:meth:`.refresh`."""
        self.refresh()

    def refresh(self, info_cache=None, stop_event=None):
        """
		Refresh the graph data by retrieving the information from the
		remote server.

		:param dict info_cache: An optional cache of data tables.
		:param stop_event: An optional object indicating that the operation should stop.
		:type stop_event: :py:class:`threading.Event`
		:return: A dictionary of cached tables from the server.
		:rtype: dict
		"""
        info_cache = (info_cache or {})
        if not self.parent.rpc:
            return info_cache
        for table in self.table_subscriptions:
            if stop_event and stop_event.is_set():
                return info_cache
            if not table in info_cache:
                info_cache[table] = list(
                    self.parent.rpc.remote_table('campaign/' + table,
                                                 self.config['campaign_id']))
        map(lambda ax: ax.clear(), self.axes)
        self._load_graph(info_cache)
        self.canvas.draw()
        return info_cache