Index: radialnet/gui/SaveDialog.py =================================================================== --- radialnet/gui/SaveDialog.py (revision 0) +++ radialnet/gui/SaveDialog.py (revision 0) @@ -0,0 +1,78 @@ +# vim: set encoding=utf-8 : + +# Copyright (C) 2009 Adriano Monteiro Marques +# +# Author: João Paulo de Souza Medeiros +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import gtk +import radialnet.gui.RadialNet as RadialNet + +from zenmapGUI.higwidgets.higdialogs import HIGAlertDialog +from zenmapGUI.higwidgets.higlabels import HIGSectionLabel +from zenmapGUI.higwidgets.higboxes import HIGHBox, HIGVBox + + +TYPES = {"PDF - Portable Document Format": (RadialNet.FILE_TYPE_PDF,".pdf"), + "PNG - Portable Network Graphics": (RadialNet.FILE_TYPE_PNG,".png"), + "PS - PostScript": (RadialNet.FILE_TYPE_PS,".ps"), + "SVG - Scalable Vectorial Graphics": (RadialNet.FILE_TYPE_SVG,".svg")} + + +class SaveDialog(gtk.FileChooserDialog): + def __init__(self): + """ + """ + super(SaveDialog, self).__init__(title=_("Save Topology"), + action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK)) + + self.__combo = gtk.combo_box_new_text() + + types_list = TYPES.keys() + types_list.sort() + + for i in types_list: + self.__combo.append_text(i) + + self.__combo.set_active(1) + self.__label = gtk.Label(_("Select the output type:")) + + self.__hbox = HIGHBox() + self.__hbox._pack_noexpand_nofill(self.__label) + self.__hbox._pack_expand_fill(self.__combo) + + self.set_extra_widget(self.__hbox) + self.set_do_overwrite_confirmation(True) + + self.__hbox.show_all() + + def show_error(self): + """ + """ + alert = HIGAlertDialog(parent=self, + type=gtk.MESSAGE_ERROR, + message_format=_("Can't create file"), + secondary_text=_("Please check if you have permission to " + "write this file.")) + alert.run() + alert.destroy() + + def get_filetype(self): + """ + """ + return TYPES[self.__combo.get_active_text()] Index: radialnet/gui/Image.py =================================================================== --- radialnet/gui/Image.py (revision 13290) +++ radialnet/gui/Image.py (working copy) @@ -21,10 +21,45 @@ import os import sys import gtk +import array from zenmapCore.Paths import Path +FORMAT_RGBA = 4 +FORMAT_RGB = 3 + + +def get_pixels_for_cairo_image_surface(pixbuf): + """ + This method return the imgage stride and a python array.ArrayType + containing the icon pixels of a gtk.gdk.Pixbuf that can be used by + cairo.ImageSurface.create_for_data() method. + """ + data = array.ArrayType('c') + format = pixbuf.get_rowstride() / pixbuf.get_width() + + i = 0 + j = 0 + while i < len(pixbuf.get_pixels()): + + b, g, r = pixbuf.get_pixels()[i:i+FORMAT_RGB] + + if format == FORMAT_RGBA: + a = pixbuf.get_pixels()[i + FORMAT_RGBA - 1] + elif format == FORMAT_RGB: + a = '\xff' + else: + raise TypeError, 'unknown image format' + + data[j:j+FORMAT_RGBA] = array.ArrayType('c', [r, g, b, a]) + + i += format + j += FORMAT_RGBA + + return (FORMAT_RGBA * pixbuf.get_width(), data) + + class Image: """ """ Index: radialnet/gui/Toolbar.py =================================================================== --- radialnet/gui/Toolbar.py (revision 13290) +++ radialnet/gui/Toolbar.py (working copy) @@ -18,10 +18,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import os import gtk import gobject from radialnet.bestwidgets.buttons import * +from radialnet.gui.SaveDialog import SaveDialog from radialnet.gui.Dialogs import AboutDialog from radialnet.gui.HostsViewer import HostsViewer @@ -120,7 +122,10 @@ #self.__tools_button.set_is_important(True) #self.__tools_button.set_menu(self.__tools_menu) #self.__tools_button.connect('clicked', self.__tools_callback) - + + self.__save_button = BWStockButton(gtk.STOCK_SAVE, _("Save Snapshot")) + self.__save_button.connect("clicked", self.__save_image_callback) + self.__hosts_button = BWStockButton(gtk.STOCK_INDEX, _("Hosts Viewer")) self.__hosts_button.connect("clicked", self.__hosts_viewer_callback) @@ -159,6 +164,7 @@ #self.insert(self.__about, 7) #self.pack_start(self.__tools_button, False) + self.pack_start(self.__save_button, False) self.pack_start(self.__hosts_button, False) self.pack_start(self.__fisheye, False) self.pack_start(self.__control, False) @@ -194,6 +200,43 @@ window = HostsViewer(self.radialnet.get_scanned_nodes()) window.show_all() window.set_keep_above(True) + + + def __save_image_callback(self, widget): + """ + """ + self.__save_chooser = SaveDialog() + + while self.__save_chooser.run() == gtk.RESPONSE_OK: + + type, ext = self.__save_chooser.get_filetype() + file = self.__save_chooser.get_filename() + + if not os.path.exists(file): + file += ext + if os.path.exists(file): + # This hack is a deep inspection in gtk.FileChooser to get + # the save button. If the file with the extension exists we + # set the filename set a timeout callback to save button + # clicked event and call the dialog again. + # TODO: Be sure that the timeout will be called after the + # dialog run method. + self.__save_chooser.set_filename(file) + save = self.__save_chooser.action_area.get_children()[0] + gobject.timeout_add(REFRESH_RATE, save.clicked) + continue + + try: + open(file, "w").close() + except: + self.__save_chooser.show_error() + continue + + self.radialnet.save_drawing_to_file(file, type) + break + + self.__save_chooser.destroy() + self.__save_chooser = None def __control_callback(self, widget=None): Index: radialnet/gui/RadialNet.py =================================================================== --- radialnet/gui/RadialNet.py (revision 13290) +++ radialnet/gui/RadialNet.py (working copy) @@ -1,8 +1,8 @@ -# vim: set fileencoding=utf-8 : +# vim: set encoding=utf-8 : # Copyright (C) 2007, 2008 Insecure.Com LLC. # -# Author: João Paulo de Souza Medeiros +# Author: João Paulo de Souza Medeiros # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ import math import time import copy +import cairo import gobject import radialnet.util.drawing as drawing @@ -32,7 +33,7 @@ from radialnet.core.Interpolation import Linear2DInterpolator from radialnet.core.Graph import Graph, Node from radialnet.gui.NodeWindow import NodeWindow -from radialnet.gui.Image import Icons +from radialnet.gui.Image import Icons, get_pixels_for_cairo_image_surface REGION_COLORS = [(1.0, 0.0, 0.0), (1.0, 1.0, 0.0), (0.0, 1.0, 0.0)] @@ -58,7 +59,12 @@ INTERPOLATION_CARTESIAN = 0 INTERPOLATION_POLAR = 1 +FILE_TYPE_PDF = 1 +FILE_TYPE_PNG = 2 +FILE_TYPE_PS = 3 +FILE_TYPE_SVG = 4 + class RadialNet(gtk.DrawingArea): """ Radial network visualization widget @@ -111,6 +117,8 @@ self.__last_group_node = None self.__pointer_status = POINTER_JUMP_TO + + self.__print_to_file = (None, None) self.__sorted_nodes = list() self.__reverse_sorted_nodes = list() @@ -173,6 +181,19 @@ return check_animation_status + def save_drawing_to_file(self, file, type=FILE_TYPE_PNG): + """ + """ + if type in [FILE_TYPE_PDF, FILE_TYPE_PNG, FILE_TYPE_PS, FILE_TYPE_SVG]: + + self.__print_to_file = (file, type) + self.queue_draw() + + return True + + return False + + def get_slow_inout(self): """ """ @@ -766,14 +787,54 @@ @rtype: boolean @return: Indicator of the event propagation """ - self.context = widget.window.cairo_create() + allocation = self.get_allocation() + file, type = self.__print_to_file + # If type is set write + if type: + if type == FILE_TYPE_PDF: + self.surface = cairo.PDFSurface(file, + allocation.width, + allocation.height) + elif type == FILE_TYPE_PNG: + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, + allocation.width, + allocation.height) + elif type == FILE_TYPE_PS: + self.surface = cairo.PSSurface(file, + allocation.width, + allocation.height) + elif type == FILE_TYPE_SVG: + self.surface = cairo.SVGSurface(file, + allocation.width, + allocation.height) + else: + raise TypeError, 'unknown surface type' + + self.context = cairo.Context(self.surface) + + else: + self.context = widget.window.cairo_create() + + # Drawing using the selected surface self.context.rectangle(*event.area) self.context.set_source_rgb(1.0, 1.0, 1.0) self.context.fill() self.__draw() + if type == FILE_TYPE_PNG: + self.surface.write_to_png(file) + + # Reset the self.__print_to_file values and queue a new draw + if type: + # Sometimes the SVGSurface doesn't write the content in file. To + # fix this we force flush and finish surface. + self.surface.flush() + self.surface.finish() + self.__print_to_file = (None, None) + self.queue_draw() + return False @@ -1063,9 +1124,23 @@ for icon in icons: - self.context.set_source_pixbuf(icon, - round(xc + x + x_gap), - round(yc - y + y_gap - 6)) + stride, data = get_pixels_for_cairo_image_surface(icon) + + # Cairo documentation says that the correct way to obtain a + # legal stride value is using the function + # cairo.ImageSurface.format_stride_for_width(). + # But this method is only available since cairo 1.6. So we are + # using the stride returned by + # get_pixels_for_cairo_image_surface() function. + surface = cairo.ImageSurface.create_for_data(data, + cairo.FORMAT_ARGB32, + icon.get_width(), + icon.get_height(), + stride) + + self.context.set_source_surface(surface, + round(xc + x + x_gap), + round(yc - y + y_gap - 6)) self.context.paint() x_gap += 13