diff --git a/tryton/common/cellrendererbinary.py b/tryton/common/cellrendererbinary.py new file mode 100644 index 0000000000..d9211555d4 --- /dev/null +++ b/tryton/common/cellrendererbinary.py @@ -0,0 +1,122 @@ +#This file is part of Tryton. The COPYRIGHT file at the top level of +#this repository contains the full copyright notices and license terms. +import gtk +import gobject + + +class CellRendererBinary(gtk.GenericCellRenderer): + __gproperties__ = { + 'visible': (gobject.TYPE_INT, 'Visible', 'Visible', 0, 10, 0, + gobject.PARAM_READWRITE), + 'editable': (gobject.TYPE_INT, 'Editable', 'Editable', 0, 10, 0, + gobject.PARAM_READWRITE), + 'content': (gobject.TYPE_INT, 'Content', 'Content', 0, 10, 0, + gobject.PARAM_READWRITE), + } + __gsignals__ = { + 'new': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), + 'open': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), + 'save': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), + 'clear': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), + } + + def __init__(self, use_filename): + self.__gobject_init__() + self.visible = True + self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE) + self.use_filename = use_filename + if use_filename: + self.buttons = ('new', 'open', 'save', 'clear') + else: + self.buttons = ('new', 'save', 'clear') + self.clicking = '' + self.images = {} + widget = gtk.Button() + for key, stock_name in ( + ('new', 'tryton-find'), + ('open', 'tryton-open'), + ('save', 'tryton-save-as'), + ('clear', 'tryton-clear')): + # hack to get gtk.gdk.Image from stock icon + img_sensitive = widget.render_icon(stock_name, + gtk.ICON_SIZE_SMALL_TOOLBAR) + img_insensitive = img_sensitive.copy() + img_sensitive.saturate_and_pixelate(img_insensitive, 0, False) + width = img_sensitive.get_width() + self.images[key] = (img_sensitive, img_insensitive, width) + + def do_set_property(self, pspec, value): + setattr(self, pspec.name, value) + + def do_get_property(self, pspec): + return getattr(self, pspec.name) + + def button_width(self, cell_area): + return ((cell_area.width - (len(self.buttons) + 1) * 2) + / len(self.buttons)) + + def on_get_size(self, widget, cell_area=None): + if cell_area is None: + return (0, 0, 30, 18) + else: + return (cell_area.x, cell_area.y, cell_area.width, cell_area.height) + + def on_start_editing(self, event, widget, path, background_area, + cell_area, flags): + button_width = self.button_width(cell_area) + for index, button_name in enumerate(self.buttons): + x_offset = button_width * index + 2 * (index + 1) + x_button = cell_area.x + x_offset + if x_button < event.x < x_button + button_width: + break + else: + button_name = None + if not self.visible or not button_name: + return + if not self.editable and button_name in ('new', 'clear'): + return + if not self.content and button_name == 'save': + return + if event is None or event.type == gtk.gdk.BUTTON_PRESS: + self.clicking = button_name + self.emit(button_name, path) + def timeout(self, widget): + self.clicking = '' + widget.queue_draw() + gobject.timeout_add(60, timeout, self, widget) + + def on_render(self, window, widget, background_area, cell_area, + expose_area, flags): + if not self.visible: + return + # Handle Pixmap window as pygtk failed + if type(window) == gtk.gdk.Pixmap: + return + button_width = self.button_width(cell_area) + for index, button_name in enumerate(self.buttons): + state = gtk.STATE_NORMAL + shadow = gtk.SHADOW_OUT + pxbf_sens, pxbf_insens, pxbf_width = self.images[button_name] + if self.clicking and flags & gtk.CELL_RENDERER_SELECTED: + state = gtk.STATE_ACTIVE + shadow = gtk.SHADOW_IN + if (not self.editable and button_name in ('new', 'clear') + or not self.content and button_name in ('open', 'save')): + state = gtk.STATE_INSENSITIVE + pixbuf = pxbf_insens + else: + pixbuf = pxbf_sens + x_offset = button_width * index + 2 * (index + 1) + widget.style.paint_box(window, state, shadow, + None, widget, "button", cell_area.x + x_offset, cell_area.y, + button_width, cell_area.height) + window.draw_pixbuf(widget.style.black_gc, + pixbuf, 0, 0, + cell_area.x + x_offset + button_width / 2 - pxbf_width / 2, + cell_area.y) + +gobject.type_register(CellRendererBinary) diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py index 69d93e7d10..30b4dee97e 100644 --- a/tryton/gui/window/view_form/view/list.py +++ b/tryton/gui/window/view_form/view/list.py @@ -17,6 +17,7 @@ import tryton.common as common from tryton.config import CONFIG from tryton.common.cellrendererbutton import CellRendererButton from tryton.common.cellrenderertoggle import CellRendererToggle +from tryton.common.cellrendererbinary import CellRendererBinary from tryton.pyson import PYSONEncoder from tryton.gui.window import Window from tryton.exceptions import TrytonServerError @@ -797,7 +798,7 @@ class ViewList(ParserView): editable = renderer.get_property('activatable') elif isinstance(renderer, (gtk.CellRendererProgress, CellRendererButton, - gtk.CellRendererPixbuf)): + gtk.CellRendererPixbuf, CellRendererBinary)): editable = False else: editable = renderer.get_property('editable') diff --git a/tryton/gui/window/view_form/view/list_gtk/parser.py b/tryton/gui/window/view_form/view/list_gtk/parser.py index f5440280ee..120dd36cd2 100644 --- a/tryton/gui/window/view_form/view/list_gtk/parser.py +++ b/tryton/gui/window/view_form/view/list_gtk/parser.py @@ -1,7 +1,9 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. +import os import gobject +import tempfile from editabletree import EditableTreeView from tryton.gui.window.view_form.view.interface import ParserInterface from tryton.gui.window.win_search import WinSearch @@ -10,7 +12,7 @@ from tryton.gui.window.view_form.screen import Screen from tryton.config import CONFIG import tryton.rpc as rpc from tryton.common import DT_FORMAT, DHM_FORMAT, COLORS, node_attributes, \ - TRYTON_ICON, HM_FORMAT + TRYTON_ICON, HM_FORMAT, file_selection, file_open import tryton.common as common from tryton.exceptions import TrytonError, TrytonServerError from tryton.common.cellrendererbutton import CellRendererButton @@ -20,6 +22,7 @@ from tryton.common.cellrenderertoggle import CellRendererToggle from tryton.common.cellrenderercombo import CellRendererCombo from tryton.common.cellrendererinteger import CellRendererInteger from tryton.common.cellrendererfloat import CellRendererFloat +from tryton.common.cellrendererbinary import CellRendererBinary from tryton.action import Action from tryton.translate import date_format from tryton.pyson import PYSONDecoder @@ -108,7 +111,7 @@ class ParserTree(ParserInterface): continue for attr_name in ('relation', 'domain', 'selection', 'relation_field', 'string', 'views', 'invisible', - 'add_remove', 'sort', 'context'): + 'add_remove', 'sort', 'context', 'filename'): if attr_name in fields[fname].attrs and \ not attr_name in node_attrs: node_attrs[attr_name] = fields[fname].attrs[attr_name] @@ -127,7 +130,8 @@ class ParserTree(ParserInterface): pass else: renderer.set_property('editable', not readonly) - if not readonly: + if (not readonly + and not isinstance(renderer, CellRendererBinary)): renderer.connect_after('editing-started', send_keys, treeview) @@ -505,6 +509,76 @@ class FloatTime(Char): if callback: callback() + +class Binary(Char): + + def __init__(self, field_name, model_name, treeview, attrs=None): + super(Binary, self).__init__(field_name, model_name, treeview, + attrs=attrs) + self.filename = attrs.get('filename') + self.renderer = CellRendererBinary(bool(self.filename)) + self.renderer.connect('new', self.new_binary) + self.renderer.connect('open', self.open_binary) + self.renderer.connect('save', self.save_binary) + self.renderer.connect('clear', self.clear_binary) + + def get_textual_value(self, record): + pass + + def value_from_text(self, record, text, callback=None): + if callback: + callback() + + def setter(self, column, cell, store, iter): + record = store.get_value(iter, 0) + size = record[self.field_name].get_size(record) + cell.set_property('content', bool(size)) + + def _get_record_field(self, path): + store = self.treeview.get_model() + record = store.get_value(store.get_iter(path), 0) + field = record.group.fields[self.field_name] + return record, field + + def new_binary(self, renderer, path): + filename = file_selection(_('Open...')) + record, field = self._get_record_field(path) + if filename: + field.set_client(record, open(filename, 'rb').read()) + + def open_binary(self, renderer, path): + if not self.filename: + return + dtemp = tempfile.mkdtemp(prefix='tryton_') + record, field = self._get_record_field(path) + filename_field = record.group.fields.get(self.filename) + filename = filename_field.get(record).replace( + os.sep, '_').replace(os.altsep or os.sep, '_') + file_path = os.path.join(dtemp, filename) + with open(file_path, 'wb') as fp: + fp.write(field.get_data(record)) + root, type_ = os.path.splitext(filename) + if type_: + type_ = type_[1:] + file_open(file_path, type_) + + def save_binary(self, renderer, path): + filename = '' + record, field = self._get_record_field(path) + if self.filename: + filename_field = record.group.fields.get(self.filename) + filename = filename_field.get(record) + filename = file_selection(_('Save As...'), filename=filename, + action=gtk.FILE_CHOOSER_ACTION_SAVE) + if filename: + with open(filename,'wb') as fp: + fp.write(field.get_data(record)) + + def clear_binary(self, renderer, path): + record, field = self._get_record_field(path) + field.set_client(record, False) + + class M2O(Char): def value_from_text(self, record, text, callback=None): @@ -994,4 +1068,5 @@ CELLTYPES = { 'progressbar': ProgressBar, 'reference': Reference, 'one2one': O2O, + 'binary': Binary, }