openerp-dev-web team mailing list archive
-
openerp-dev-web team
-
Mailing list archive
-
Message #05866
[Merge] lp:~openerp-dev/openobject-addons/6.0-bug-751556-tta into lp:openobject-addons/6.0
Tejaskumar Tank (OpenERP) has proposed merging lp:~openerp-dev/openobject-addons/6.0-bug-751556-tta into lp:openobject-addons/6.0.
Requested reviews:
OpenERP Core Team (openerp)
Related bugs:
Bug #751556 in OpenERP GTK Client: "Filters doesn't work except on text fields"
https://bugs.launchpad.net/openobject-client/+bug/751556
For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-addons/6.0-bug-751556-tta/+merge/58938
--
The attached diff has been truncated due to its size.
https://code.launchpad.net/~openerp-dev/openobject-addons/6.0-bug-751556-tta/+merge/58938
Your team OpenERP R&D Team is subscribed to branch lp:~openerp-dev/openobject-addons/6.0-bug-751556-tta.
=== added file 'MANIFEST.in'
--- MANIFEST.in 1970-01-01 00:00:00 +0000
+++ MANIFEST.in 2011-04-25 08:38:27 +0000
@@ -0,0 +1,14 @@
+include MANIFEST.in
+include setup.cfg
+include Makefile.translation
+include configure
+include configure.makefile
+include rpminstall_sh.txt
+include mydistutils.py
+include msgfmt.py
+include setup.nsi
+graft bin
+graft man
+graft doc
+graft debian
+global-exclude *pyc *~
=== added file 'Makefile.translation'
--- Makefile.translation 1970-01-01 00:00:00 +0000
+++ Makefile.translation 2011-04-25 08:38:27 +0000
@@ -0,0 +1,24 @@
+LANG=fr
+PYTHON_FILES=$(shell find -name "*py")
+PYTHONC_FILES=$(shell find -name "*pyc")
+APP=openerp-client
+LANGS=$(shell for i in `find bin/po -name "*.po"`; do basename $$i | cut -d'.' -f1; done;)
+
+all:
+
+clean:
+ rm -f bin/*bak $(PYTHONC_FILES)
+ rm -f bin/openerp.gladep
+
+translate_get:
+ xgettext -k_ -kN_ -o bin/po/$(APP).pot $(PYTHON_FILES) bin/openerp.glade bin/win_error.glade
+
+translate_set:
+ for i in $(LANGS); do msgfmt bin/po/$$i.po -o bin/share/locale/$$i/LC_MESSAGES/$(APP).mo; done;
+
+merge:
+ for i in $(LANGS); do msgmerge bin/po/$$i.po bin/po/$(APP).pot -o bin/po/$$i.po --strict; done;
+
+test:
+ echo $(LANGS)
+
=== added directory 'bin'
=== added directory 'bin/SpiffGtkWidgets'
=== added directory 'bin/SpiffGtkWidgets/Calendar'
=== added file 'bin/SpiffGtkWidgets/Calendar/Calendar.py'
--- bin/SpiffGtkWidgets/Calendar/Calendar.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/Calendar.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,255 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import gtk
+import gobject
+import pango
+import calendar
+import datetime
+import time
+import util
+import hippo
+from SpiffGtkWidgets.color import to_int as c2i
+from CanvasDayRange import CanvasDayRange
+
+class Calendar(hippo.Canvas):
+ RANGE_WEEK = 1
+ RANGE_MONTH = 2
+ RANGE_CUSTOM = 3
+
+ def __init__(self, model, mode='month'):
+ """
+ Constructor.
+ """
+ hippo.Canvas.__init__(self)
+ self.root = hippo.CanvasBox()
+ self.set_root(self.root)
+
+ # Init the model and view options.
+ self.realized = False
+ self.model = model
+ self.selected = datetime.date(*time.localtime(time.time())[:3])
+ if mode == 'week':
+ self.range = self.RANGE_WEEK
+ self.visible_range = model.get_week(self.selected)
+ self.active_range = self.visible_range
+ else:
+ self.range = self.RANGE_MONTH
+ self.visible_range = model.get_month_weeks(self.selected)
+ self.active_range = model.get_month(self.selected)
+ # Widgets and canvas items.
+ self.range_item = None
+ self.colors = None
+ self.font = None
+
+ # Configure the canvas.
+ self.set_flags(gtk.CAN_FOCUS)
+ self.set_events(gtk.gdk.EXPOSURE_MASK
+ | gtk.gdk.BUTTON_PRESS_MASK
+ | gtk.gdk.BUTTON_RELEASE_MASK
+ | gtk.gdk.POINTER_MOTION_MASK
+ | gtk.gdk.POINTER_MOTION_HINT_MASK
+ | gtk.gdk.KEY_PRESS_MASK
+ | gtk.gdk.KEY_RELEASE_MASK
+ | gtk.gdk.ENTER_NOTIFY_MASK
+ | gtk.gdk.LEAVE_NOTIFY_MASK
+ | gtk.gdk.FOCUS_CHANGE_MASK)
+ self.connect_after('realize', self.on_realize)
+ self.connect ('size-allocate', self.on_size_allocate)
+ self.connect ('key-press-event', self.on_key_press_event)
+
+
+ def set_range(self, range):
+ self.range = range
+ self.refresh()
+
+
+ def set_custom_range(self,
+ start,
+ end,
+ active_start = None,
+ active_end = None):
+ if active_start is None:
+ active_start = start
+ if active_end is None:
+ active_end = end
+ self.range = self.RANGE_CUSTOM
+ self.visible_range = start, end
+ self.active_range = active_start, active_end
+ self.refresh()
+
+
+ def select(self, date):
+ self.selected = date
+ self.refresh()
+
+
+ def get_selected(self):
+ return self.selected
+
+
+ def on_realize(self, *args):
+ self.realized = True
+ self.grab_focus()
+ self.on_size_allocate(*args)
+
+
+ def on_size_allocate(self, *args):
+ alloc = self.get_allocation()
+ if not self.realized: # or alloc.width < 10 or alloc.height < 10:
+ return
+ #self.set_bounds(0, 0, alloc.width, alloc.height)
+
+ # Initialize colors.
+ if self.colors is not None:
+ return
+
+ style = self.get_style()
+ self.font = style.font_desc
+ self.colors = dict(bg = c2i(style.bg[gtk.STATE_PRELIGHT]),
+ text = c2i(style.fg[gtk.STATE_NORMAL]),
+ text_inactive = c2i(style.fg[gtk.STATE_INSENSITIVE]),
+ body = c2i(style.light[gtk.STATE_ACTIVE]),
+ body_today = c2i('peach puff'),
+ border = c2i(style.mid[gtk.STATE_NORMAL]),
+ selected = c2i(style.mid[gtk.STATE_SELECTED]),
+ inactive = c2i(style.bg[gtk.STATE_PRELIGHT]))
+ self.refresh()
+
+ def refresh(self):
+ if not self.realized:
+ return
+ self.draw_background()
+ self.draw_days()
+
+ def draw_background(self):
+ self.root.color = self.colors['bg']
+
+ def draw_days(self):
+ """
+ Draws the currently selected range of days.
+ """
+ if self.range_item is None:
+ self.range_item = CanvasDayRange(self)
+ self.root.append(self.range_item, hippo.PACK_EXPAND)
+ self.range_item.connect('day-clicked', self.on_day_clicked)
+ self.range_item.connect('time-clicked', self.on_time_clicked)
+ self.range_item.connect('event-clicked', self.on_event_clicked)
+
+ if self.range == self.RANGE_WEEK:
+ self.visible_range = self.model.get_week(self.selected)
+ self.active_range = self.visible_range
+ elif self.range == self.RANGE_MONTH:
+ self.visible_range = self.model.get_month_weeks(self.selected)
+ self.active_range = self.model.get_month(self.selected)
+ elif self.range == self.RANGE_CUSTOM:
+ pass
+ else:
+ raise TypeError('Invalid range ' + self.range)
+
+ date = self.selected
+ self.range_item.range = self.visible_range
+ self.range_item.active_range = self.active_range
+ self.range_item.selected = self.selected
+ self.range_item.update()
+
+
+ def on_event_store_event_removed(self, store, event):
+ self.refresh()
+
+
+ def on_event_store_event_added(self, store, event):
+ self.refresh()
+
+
+ def on_key_press_event(self, widget, event):
+ date = self.get_selected()
+ if event.keyval == 65362: # Up
+ self.select(util.previous_week(date))
+ self.emit('day-selected', date, event)
+ elif event.keyval == 65364: # Down
+ self.select(util.next_week(date))
+ self.emit('day-selected', date, event)
+ elif event.keyval == 65361: # Left
+ self.select(util.previous_day(date))
+ self.emit('day-selected', date, event)
+ elif event.keyval == 65363: # Right
+ self.select(util.next_day(date))
+ self.emit('day-selected', date, event)
+ elif event.keyval == 65293: # Enter
+ if not self.emit('day-activate', date, event):
+ self.set_range(self.RANGE_WEEK)
+ return True
+
+
+ def on_event_clicked(self, sender, event, ev):
+ self.emit('event-clicked', event, ev)
+
+
+ def on_day_clicked(self, sender, date, event):
+ if self.emit('date-clicked', date, event):
+ return True
+ if self.range == self.RANGE_MONTH \
+ and not self.range_item.is_active(date):
+ self.set_range(self.RANGE_MONTH)
+ if date < self.active_range[0]:
+ self.emit('do_month_back_forward', -1)
+ else:
+ self.emit('do_month_back_forward', 1)
+ self.select(date)
+ if self.emit('day-selected', date, event):
+ return True
+
+
+ def on_time_clicked(self, sender, date, event):
+ self.emit('time-clicked', date, event)
+
+
+gobject.signal_new('event-clicked',
+ Calendar,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
+gobject.signal_new('time-clicked',
+ Calendar,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
+gobject.signal_new('date-clicked',
+ Calendar,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
+gobject.signal_new('day-selected',
+ Calendar,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
+gobject.signal_new('day-activate',
+ Calendar,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
+
+gobject.signal_new('do_month_back_forward',
+ Calendar,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,))
+
+gobject.type_register(Calendar)
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasDay.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasDay.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasDay.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import hippo
+import gobject
+import calendar
+import pango
+import util
+from SpiffGtkWidgets import color
+
+class CanvasDay(hippo.CanvasBox, hippo.CanvasItem):
+ """
+ A canvas item representing a day.
+ """
+ def __init__(self, cal, **kwargs):
+ """
+ Constructor.
+ """
+ hippo.CanvasBox.__init__(self, **kwargs)
+
+ self.cal = cal
+ self.date = kwargs.get('date')
+ self.active = True
+ self.selected = False
+ self.highlighted = False
+ self.show_rulers = False
+
+ # Create canvas items.
+ self.box = hippo.CanvasGradient(padding = 2, padding_right = 5)
+ self.text = hippo.CanvasText(xalign = hippo.ALIGNMENT_END,
+ size_mode = hippo.CANVAS_SIZE_ELLIPSIZE_END)
+ self.body = hippo.CanvasGradient()
+
+ self.box.append(self.text, hippo.PACK_EXPAND)
+ self.append(self.box)
+ self.append(self.body, hippo.PACK_EXPAND)
+ self.box.set_visible(True)
+
+
+ def set_date(self, date):
+ self.date = date
+
+
+ def set_active(self, active):
+ self.active = active
+
+
+ def set_selected(self, selected):
+ self.selected = selected
+
+
+ def set_highlighted(self, highlighted):
+ self.highlighted = highlighted
+
+
+ def set_show_title(self, show_title):
+ self.box.set_visible(show_title)
+
+
+ def set_show_rulers(self, show_rulers):
+ self.show_rulers = show_rulers
+
+
+ def _set_color(self, box, color):
+ box.set_property('start-color', color)
+ box.set_property('end-color', color)
+
+
+ def get_body_position(self):
+ return self.get_position(self.body)
+
+
+ def get_body_allocation(self):
+ return self.body.get_allocation()
+
+
+ def update(self):
+ # Draw the title box.
+ if self.selected:
+ self._set_color(self.box, self.cal.colors['selected'])
+ elif not self.active:
+ self._set_color(self.box, self.cal.colors['inactive'])
+ else:
+ self._set_color(self.box, self.cal.colors['border'])
+
+ # Draw the title text.
+ if self.date is not None:
+ #day_name = self.cal.model.get_day_name(self.date)
+ #caption = '%s %s' % (self.date.timetuple()[2], day_name)
+ caption = '%d' % self.date.timetuple()[2]
+ self.text.set_property('font', self.cal.font.to_string())
+ self.text.set_property('text', caption)
+ self.text.set_property('color', self.cal.colors['text'])
+
+ # Draw the "body" of the day.
+ if self.highlighted:
+ self._set_color(self.body, self.cal.colors['body_today'])
+ else:
+ self._set_color(self.body, self.cal.colors['body'])
+ self.body.set_property('spacing', 0)
+
+
+ def do_paint_above_children(self, ctx, rect):
+ if not self.show_rulers:
+ return
+ ctx.set_source_rgba(*color.to_rgba(self.cal.colors['inactive']))
+ ctx.rectangle(rect.x, rect.y, rect.width, rect.height)
+ ctx.set_line_width(1)
+ ctx.set_dash((1, 1))
+ ctx.clip()
+ w, h = self.get_allocation()
+
+ for n in range(0, 24):
+ y = n * h / 24
+ ctx.move_to(0, y)
+ ctx.line_to(w, y)
+ ctx.stroke()
+
+gobject.type_register(CanvasDay)
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasDayRange.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasDayRange.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasDayRange.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,375 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import hippo
+import gobject
+import math
+import time
+import datetime
+import util
+from SpiffGtkWidgets import color
+from CanvasTimeline import CanvasTimeline
+from CanvasGrid import CanvasGrid
+from CanvasDay import CanvasDay
+from CanvasTable import CanvasTable
+from CanvasHEventView import CanvasHEventView
+from CanvasVEventView import CanvasVEventView
+
+class CanvasDayRange(CanvasTable, hippo.CanvasItem):
+ """
+ A canvas item that shows a range of days.
+ """
+ def __init__(self, cal, **kwargs):
+ """
+ Constructor.
+ """
+ CanvasTable.__init__(self, **kwargs)
+
+ self.cal = cal
+ self.range = kwargs.get('range')
+ self.active_range = self.range
+ self.selected = None
+
+ # Create canvas items.
+ self.scroll = hippo.CanvasScrollbars()
+ self.hbox_top = hippo.CanvasBox(orientation = hippo.ORIENTATION_HORIZONTAL)
+ self.vbox_top = hippo.CanvasBox()
+ self.day_captions = CanvasTable()
+ self.timeline = CanvasTimeline(self.cal)
+ self.padding_left = hippo.CanvasBox()
+ self.padding_right = hippo.CanvasBox()
+ self.allday_view = CanvasHEventView(self.cal, yalign = hippo.ALIGNMENT_FILL)
+ self.grid = CanvasGrid(self._new_cell)
+ self.gridbox = hippo.CanvasBox(orientation = hippo.ORIENTATION_HORIZONTAL)
+ self.vevent_views = {}
+ self.hevent_views = {}
+ self.allocs = {}
+ self.allocs[self.padding_left] = (0, 0, 0, 0)
+ self.allocs[self.padding_right] = (0, 0, 0, 0)
+
+ self.vbox_top.append(self.day_captions)
+ self.vbox_top.append(self.allday_view)
+ self.day_captions.set_homogeneus_columns(True)
+
+ self.hbox_top.append(self.padding_left)
+ self.hbox_top.append(self.vbox_top, hippo.PACK_EXPAND)
+ self.hbox_top.append(self.padding_right)
+
+ self.gridbox.append(self.timeline)
+ self.gridbox.append(self.grid, hippo.PACK_EXPAND)
+ self.scroll.set_root(self.gridbox)
+
+ self.add(self.hbox_top, 0, 1, 0, 1)
+ self.add(self.scroll, 0, 1, 1, 2)
+ self.set_row_expand(1, True)
+ self.set_column_expand(0, True)
+ self.allday_view.show_normal = False
+ self.allday_view.connect('event-clicked', self.on_view_event_clicked)
+ self.grid.connect('paint', self.on_grid_paint)
+ self.grid.set_homogeneus_columns(True)
+
+
+ def on_day_button_press_event(self, widget, event):
+ date = datetime.datetime(*widget.date.timetuple()[:3])
+ self.emit('time-clicked', date, event)
+ self.emit('day-clicked', widget.date, event)
+
+
+ def on_view_time_clicked(self, view, item, ev, time):
+ date = datetime.date(*time.timetuple()[:3])
+ self.emit('time-clicked', time, ev)
+ self.emit('day-clicked', date, ev)
+
+
+ def on_view_event_clicked(self, view, item, ev):
+ self.emit('event-clicked', item.event, ev)
+
+
+ def _new_cell(self):
+ cell = CanvasDay(self.cal, xalign = hippo.ALIGNMENT_FILL)
+ cell.connect('button-press-event', self.on_day_button_press_event)
+ return cell
+
+
+ def is_active(self, date):
+ return self.active_range[0] <= date <= self.active_range[1]
+
+
+ def _get_event_view(self, row, start, end, horizontal):
+ if horizontal:
+ if row in self.hevent_views:
+ view = self.hevent_views[row]
+ view.set_range(start, end)
+ return view
+ view = CanvasHEventView(self.cal, start, end)
+ self.hevent_views[row] = view
+ else:
+ if row in self.vevent_views:
+ view = self.vevent_views[row]
+ view.set_range(start, end)
+ return view
+ view = CanvasVEventView(self.cal, start, end)
+ self.vevent_views[row] = view
+ view.connect('time-clicked', self.on_view_time_clicked)
+ view.connect('event-clicked', self.on_view_event_clicked)
+ self.allocs[view] = (0, 0, 0, 0)
+ self.gridbox.append(view, hippo.PACK_FIXED)
+ return view
+
+
+ def _remove_vevent_view(self, cell):
+ if not cell in self.vevent_views:
+ return
+ view = self.vevent_views[cell]
+ self.gridbox.remove(view)
+ del self.vevent_views[cell]
+ del self.allocs[view]
+
+
+ def _remove_hevent_view(self, cell):
+ if not cell in self.hevent_views:
+ return
+ view = self.hevent_views[cell]
+ self.gridbox.remove(view)
+ del self.hevent_views[cell]
+ del self.allocs[view]
+
+
+ def update_one_row(self):
+ self.scroll.set_policy(hippo.ORIENTATION_VERTICAL,
+ hippo.SCROLLBAR_ALWAYS)
+ self.grid.set_properties(box_height = 800)
+ self.allday_view.set_range(*self.range)
+ self.allday_view.set_visible(True)
+ self.padding_left.set_visible(True)
+ self.padding_right.set_visible(True)
+
+ # Hide all event views.
+ for cell in [c for c in self.hevent_views]:
+ self._remove_hevent_view(cell)
+ current_children = self.grid.get_children()
+ for cell in [c for c in self.vevent_views]:
+ if cell not in current_children:
+ self._remove_vevent_view(cell)
+
+ # Create an event view on top of each cell.
+ for child in self.grid.get_children():
+ start = child.date
+ end = util.end_of_day(child.date)
+ view = self._get_event_view(child, start, end, False)
+ self.allocs[view] = (0, 0, 0, 0)
+
+
+ def update_multi_row(self):
+ self.scroll.set_policy(hippo.ORIENTATION_VERTICAL,
+ hippo.SCROLLBAR_NEVER)
+ self.grid.set_properties(box_height = -1)
+ self.allday_view.set_visible(False)
+ self.padding_left.set_visible(False)
+ self.padding_right.set_visible(False)
+
+ # Hide all event views.
+ for cell in [c for c in self.vevent_views]:
+ self._remove_vevent_view(cell)
+ current_children = self.grid.get_children()
+ for cell in [c for c in self.hevent_views]:
+ if cell not in current_children:
+ self._remove_hevent_view(cell)
+
+ # Create an event view on top of each row.
+ for row in self.grid.get_rows():
+ start = row[0].date
+ end = row[-1].date
+ view = self._get_event_view(row[0], start, end, True)
+ self.allocs[view] = (0, 0, 0, 0)
+
+
+ def update(self):
+ date = self.range[0]
+ days = (self.range[1] - self.range[0]).days + 1
+ rows = int(math.ceil(float(days) / 7.0))
+ cols = days
+ today = datetime.date(*time.localtime(time.time())[:3])
+ if days > 7:
+ cols = int(math.ceil(float(days) / float(rows)))
+
+ # Update the timeline.
+ self.timeline.set_visible(rows == 1)
+
+ # Show captions for the day.
+ if cols == 7 or rows == 1:
+ for child in self.day_captions.get_children():
+ self.day_captions.remove(child)
+ for col in range(cols):
+ this_date = self.range[0] + datetime.timedelta(col)
+ day_name = self.cal.model.get_day_name(this_date)
+ text = hippo.CanvasText(text = day_name,
+ xalign = hippo.ALIGNMENT_CENTER,
+ size_mode = hippo.CANVAS_SIZE_ELLIPSIZE_END)
+ self.day_captions.add(text, col, col + 1, 0, 1)
+ self.day_captions.set_column_expand(col, True)
+ self.day_captions.set_visible(True)
+ else:
+ self.day_captions.set_visible(False)
+
+ # Update the grid.
+ self.grid.set_size(rows, cols)
+ for row in range(rows):
+ self.grid.set_row_expand(row, True)
+ for child in self.grid.get_children():
+ child.set_active(self.is_active(date))
+ child.set_show_title(rows != 1)
+ child.set_show_rulers(rows == 1)
+ child.set_selected(date == self.selected)
+ child.set_highlighted(date == today)
+ child.set_date(date)
+ child.update()
+ date = util.next_day(date)
+
+ if rows == 1:
+ self.update_one_row()
+ else:
+ self.update_multi_row()
+
+
+ def do_allocate_one_row(self, width, height, origin_changed):
+ grid_x, grid_y = self.gridbox.get_position(self.grid)
+ grid_w, grid_h = self.grid.get_allocation()
+
+ for cell in self.grid.get_children():
+ cell_x_off, cell_y_off = self.grid.get_position(cell)
+
+ # the 18 number is for the size of the scrollbar on the right
+ # (don't know how to get it)
+ try:
+ cell_x_off = math.ceil((float(cell_x_off)) * (width - grid_x - 18) / grid_w)
+ except:
+ pass
+
+ cell_w, cell_h = cell.get_allocation()
+ x = int(grid_x + cell_x_off)
+ y = int(grid_y + cell_y_off)
+ w = cell_w
+ h = cell_h
+ view = self.vevent_views[cell]
+ alloc = (x, y, w, h)
+
+ if self.allocs[view] == alloc:
+ continue
+ self.allocs[view] = alloc
+ self.gridbox.set_position(view, alloc[0], alloc[1])
+ view.set_properties(box_width = alloc[2],
+ box_height = alloc[3])
+
+
+ def do_allocate_multi_row(self, width, height, origin_changed):
+ days = (self.range[1] - self.range[0]).days + 1
+ rows = int(math.ceil(float(days) / 7.0))
+ cols = days
+ grid_x, grid_y = self.gridbox.get_position(self.grid)
+ if days > 7:
+ cols = int(math.ceil(float(days) / float(rows)))
+
+ for row in self.grid.get_rows():
+ start = row[0].date
+ end = row[-1].date
+ view = self._get_event_view(row[0], start, end, True)
+ view.set_column_count(cols)
+
+ # Find the position of the writable area of the row.
+ grid_w, grid_h = self.grid.get_allocation()
+ grid_w, grid_h = (width, height)
+ row_x_off, row_y_off = self.grid.get_position(row[0])
+ cell_w, cell_h = row[0].get_allocation()
+ title_x_off, title_y_off = row[0].get_body_position()
+ x = grid_x + row_x_off
+ y = grid_y + row_y_off + title_y_off
+ w = grid_w
+ h = cell_h - title_y_off
+ alloc = (x, y, w, h)
+
+ if self.allocs[view] == alloc:
+ continue
+ self.allocs[view] = alloc
+ self.gridbox.set_position(view, alloc[0], alloc[1])
+ view.set_properties(box_width = alloc[2],
+ box_height = alloc[3])
+
+
+ def do_allocate(self, width, height, origin_changed):
+ days = (self.range[1] - self.range[0]).days + 1
+ rows = int(math.ceil(float(days) / 7.0))
+ cols = days
+ grid_x, grid_y = self.gridbox.get_position(self.grid)
+ if days > 7:
+ cols = int(math.ceil(float(days) / float(rows)))
+
+ # Show the items for one-row mode or multi-row mode.
+ if rows == 1:
+ self.do_allocate_one_row(width, height, origin_changed)
+ else:
+ self.do_allocate_multi_row(width, height, origin_changed)
+ hippo.CanvasBox.do_allocate(self, width, height, origin_changed)
+
+
+ def on_grid_paint_one_row(self):
+ w, h = self.get_allocation()
+ grid_x, grid_y = self.gridbox.get_position(self.grid)
+ grid_w, grid_h = self.grid.get_allocation()
+ timeline_w, timeline_h = self.timeline.get_allocation()
+ padding_left = timeline_w
+ padding_right = w - timeline_w - grid_w
+
+ if self.allocs[self.padding_left][2] != padding_left:
+ self.allocs[self.padding_left] = (0, 0, padding_left, 0)
+ self.padding_left.set_properties(box_width = padding_left)
+ if self.allocs[self.padding_right][2] != padding_right:
+ self.allocs[self.padding_right] = (0, 0, padding_right, 0)
+ self.padding_right.set_properties(box_width = padding_right)
+
+
+ def on_grid_paint(self, grid, ptr, rect):
+ # catching this signal is ugly, but trying to do this
+ # in do_allocate() will result in painful to avoid event
+ # loops.
+ days = (self.range[1] - self.range[0]).days + 1
+ rows = int(math.ceil(float(days) / 7.0))
+
+ if rows == 1:
+ self.on_grid_paint_one_row()
+
+
+gobject.type_register(CanvasDayRange)
+
+gobject.signal_new('day-clicked',
+ CanvasDayRange,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
+
+gobject.signal_new('time-clicked',
+ CanvasDayRange,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
+
+gobject.signal_new('event-clicked',
+ CanvasDayRange,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasEvent.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasEvent.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasEvent.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+#
+##############################################################################
+
+import hippo
+import gobject
+from SpiffGtkWidgets import color
+from CanvasRectangle import CanvasRectangle
+
+class CanvasEvent(CanvasRectangle):
+ """
+ A canvas item representing a day.
+ """
+ def __init__(self, cal, event, **kwargs):
+ """
+ Constructor.
+ """
+ self.cal = cal
+ self.event = event
+ self.rulers = []
+ CanvasRectangle.__init__(self, **kwargs)
+ # Create canvas items.
+ self.text = hippo.CanvasText(xalign = hippo.ALIGNMENT_CENTER,
+ yalign = hippo.ALIGNMENT_CENTER,
+ size_mode = hippo.CANVAS_SIZE_ELLIPSIZE_END)
+ self.append(self.text, hippo.PACK_EXPAND)
+
+ def set_text(self, text, description = ''):
+ self.text.set_property('text', text + ', ' + description)
+
+
+ def set_text_color(self, newcolor):
+ self.text.props.color = color.to_int(newcolor)
+
+
+ def set_text_properties(self, **kwargs):
+ self.text.set_properties(**kwargs)
+
+
+gobject.type_register(CanvasEvent)
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasEventView.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasEventView.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasEventView.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import hippo
+import gobject
+import datetime
+import util
+from SpiffGtkWidgets import color
+from CanvasEvent import CanvasEvent
+
+class CanvasEventView(hippo.CanvasBox):
+ """
+ A canvas item that shows a range of events.
+ """
+ def __init__(self, cal, start = None, end = None, **kwargs):
+ """
+ Constructor.
+ """
+ hippo.CanvasBox.__init__(self, **kwargs)
+
+ self.cal = cal
+ self.model = cal.model
+ self.range = None
+ self.event_items = {}
+ self.allocs = {}
+ self.set_range(start, end)
+ self.connect_after('button-press-event', self.on_button_press_event)
+ self.model.connect('event-added', self.on_model_event_added)
+ self.model.connect('event-removed', self.on_model_event_removed)
+
+
+ def on_model_event_added(self, model, event):
+ self.update()
+
+
+ def on_model_event_removed(self, model, event):
+ if event is not None \
+ and event not in self.event_items:
+ return
+ self.update()
+
+
+ def on_button_press_event(self, widget, event):
+ w, h = self.get_allocation()
+ days = (self.range[1] - self.range[0]).days + 1
+ day_w = w / float(days)
+ day = int(event.x / day_w)
+ date = self.range[0] + datetime.timedelta(1) * day
+ time = 24 / float(h) * event.y
+ hour = int(time)
+ min = (time - hour) * 60
+ date += datetime.timedelta(0, 0, 0, 0, min, hour)
+ self.emit('time-clicked', widget, event, date)
+
+
+ def on_event_button_press_event(self, widget, event):
+ self.emit('event-clicked', widget, event)
+ return True
+
+
+ def on_event_button_release_event(self, widget, event):
+ self.emit('event-released', widget, event)
+ return True
+
+
+ def set_range(self, start, end):
+ if start is None or end is None:
+ return
+ range = datetime.datetime(*start.timetuple()[:3]), \
+ datetime.datetime(*end.timetuple()[:7])
+
+ # Update end if it's a `datetime.date' and not a `datetime.datetime',
+ # because day ranges are inclusive (so day must _end_ at 23:59:59)
+ if isinstance(end, datetime.date):
+ range = range[0], util.end_of_day(range[1])
+
+ if self.range is not None \
+ and self.range[0] == range[0] \
+ and self.range[1] == range[1]:
+ return
+ self.range = range
+ self.update()
+
+
+gobject.type_register(CanvasEventView)
+
+gobject.signal_new('time-clicked',
+ CanvasEventView,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT))
+
+gobject.signal_new('event-clicked',
+ CanvasEventView,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
+
+gobject.signal_new('event-released',
+ CanvasEventView,
+ gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasGrid.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasGrid.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasGrid.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import hippo
+import gobject
+import calendar
+import pango
+import util
+from CanvasTable import CanvasTable
+
+class CanvasGrid(CanvasTable):
+ """
+ A table item that automatically retrieves the cell content from a given
+ data provider.
+ """
+ def __init__(self, provider, **kwargs):
+ """
+ Constructor.
+ """
+ CanvasTable.__init__(self, 1, 1)
+ self.provider = provider
+
+
+ def _new_cell(self):
+ cell = self.provider()
+ return cell
+
+
+ def _add_line(self, length):
+ rows, cols = self.get_size()
+ for colnum in range(length):
+ self.set_column_expand(colnum, True)
+ self.add(self._new_cell(), colnum, colnum + 1, rows, rows + 1)
+ self.set_row_expand(rows, True)
+
+
+ def _add_column(self):
+ rows, cols = self.get_size()
+ for rownum in range(rows):
+ self.add(self._new_cell(), cols, cols + 1, rownum, rownum + 1)
+
+
+ def set_size(self, rows, cols):
+ old_rows, old_cols = self.get_size()
+
+ # Create new cells if the new size is bigger.
+ if cols > old_cols:
+ for x in range(old_cols, cols):
+ self._add_column()
+ if rows > old_rows:
+ for rownum in range(old_rows, rows):
+ self._add_line(cols)
+
+ # Remove cells if the new size is smaller.
+ self.shrink(rows, cols)
+ CanvasTable.set_size(self, rows, cols)
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasHEventView.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasHEventView.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasHEventView.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,220 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import hippo
+import gobject
+import pango
+import datetime
+import util
+from SpiffGtkWidgets import color
+from CanvasEvent import CanvasEvent
+from CanvasEventView import CanvasEventView
+from CanvasMagnetTable import CanvasMagnetTable
+
+class CanvasHEventView(CanvasEventView, hippo.CanvasItem):
+ """
+ A canvas item that shows a range of events.
+ """
+ def __init__(self, cal, start = None, end = None, **kwargs):
+ """
+ Constructor.
+ """
+ CanvasEventView.__init__(self, cal, **kwargs)
+ self.table = CanvasMagnetTable(align = CanvasMagnetTable.ALIGN_TOP)
+ self.table.set_homogeneus_columns(True)
+ self.append(self.table, hippo.PACK_EXPAND)
+ self.show_normal = True # whether to show normal events
+ self.overflow_indic = []
+ self.columns = 0
+
+
+ def _format_time(self, event):
+ hour, minute = event.start.timetuple()[3:5]
+# if minute == 0:
+# text = '%d %s' % (hour, event.caption)
+# else:
+ text = '%d:%02d %s' % (hour, minute, event.caption)
+ return text
+
+
+ def set_column_count(self, count):
+ if count == self.columns:
+ return
+ self.columns = count
+
+ self.table.set_column_count(count)
+
+ # Hide all overflow indicators.
+ for child in self.get_children():
+ if child != self.table:
+ self.remove(child)
+
+ for col in range(count):
+ font = self.cal.font.copy()
+ font.set_style(pango.STYLE_ITALIC)
+ text = hippo.CanvasText(text = ' ',
+ font = font.to_string(),
+ xalign = hippo.ALIGNMENT_CENTER)
+ self.append(text, hippo.PACK_FIXED)
+ self.overflow_indic.append(text)
+ text.set_visible(False)
+ self.allocs[text] = (0, 0, 0, 0)
+
+
+ def _add_event(self, event):
+ event_start = max(event.start, self.range[0])
+ event_end = min(event.end, self.range[1])
+ event_off_days = (event_start - self.range[0]).days
+ event_width_days = (event_end - event_start).days + 1
+
+ # Create the event.
+ item = CanvasEvent(self.cal, event)
+ self.event_items[event] = item
+ item.connect('button-press-event', self.on_event_button_press_event)
+ item.connect('button-release-event', self.on_event_button_release_event)
+ self.table.add(item,
+ event_off_days,
+ event_off_days + event_width_days,
+ len(self.event_items))
+ item.set_text(event.caption, event.description)
+ item.set_property('color', color.to_int(event.bg_color))
+ if self.show_normal and not util.same_day(event.start, event.end):
+ item.set_property('color', color.to_int(event.bg_color))
+ elif not event.all_day:
+ time = self._format_time(event)
+ item.set_text(time, event.description)
+ item.set_text_properties(xalign = hippo.ALIGNMENT_START)
+ if event.text_color is not None:
+ item.set_text_color(event.text_color)
+ radius_top_left = 10
+ radius_top_right = 10
+ radius_bottom_left = 10
+ radius_bottom_right = 10
+ if event.end > self.range[1]:
+ radius_top_right = 0
+ radius_bottom_right = 0
+ if event.start < self.range[0]:
+ radius_top_left = 0
+ radius_bottom_left = 0
+ item.set_properties(radius_top_left = radius_top_left,
+ radius_top_right = radius_top_right,
+ radius_bottom_left = radius_bottom_left,
+ radius_bottom_right = radius_bottom_right)
+
+
+ def update(self):
+ # Don't crash if we didn't have a range now
+ if self.range is None:
+ return
+
+ days = (self.range[1] - self.range[0]).days + 1
+ self.table.set_column_count(days)
+ self.table.set_row_count(-1)
+
+ # Remove old events.
+ for item in self.table.get_children():
+ self.table.remove(item)
+
+ # Add events.
+ if self.range is None:
+ return
+ for event in self.model.get_all_day_events(self.range[0],
+ self.range[1],
+ self.show_normal == True):
+ self._add_event(event)
+ if self.show_normal:
+ for event in self.model.get_normal_events(self.range[0],
+ self.range[1],
+ False):
+ self._add_event(event)
+
+ # Force all children to be visible, to fix 'overflow' positioning.
+ for child in self.get_children():
+ child.set_visible(True)
+
+ # Change to fixed sizing.
+ rows, cols = self.table.get_size()
+ self.table.set_size(rows, cols)
+
+
+ def do_allocate(self, width, height, origin_changed):
+ CanvasEventView.do_allocate(self, width, height, origin_changed)
+
+ # Hide all overflow indicators.
+ for child in self.get_children():
+ if child != self.table:
+ child.set_visible(False)
+
+ rows, cols = self.table.get_size()
+ if min(rows, width, height) <= 0:
+ return
+
+ # Measure the height of the first event.
+ children = self.table.get_children()
+ if len(children) == 0:
+ return
+ first = children[0]
+ min_row_h, row_h = first.get_height_request(width / cols)
+ if row_h <= 0:
+ return
+
+ # Hide events that do not fit into the box.
+ max_rows = height / row_h
+ matrix = self.table.get_matrix()
+ for colnum, col in enumerate(matrix.get_columns()):
+ # Count rows that are already hidden.
+ hidden = 0
+ for child in col:
+ if not child.get_visible():
+ hidden += 1
+
+ # No need to hide anything if the box is large enough.
+ if len(col) <= max_rows:
+ for child in col:
+ child.set_visible(True)
+ continue
+
+ # Hide enough rows to make room for an overflow indicator.
+ to_hide = len(col) - max_rows + 1
+ hidden = 0
+ for row in reversed(col):
+ if hidden >= to_hide:
+ row.set_visible(True)
+ continue
+ if not row.get_visible():
+ hidden += 1
+ continue
+ row.set_visible(False)
+ hidden += 1
+
+ # Show overflow indicator
+ indic = self.overflow_indic[colnum]
+ caption = '%d more' % hidden
+ alloc = (width / cols * colnum, height - row_h, width / cols, hidden)
+ indic.set_visible(True)
+
+ if self.allocs[indic] == alloc:
+ continue
+ self.allocs[indic] = alloc
+ indic.set_properties(text = caption,
+ box_width = alloc[2])
+ self.set_position(indic, alloc[0], alloc[1])
+ CanvasEventView.do_allocate(self, width, height, origin_changed)
+
+
+gobject.type_register(CanvasHEventView)
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasMagnetTable.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasMagnetTable.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasMagnetTable.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import hippo
+import gobject
+import sys
+import util
+from SpiffGtkWidgets import color
+from CanvasTable import CanvasTable
+
+class CanvasMagnetTable(CanvasTable):
+ """
+ A table that works similar to four-in-a-row. It has a number homogeneous
+ columns, and every child is dragged towards the top of the column.
+ The table also allows for adding children that span multiple columns.
+ """
+ ALIGN_TOP = 1
+ ALIGN_BOTTOM = 2
+ ALIGN_LEFT = 4
+ ALIGN_RIGHT = 8
+
+ __gproperties__ = {
+ 'align': (gobject.TYPE_LONG,
+ 'the alignment',
+ 'the direction into which children are pulled. one of'
+ + ' ALIGN_TOP, ALIGN_BOTTOM, ALIGN_LEFT or ALIGN_RIGHT',
+ 1,
+ 4,
+ ALIGN_TOP,
+ gobject.PARAM_READWRITE)
+ }
+ def __init__(self, **kwargs):
+ """
+ Constructor.
+ """
+ self.property_names = ('align',)
+ self.align = self.ALIGN_TOP
+ CanvasTable.__init__(self, **kwargs)
+
+
+ def do_get_property(self, property):
+ if property.name in self.property_names:
+ return self.__getattribute__(property.name.replace('-', '_'))
+ else:
+ raise AttributeError, 'unknown property %s' % property.name
+
+
+ def do_set_property(self, property, value):
+ if property.name in self.property_names:
+ return self.__setattr__(property.name.replace('-', '_'), value)
+ else:
+ raise AttributeError, 'unknown property %s' % property.name
+
+
+ def _shift(self, matrix, move_func):
+ for cell in matrix.get_cells():
+ old_pos = matrix.get_pos(cell)
+ new_pos = move_func(cell)
+ #print "OLD", old_pos, "NEW", new_pos, cell.event.caption
+ if old_pos != new_pos:
+ self.remove(cell)
+ self.add(cell, new_pos[0], new_pos[2], new_pos[1], new_pos[3])
+
+
+ def add(self, child, left=None, right=None, top=None, bottom=None, flags=0):
+ CanvasTable.add(self, child, left, right, top, bottom, flags)
+ matrix = self.get_matrix()
+ if self.align == self.ALIGN_TOP:
+ self._shift(matrix, matrix.move_top)
+ elif self.align == self.ALIGN_BOTTOM:
+ self._shift(matrix, matrix.move_bottom)
+ if self.align == self.ALIGN_LEFT:
+ self._shift(matrix, matrix.move_left)
+ elif self.align == self.ALIGN_RIGHT:
+ self._shift(matrix, matrix.move_right)
+
+gobject.type_register(CanvasMagnetTable)
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasRectangle.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasRectangle.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasRectangle.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import hippo
+import gobject
+from SpiffGtkWidgets import color
+import sys
+
+class CanvasRectangle(hippo.CanvasBox):
+ """
+ A canvas item that draws a rectangle, optionally with rounded corners.
+ """
+ __gproperties__ = {
+ 'radius-top-left': (gobject.TYPE_LONG,
+ 'top left radius',
+ 'radius of the top left corner',
+ 0,
+ sys.maxint,
+ 10,
+ gobject.PARAM_READWRITE),
+ 'radius-top-right': (gobject.TYPE_LONG,
+ 'top right radius',
+ 'radius of the top right corner',
+ 0,
+ sys.maxint,
+ 10,
+ gobject.PARAM_READWRITE),
+ 'radius-bottom-left': (gobject.TYPE_LONG,
+ 'bottom left radius',
+ 'radius of the bottom left corner',
+ 0,
+ sys.maxint,
+ 10,
+ gobject.PARAM_READWRITE),
+ 'radius-bottom-right': (gobject.TYPE_LONG,
+ 'bottom right radius',
+ 'radius of the bottom right corner',
+ 0,
+ sys.maxint,
+ 10,
+ gobject.PARAM_READWRITE),
+ }
+
+ def __init__(self, **kwargs):
+ """
+ Constructor.
+ """
+ self.property_names = ('radius-top-left',
+ 'radius-top-right',
+ 'radius-bottom-left',
+ 'radius-bottom-right')
+ self.radius_top_left = 10
+ self.radius_top_right = 10
+ self.radius_bottom_left = 10
+ self.radius_bottom_right = 10
+ hippo.CanvasBox.__init__(self, **kwargs)
+
+
+ def do_get_property(self, property):
+ if property.name in self.property_names:
+ return self.__getattribute__(property.name.replace('-', '_'))
+ else:
+ raise AttributeError, 'unknown property %s' % property.name
+
+
+ def do_set_property(self, property, value):
+ if property.name in self.property_names:
+ return self.__setattr__(property.name.replace('-', '_'), value)
+ else:
+ raise AttributeError, 'unknown property %s' % property.name
+
+
+ def do_paint_below_children(self, ctx, rect):
+ ctx.set_source_rgba(*color.to_rgba(self.props.color))
+ ctx.rectangle(rect.x, rect.y, rect.width, rect.height)
+ ctx.clip()
+ rtl = self.props.radius_top_left
+ rtr = self.props.radius_top_right
+ rbl = self.props.radius_bottom_left
+ rbr = self.props.radius_bottom_right
+ x, y = 0, 0
+ w, h = self.get_allocation()
+
+ # A****BQ
+ # H C
+ # * *
+ # G D
+ # F****E
+ ctx.move_to(x+rtl,y) # A
+ ctx.line_to(x+w-rtr,y) # B
+ ctx.curve_to(x+w,y,x+w,y,x+w,y+rtr) # C, both control points at Q
+ ctx.line_to(x+w,y+h-rbr) # D
+ ctx.curve_to(x+w,y+h,x+w,y+h,x+w-rbr,y+h) # E
+ ctx.line_to(x+rbl,y+h) # F
+ ctx.curve_to(x,y+h,x,y+h,x,y+h-rbl) # G
+ ctx.line_to(x,y+rtl) # H
+ ctx.curve_to(x,y,x,y,x+rtl,y) # A
+ ctx.fill()
+
+gobject.type_register(CanvasRectangle)
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasTable.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasTable.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasTable.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import gtk
+import gobject
+import hippo
+from TableLayout import TableLayout
+from Matrix import Matrix
+
+class CanvasTable(hippo.CanvasBox):
+ def __init__(self, column_spacing=0, row_spacing=0, **kwargs):
+ hippo.CanvasBox.__init__(self, **kwargs)
+
+ self.__layout = TableLayout(column_spacing=column_spacing, row_spacing=row_spacing)
+ self.set_layout(self.__layout)
+
+ def add(self, child, left=None, right=None, top=None, bottom=None, flags=0):
+ self.__layout.add(child, left, right, top, bottom, flags)
+
+ def remove(self, child):
+ hippo.CanvasBox.remove(self, child)
+
+ def set_homogeneus_rows(self, homogeneus):
+ self.__layout.set_homogeneus_rows(homogeneus)
+
+ def set_homogeneus_columns(self, homogeneus):
+ self.__layout.set_homogeneus_columns(homogeneus)
+
+ def set_column_expand(self, column, expand):
+ self.__layout.set_column_expand(column, expand)
+
+ def set_row_expand(self, row, expand):
+ self.__layout.set_row_expand(row, expand)
+
+ def set_row_count(self, rows):
+ self.__layout.set_row_count(rows)
+
+ def set_column_count(self, cols):
+ self.__layout.set_column_count(cols)
+
+ def set_size(self, rows, cols):
+ self.__layout.set_size(rows, cols)
+
+ def get_size(self):
+ rows = self.__layout.get_row_count()
+ cols = self.__layout.get_column_count()
+ return rows, cols
+
+ def get_total_row_spacing(self):
+ return self.__layout.get_total_row_spacing()
+
+ def get_total_column_spacing(self):
+ return self.__layout.get_total_column_spacing()
+
+ def shrink(self, rows, cols):
+ for child in self.get_children():
+ box = self.find_box_child(child)
+ if box.bottom > rows or box.right > cols:
+ self.remove(child)
+
+ def get_matrix(self):
+ rows, cols = self.get_size()
+ matrix = Matrix(rows, cols)
+ for child in self.get_children():
+ box = self.find_box_child(child)
+ matrix.set(child, box.left, box.top, box.right, box.bottom)
+ return matrix
+
+ def get_rows(self):
+ matrix = self.get_matrix()
+ return matrix.get_rows()
+
+ def remove_empty_rows(self):
+ pass #FIXME
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasTimeline.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasTimeline.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasTimeline.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import hippo
+import gobject
+import calendar
+import pango
+
+class CanvasTimeline(hippo.CanvasBox):
+ """
+ A canvas item representing a timeline.
+ """
+ def __init__(self, cal, **kwargs):
+ """
+ Constructor.
+ """
+ hippo.CanvasBox.__init__(self, **kwargs)
+
+ self.cal = cal
+ self.text = {}
+
+ # Create canvas items.
+ for n in range(0, 24):
+ if n == -1:
+ caption = ' '
+ else:
+ caption = '%d' % n
+ box = hippo.CanvasGradient(padding_right = 5)
+ text = hippo.CanvasText(text = caption,
+ xalign = hippo.ALIGNMENT_END,
+ yalign = hippo.ALIGNMENT_START)
+ box.append(text, hippo.PACK_EXPAND)
+ self.append(box, hippo.PACK_EXPAND)
+
+
+ def _set_color(self, box, color):
+ box.set_property('start-color', color)
+ box.set_property('end-color', color)
+
+
+ def update(self):
+ line_height = self.height / 24
+
+ # Draw the timeline.
+ for n, box in enumerate(self.get_children()):
+ text = box.get_children()[0]
+ self._set_color(box, self.cal.colors['border'])
+ text.set_property('font', self.cal.font.to_string())
+ text.set_property('color', self.cal.colors['text'])
=== added file 'bin/SpiffGtkWidgets/Calendar/CanvasVEventView.py'
--- bin/SpiffGtkWidgets/Calendar/CanvasVEventView.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/CanvasVEventView.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import hippo
+import gobject
+import datetime
+import util
+from SpiffGtkWidgets import color
+from math import ceil
+from CanvasEvent import CanvasEvent
+from CanvasEventView import CanvasEventView
+from CanvasMagnetTable import CanvasMagnetTable
+
+class CanvasVEventView(CanvasEventView):
+ """
+ A canvas item that shows a range of events.
+ """
+ def __init__(self, cal, start = None, end = None, **kwargs):
+ """
+ Constructor.
+ """
+ self.table = CanvasMagnetTable(align = CanvasMagnetTable.ALIGN_LEFT)
+ self.table.set_row_count(24 * 4)
+ self.table.set_homogeneus_rows(True)
+ CanvasEventView.__init__(self, cal, start, end, **kwargs)
+ self.append(self.table, hippo.PACK_EXPAND)
+
+
+ def _add_event(self, event):
+ rows, cols = self.table.get_size()
+ event_start = max(event.start, self.range[0])
+ event_end = min(event.end, self.range[1])
+ event_off = (event_start - self.range[0]).seconds
+ event_len = (event_end - event_start).seconds
+ range_len = self.range[1] - self.range[0]
+ seconds = range_len.days * 24 * 60 * 60 + range_len.seconds
+ row_seconds = ceil(seconds / float(rows))
+ event_off_rows = int(ceil(event_off / float(row_seconds)))
+ event_len_rows = int(ceil(event_len / float(row_seconds)))
+ event_end_rows = event_off_rows + event_len_rows
+
+ # Create the event.
+ item = CanvasEvent(self.cal, event)
+ self.event_items[event] = item
+ item.connect('button-press-event', self.on_event_button_press_event)
+ item.connect('button-release-event', self.on_event_button_release_event)
+ self.table.set_column_expand(cols, True)
+ self.table.add(item, cols + 1, cols + 2, event_off_rows, event_end_rows)
+ item.set_text(event.caption, event.description)
+ item.set_property('color', color.to_int(event.bg_color))
+ if event.text_color is not None:
+ item.set_text_color(event.text_color)
+
+ radius_top_left = 10
+ radius_top_right = 10
+ radius_bottom_left = 10
+ radius_bottom_right = 10
+ if event.end > self.range[1]:
+ radius_bottom_left = 0
+ radius_bottom_right = 0
+ if event.start < self.range[0]:
+ radius_top_left = 0
+ radius_top_right = 0
+ item.set_properties(radius_top_left = radius_top_left,
+ radius_top_right = radius_top_right,
+ radius_bottom_left = radius_bottom_left,
+ radius_bottom_right = radius_bottom_right)
+
+
+ def update(self):
+ # Don't crash if we didn't have a range now
+ if self.range is None:
+ return
+
+ # Remove old events.
+ for item in self.table.get_children():
+ self.table.remove(item)
+ self.table.set_column_count(0)
+
+ if self.range is None:
+ return
+ for event in self.model.get_normal_events(*self.range):
+ self._add_event(event)
+
+
+gobject.type_register(CanvasVEventView)
=== added file 'bin/SpiffGtkWidgets/Calendar/Event.py'
--- bin/SpiffGtkWidgets/Calendar/Event.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/Event.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import util
+import datetime
+
+class Event(object):
+ """
+ This class represents an event that can be displayed in the calendar.
+ """
+
+ def __init__(self, caption, start, end = None, **kwargs):
+ """
+ Constructor.
+
+ start -- datetime
+ end -- datetime
+ """
+ assert caption is not None
+ assert start is not None
+ self.id = None
+ self.caption = caption
+ self.start = start
+ self.end = end
+ self.all_day = kwargs.get('all_day', False)
+ self.text_color = kwargs.get('text_color', None)
+ self.bg_color = kwargs.get('bg_color', 'orangered')
+ self.description = kwargs.get('description','')
+
+ if end is None:
+ self.all_day = True
+ self.end = start
+
+ if end is not None:
+ # Check if end date (deadline) have a time set to 00:00:00,
+ # this means the event should really end on the day before,
+ # so remove 'one' second.
+ end_day = datetime.datetime(*end.timetuple()[:3])
+ end_day_seconds = datetime.datetime(*end.timetuple()[:6])
+ if end_day == end_day_seconds:
+ self.end = end - datetime.timedelta(seconds=1)
+
+ if self.all_day:
+ self.end = util.end_of_day(self.end)
=== added file 'bin/SpiffGtkWidgets/Calendar/Matrix.py'
--- bin/SpiffGtkWidgets/Calendar/Matrix.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/Matrix.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,241 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+class Matrix(object):
+ def __init__(self, rows = -1, cols = -1):
+ self.rows = rows
+ self.cols = cols
+ self.matrix = []
+ self.resize(max(self.rows, 1), max(self.cols, 1))
+
+
+ def resize(self, rows, cols):
+ old_rows, old_cols = self.get_size()
+ diff_rows, diff_cols = rows - old_rows, cols - old_cols
+ while diff_rows > 0:
+ self.matrix.append([None for f in range(old_cols)])
+ diff_rows -= 1
+ while diff_rows < 0:
+ self.matrix.pop()
+ diff_rows += 1
+ for i in range(rows):
+ diff = diff_cols
+ while diff > 0:
+ self.matrix[i].append(None)
+ diff -= 1
+ while diff < 0:
+ self.matrix[i].pop()
+ diff += 1
+
+
+ def set(self, value, x1, y1, x2, y2):
+ assert x1 <= x2
+ assert y1 <= y2
+
+ # Make sure that the table is big enough.
+ rows, cols = self.get_size()
+ if x2 > cols and self.cols != -1:
+ msg = 'Adding a cell at column %s in a matrix with %s columns'
+ raise Exception(msg % (x2, cols))
+ elif y2 > rows and self.rows != -1:
+ msg = 'Adding a cell at row %s in a matrix with %s rows'
+ raise Exception(msg % (y2, rows))
+
+ # Resize if necessary.
+ if x2 > cols or y2 > rows:
+ cols = max(cols, x2)
+ rows = max(rows, y2)
+ self.resize(rows, cols)
+
+ # Allocate the cells to the new child.
+ for rownum in range(y1, y2):
+ for colnum in range(x1, x2):
+ self.matrix[rownum][colnum] = value
+
+
+ def unset(self, child):
+ free_rows = []
+ for rownum, row in enumerate(self.matrix):
+ for cellnum, cell in enumerate(row):
+ if cell == child:
+ row[cellnum] = None
+
+ # Remove free rows from the end of the table.
+ if self.rows == -1:
+ free_rows = 0
+ for rows in reversed(self.matrix):
+ if not self._row_is_free(rows):
+ break
+ free_rows += 1
+ for row in range(free_rows):
+ self.matrix.pop()
+
+ # Remove free columns from the end of the table.
+ if self.cols == -1:
+ free_cols = 0
+ for rows in reversed(self.matrix):
+ free_cells = 0
+ for cell in reversed(row):
+ if cell is not None:
+ break
+ free_cells += 1
+ free_cols = min(free_cells, free_cols)
+ if free_cols == 0:
+ break
+ for row in self.matrix:
+ for cell in range(free_cols):
+ row.pop()
+
+
+ def get_size(self):
+ rows = len(self.matrix)
+ if rows == 0:
+ return 0, 0
+ return rows, len(self.matrix[0])
+
+
+ def get_pos(self, child):
+ x1, y1, x2, y2 = -1, -1, -1, -1
+ for rownum, row in enumerate(self.matrix):
+ for colnum, cell in enumerate(row):
+ if cell != child:
+ continue
+ if x1 == -1:
+ x1 = colnum
+ else:
+ x1 = min(x1, colnum)
+ if y1 == -1:
+ y1 = rownum
+ else:
+ y1 = min(y1, rownum)
+ x2 = max(x2, colnum + 1)
+ y2 = max(y2, rownum + 1)
+ return x1, y1, x2, y2
+
+
+ def move_top(self, child):
+ x1, y1, x2, y2 = self.get_pos(child)
+ while True:
+ if y1 <= 0:
+ break
+ if not self.is_free(x1, y1 - 1, x2, y1):
+ break
+ self.set(child, x1, y1 - 1, x2, y1)
+ self.set(None, x1, y2 - 1, x2, y2)
+ y1 -= 1
+ y2 -= 1
+ return x1, y1, x2, y2
+
+
+ def move_bottom(self, child):
+ rows, cols = self.get_size()
+ x1, y1, x2, y2 = self.get_pos(child)
+ while True:
+ if y2 >= rows:
+ break
+ if not self.is_free(x1, y2, x2, y2 + 1):
+ break
+ self.set(child, x1, y2, x2, y2 + 1)
+ self.set(None, x1, y1, x2, y1 + 1)
+ y1 += 1
+ y2 += 1
+ return x1, y1, x2, y2
+
+
+ def move_left(self, child):
+ x1, y1, x2, y2 = self.get_pos(child)
+ while True:
+ if x1 <= 0:
+ break
+ if not self.is_free(x1 - 1, y1, x1, y2):
+ break
+ self.set(child, x1 - 1, y1, x1, y2)
+ self.set(None, x2 - 1, y1, x2, y2)
+ x1 -= 1
+ x2 -= 1
+ return x1, y1, x2, y2
+
+
+ def move_right(self, child):
+ rows, cols = self.get_size()
+ x1, y1, x2, y2 = self.get_pos(child)
+ while True:
+ if x2 >= cols:
+ break
+ if not self.is_free(x2, y1, x2 + 1, y2):
+ break
+ self.set(child, x2, y1, x2 + 1, y2)
+ self.set(None, x1, y1, x1 + 1, y2)
+ x1 += 1
+ x2 += 1
+ return x1, y1, x2, y2
+
+
+ def get_cells(self):
+ cells = []
+ for row in self.matrix:
+ for cell in row:
+ if cell is not None and cell not in cells:
+ cells.append(cell)
+ return cells
+
+
+ def get_rows(self):
+ rows = []
+ for row in self.matrix:
+ cells = []
+ for cell in row:
+ if cell is not None and cell not in cells:
+ cells.append(cell)
+ rows.append(cells)
+ return rows
+
+
+ def get_columns(self):
+ rows, cols = self.get_size()
+ result = [[] for col in range(cols)]
+ for row in self.matrix:
+ for colnum, cell in enumerate(row):
+ if cell is not None and cell not in result[colnum]:
+ result[colnum].append(cell)
+ return result
+
+
+ def is_free(self, x1, y1, x2, y2):
+ for rownum in range(y1, y2):
+ for colnum in range(x1, x2):
+ if self.matrix[rownum][colnum] is not None:
+ return False
+ return True
+
+
+ def row_is_free(self, row):
+ for cell in row:
+ if cell is not None:
+ return False
+ return True
+
+
+ def dump(self):
+ for row in self.matrix:
+ for cell in row:
+ if cell is None:
+ print "None",
+ else:
+ print "CELL",
+ print
=== added file 'bin/SpiffGtkWidgets/Calendar/Model.py'
--- bin/SpiffGtkWidgets/Calendar/Model.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/Model.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import gobject
+import datetime
+import calendar
+import util
+from MyCalendar import MyCalendar
+
+class Model(gobject.GObject):
+ __gsignals__ = {
+ 'event-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,)),
+ 'event-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,))
+ }
+
+ def __init__(self):
+ """
+ Constructor.
+
+ start -- datetime
+ end -- datetime
+ """
+ self.__gobject_init__()
+ self.next_event_id = 0
+ self.calendar = MyCalendar(calendar.SUNDAY)
+ self.events = {}
+
+
+ def get_week(self, date):
+ """
+ Returns a tuple (start, end), where "start" points to the first day
+ of the given week, and "end" points to the last day of given week.
+ """
+ return self.calendar.get_week(date)
+
+
+ def get_month(self, date):
+ """
+ Returns a tuple (start, end), where "start points to the first day
+ of the given month and "end" points to the last day of the given
+ month.
+ """
+ return self.calendar.get_month(date)
+
+
+ def get_month_weeks(self, date, fill = True):
+ """
+ Returns a tuple (start, end), where "start" points to the first day
+ of the first week of given month, and "end" points to the last day of
+ the last week of the same month.
+ """
+ return self.calendar.get_month_weeks(date, fill)
+
+
+ def get_day_name(self, date):
+ """
+ Returns the name of the given week day.
+ """
+ return self.calendar.get_day_name(date)
+
+
+ def get_month_name(self, date):
+ """
+ Returns the name of the given month.
+ """
+ return self.calendar.get_month_name(date)
+
+
+ def remove_event(self, event):
+ assert event is not None
+ if event.id is None:
+ return
+ del self.events[event.id]
+ self.emit('event-removed', event)
+
+
+ def add_event(self, event):
+ assert event is not None
+ assert event.id is None
+ self.events[self.next_event_id] = event
+ event.id = self.next_event_id
+ self.next_event_id += 1
+ self.emit('event-added', event)
+
+
+ def get_events(self, start, end):
+ """
+ Returns a list of all events that intersect with the given start
+ and end times.
+ """
+ events = []
+ for event in self.events.values():
+ if util.event_intersects(event, start, end):
+ events.append(event)
+ events.sort(util.event_days, reverse = True)
+ return events
+
+
+ def get_all_day_events(self, start, end, include_timed_events = False):
+ # Get a list of all-day events and sort them by length.
+ events = []
+ for event in self.get_events(start, end):
+ if event.all_day:
+ events.append(event)
+ continue
+ if include_timed_events \
+ and not util.same_day(event.start, event.end):
+ events.append(event)
+ return events
+
+
+ def get_normal_events(self, start, end, include_multi_day_events = True):
+ # Get a list of non-all-day events and sort them by length.
+ events = []
+ for event in self.get_events(start, end):
+ if not include_multi_day_events \
+ and not util.same_day(event.start, event.end):
+ continue
+ if not event.all_day:
+ events.append(event)
+ return events
=== added file 'bin/SpiffGtkWidgets/Calendar/MyCalendar.py'
--- bin/SpiffGtkWidgets/Calendar/MyCalendar.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/MyCalendar.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import datetime
+import calendar
+import locale
+
+
+class MyCalendar(object):
+ """
+ A wrapper around Python's calendar.Calendar() that doesn't suck.
+ """
+
+ def __init__(self, week_start = calendar.SUNDAY):
+ """
+ Constructor.
+
+ week_start -- the first day of the week, e.g. calendar.SUNDAY
+ """
+ assert week_start is not None
+ self.calendar = calendar.Calendar(week_start)
+
+
+ def get_week(self, date):
+ """
+ Returns a tuple (start, end), where "start" points to the first day
+ of the given week, and "end" points to the last day of given week.
+ """
+ month_tuple = date.timetuple()[:2]
+ week_tuple = date.timetuple()[:3]
+ weeks = self.calendar.monthdatescalendar(*month_tuple)
+ for week in weeks:
+ if week_tuple not in [d.timetuple()[:3] for d in week]:
+ continue
+ return week[0], week[-1]
+ raise Exception('No such week')
+
+
+ def get_month(self, date):
+ """
+ Returns a tuple (start, end), where "start points to the first day
+ of the given month and "end" points to the last day of the given
+ month.
+ """
+ date_tuple = date.timetuple()
+ year = date_tuple[0]
+ month = date_tuple[1]
+ last_day = calendar.monthrange(year, month)[1]
+ start = datetime.date(year, month, 1)
+ end = datetime.date(year, month, last_day)
+ return start, end
+
+
+ def get_month_weeks(self, date, fill = True):
+ """
+ Returns a tuple (start, end), where "start" points to the first day
+ of the first week of given month, and "end" points to the last day of
+ the last week of the same month.
+
+ If "fill" is True, this function always returns 6 weeks per month by
+ appending another week if the time span is shorter.
+ """
+ weeks = self.calendar.monthdatescalendar(*date.timetuple()[:2])
+ start = weeks[0][0]
+ end = weeks[-1][-1]
+ if fill:
+ end += datetime.timedelta(7) * (6 - len(weeks))
+ return start, end
+
+
+ def get_day_name(self, date):
+ day = calendar.weekday(*date.timetuple()[:3])
+ lang, encoding = locale.getlocale()
+ return calendar.day_name[day].decode(encoding)
+
+
+ def get_month_name(self, date):
+ return calendar.month_name[date.timetuple()[1]]
=== added file 'bin/SpiffGtkWidgets/Calendar/TableLayout.py'
--- bin/SpiffGtkWidgets/Calendar/TableLayout.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/TableLayout.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,391 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import copy
+import gobject
+import hippo
+
+def compute_homogeneus(width, count):
+ lengths = []
+ while count > 0:
+ length = int(float(width) / count)
+ lengths.append(length)
+ width -= length
+ count -= 1
+ lengths.append(width)
+ return lengths
+
+
+def compute_lengths(allocated, min_lengths, natural_lengths, expand_map=None):
+ count = len(min_lengths)
+ total_natural = sum(natural_lengths)
+ to_shrink = total_natural - allocated
+
+ if to_shrink > 0:
+ lengths = copy.copy(natural_lengths)
+ # We were allocated less than our natural height. We want to shrink lines
+ # as equally as possible, but no line more than it's maximum shrink.
+ #
+ # To do this, we process the lines in order of the available shrink from
+ # least available shrink to most
+ #
+ shrinks = []
+ for i in xrange(0, count):
+ shrinks.append((i, natural_lengths[i] - min_lengths[i]))
+ shrinks.sort(key=lambda t: t[1])
+
+ lines_remaining = count
+ for (i, shrink) in shrinks:
+ # If we can shrink the rest of the lines equally, do that. Otherwise
+ # shrink this line as much as possible
+ if shrink * lines_remaining >= to_shrink:
+ shrink = to_shrink // lines_remaining
+
+ lengths[i] -= shrink
+ lines_remaining -= 1
+ to_shrink -= shrink
+
+ return lengths
+ elif to_shrink < 0 and expand_map != None and len(expand_map) > 0:
+ expand_count = len(expand_map)
+ lengths = copy.copy(natural_lengths)
+ to_grow = -to_shrink
+
+ for i in xrange(0, count):
+ if i in expand_map:
+ delta = to_grow // expand_count
+ lengths[i] += delta
+ to_grow -= delta
+ expand_count -= 1
+
+ return lengths
+ else:
+ return natural_lengths
+
+
+class TableLayout(gobject.GObject,hippo.CanvasLayout):
+ """
+ A Canvas Layout manager that arranges items in a grid
+ """
+
+ def __init__(self, column_spacing=0, row_spacing=0):
+ """
+ Create a new TableLayout object
+
+ Arguments:
+ column_spacing: Spacing between columns of items
+ row_spacing: Spacing between rows. This is added between all rows, whether
+ they are rows of items or header rows. You can add more spacing above
+ or below a header item by setting i's padding.
+
+ """
+
+ gobject.GObject.__init__(self)
+ self.__box = None
+ self.__column_spacing = column_spacing
+ self.__row_spacing = row_spacing
+ self.__rows = -1
+ self.__cols = -1
+ self.__homogeneous_rows = False
+ self.__homogeneous_cols = False
+
+ self.__expand_rows = {}
+ self.__expand_columns = {}
+
+ def add(self, child, left=None, right=None, top=None, bottom=None, flags=0):
+ """
+ Add an item to the layout.
+
+ Arguments:
+ left:
+ right:
+ top:
+ bottom:
+ flags: flags to pass to hippo.CanvasBox.append(). Currently, all flags
+ passed in are ignored by this layout manager. (default=0)
+ """
+ if self.__box == None:
+ raise Exception("Layout must be set on a box before adding children")
+
+ if left == None and right == None:
+ raise Exception("Either right or left must be specified")
+
+ if left == None:
+ left = right - 1
+ elif right == None:
+ right = left + 1
+
+ if left < 0:
+ raise Exception("Left attach is < 0")
+ if right <= left:
+ raise Exception("Right attach is <= left attach")
+
+ if top == None and bottom == None:
+ raise Exception("Either bottom or top must be specified")
+
+ if top == None:
+ top = bottom - 1
+ elif bottom == None:
+ bottom = top + 1
+
+ if top < 0:
+ raise Exception("Top attach is < 0")
+ if bottom <= top:
+ raise Exception("Bottom attach is <= top")
+
+ self.__box.append(child, flags=flags)
+ box_child = self.__box.find_box_child(child)
+ box_child.left = left
+ box_child.right = right
+ box_child.top = top
+ box_child.bottom = bottom
+
+ def __set_expand(self, expands, i, expand):
+ if expand:
+ expands[i] = True
+ else:
+ try:
+ del expands[column]
+ except KeyError:
+ pass
+
+ def set_column_expand(self, column, expand):
+ self.__set_expand(self.__expand_columns, column, expand)
+
+ def set_row_expand(self, row, expand):
+ self.__set_expand(self.__expand_rows, row, expand)
+
+ def set_row_count(self, rows):
+ self.__rows = rows
+
+ # Remove items from the expand map, if necessary.
+ expand = self.__expand_rows
+ self.__expand_rows = {}
+ for row in expand:
+ if row < self.__rows:
+ self.__expand_rows[row] = True
+
+ def set_column_count(self, cols):
+ self.__cols = cols
+
+ # Remove items from the expand map, if necessary.
+ expand = self.__expand_columns
+ self.__expand_columns = {}
+ for col in expand:
+ if col < self.__cols:
+ self.__expand_columns[col] = True
+
+ def set_size(self, rows, cols):
+ self.set_row_count(rows)
+ self.set_column_count(cols)
+
+ def set_homogeneus_rows(self, homogeneus):
+ self.__homogeneous_rows = homogeneus
+
+ def set_homogeneus_columns(self, homogeneus):
+ self.__homogeneous_cols = homogeneus
+
+ def do_set_box(self, box):
+ self.__box = box
+
+ def get_column_count(self):
+ columns = max(0, self.__cols)
+
+ for box_child in self.__box.get_layout_children():
+ columns = max(columns, box_child.right)
+
+ for column in self.__expand_columns:
+ columns = max(columns, column + 1)
+
+ return columns
+
+ def get_row_count(self):
+ rows = max(0, self.__rows)
+
+ for box_child in self.__box.get_layout_children():
+ rows = max(rows, box_child.bottom)
+
+ for row in self.__expand_rows:
+ rows = max(rows, row + 1)
+
+ return rows
+
+ def __get_total_column_spacing(self, count):
+ if count > 1:
+ return (count - 1) * self.__column_spacing
+ else:
+ return 0
+
+ def __get_total_row_spacing(self, count):
+ if count > 1:
+ return (count - 1) * self.__row_spacing
+ else:
+ return 0
+
+ def get_total_column_spacing(self):
+ return self.__get_total_column_spacing(self.get_column_count())
+
+ def get_total_row_spacing(self):
+ return self.__get_total_row_spacing(self.get_row_count())
+
+ def __get_request(self, count, get_start_end, get_request):
+ min_lengths = [0 for i in xrange(0,count)]
+ natural_lengths = [0 for i in xrange(0,count)]
+
+ # First process non-spanned children
+ for box_child in self.__box.get_layout_children():
+ start, end = get_start_end(box_child)
+ if end != start + 1:
+ continue
+
+ (min_length, natural_length) = get_request(box_child)
+
+ min_lengths[start] = max(min_lengths[start], min_length)
+ natural_lengths[start] = max(natural_lengths[start], natural_length)
+
+ # Then process spanned children
+ for box_child in self.__box.get_layout_children():
+ start, end = get_start_end(box_child)
+
+ if end == start + 1:
+ continue
+
+ (min_length, natural_length) = get_request(box_child)
+
+ current_min_length = 0
+ current_natural_length = 0
+ for i in range(start, end):
+ current_min_length += min_lengths[i]
+ current_natural_length += natural_lengths[i]
+
+ if current_min_length < min_length:
+ excess = min_length - current_min_length
+ child_count = end - start
+
+ for i in range(start, end):
+ delta = excess // child_count
+ min_lengths[i] += delta
+ excess -= delta
+ child_count -= 1
+
+ if current_natural_length < natural_length:
+ excess = natural_length - current_natural_length
+ child_count = end - start
+
+ for i in range(start, end):
+ delta = excess // child_count
+ natural_lengths[i] += delta
+ excess -= delta
+ child_count -= 1
+
+ return (min_lengths, natural_lengths)
+
+ def do_get_width_request(self):
+ column_count = self.get_column_count()
+ total_column_spacing = self.__get_total_column_spacing(column_count)
+
+ (min_widths, natural_widths) = self.__get_request(column_count,
+ lambda box_child: (box_child.left, box_child.right),
+ lambda box_child: box_child.get_width_request())
+
+ if self.__homogeneous_cols:
+ max_min_width = 0
+ for width in min_widths:
+ max_min_width = max(max_min_width, width)
+ max_nat_width = 0
+ for width in natural_widths:
+ max_nat_width = max(max_nat_width, width)
+ min_widths = [max_min_width] * column_count
+ natural_widths = [max_nat_width] * column_count
+
+ self.__min_widths = min_widths
+ self.__natural_widths = natural_widths
+
+ return (sum(self.__min_widths) + total_column_spacing, sum(self.__natural_widths) + total_column_spacing)
+
+ def __compute_column_widths(self, width):
+ cols = max(self.__cols, len(self.__min_widths))
+ if self.__homogeneous_cols and width > 0 and cols > 0:
+ return compute_homogeneus(width, cols)
+ return compute_lengths(width, self.__min_widths, self.__natural_widths, self.__expand_columns)
+
+ def do_get_height_request(self, width):
+ row_count = self.get_row_count()
+ total_row_spacing = self.__get_total_row_spacing(row_count)
+ total_column_spacing = self.get_total_column_spacing()
+ widths = self.__compute_column_widths(width - total_column_spacing)
+
+ def get_child_height_request(box_child):
+ child_width = 0
+ for i in xrange(box_child.left, box_child.right):
+ child_width += widths[i]
+
+ return box_child.get_height_request(child_width)
+
+ (min_heights, natural_heights) = self.__get_request(row_count,
+ lambda box_child: (box_child.top, box_child.bottom),
+ get_child_height_request)
+
+ self.__min_heights = min_heights
+ self.__natural_heights = natural_heights
+
+# _logger.debug("height_request: min_heights=%s, natural_heights=%s", min_heights, natural_heights)
+
+ return (sum(self.__min_heights) + total_row_spacing, sum(self.__natural_heights) + total_row_spacing)
+
+ def __compute_row_heights(self, height):
+ rows = self.get_row_count()
+ if self.__homogeneous_rows and height > 0 and rows > 0:
+ return compute_homogeneus(height, rows)
+ return compute_lengths(height, self.__min_heights, self.__natural_heights, self.__expand_rows)
+
+ def do_allocate(self, x, y, width, height, requested_width, requested_height, origin_changed):
+ column_count = len(self.__min_widths)
+ total_column_spacing = self.__get_total_column_spacing(column_count)
+
+ row_count = len(self.__min_heights)
+ total_row_spacing = self.__get_total_row_spacing(row_count)
+
+ widths = self.__compute_column_widths(width - total_column_spacing)
+ heights = self.__compute_row_heights(height - total_row_spacing)
+
+# _logger.debug("allocate: widths=%s, heights=%s", widths, heights)
+
+ x = 0
+ xs = []
+ for width in widths:
+ xs.append(x)
+ x += width + self.__column_spacing
+ xs.append(x)
+
+ y = 0
+ ys = []
+ for height in heights:
+ ys.append(y)
+ y += height + self.__row_spacing
+ ys.append(y)
+
+ for box_child in self.__box.get_layout_children():
+ x = xs[box_child.left]
+ width = xs[box_child.right] - x - self.__column_spacing
+ y = ys[box_child.top]
+ height = ys[box_child.bottom] - y - self.__row_spacing
+
+ box_child.allocate(x, y, width, height, origin_changed)
+
+gobject.type_register(TableLayout)
=== added file 'bin/SpiffGtkWidgets/Calendar/__init__.py'
--- bin/SpiffGtkWidgets/Calendar/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+from Calendar import Calendar
+from Model import Model
+from Event import Event
+
+import inspect
+__all__ = [name for name, obj in locals().items()
+ if not (name.startswith('_') or inspect.ismodule(obj))]
=== added file 'bin/SpiffGtkWidgets/Calendar/util.py'
--- bin/SpiffGtkWidgets/Calendar/util.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/Calendar/util.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+# Copyright (C) 2008-2011 Samuel Abels
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License
+# version 3 as published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import datetime
+
+def time_delta(datetime1, datetime2):
+ delta = datetime1 - datetime2
+ if delta < datetime.timedelta():
+ return -delta
+ return delta
+
+
+def same_day(date1, date2):
+ return date1.timetuple()[:3] == date2.timetuple()[:3]
+
+
+def end_of_day(date):
+ start = datetime.datetime(*date.timetuple()[:3])
+ return start + datetime.timedelta(1) - datetime.timedelta(0, 0, 0, 1)
+
+
+def previous_day(date):
+ return date - datetime.timedelta(1)
+
+
+def next_day(date):
+ return date + datetime.timedelta(1)
+
+
+def previous_week(date):
+ return date - datetime.timedelta(7)
+
+
+def next_week(date):
+ return date + datetime.timedelta(7)
+
+
+def previous_month(cal, date):
+ year, month, day = date.timetuple()[:3]
+ if month == 1:
+ year -= 1
+ month = 12
+ else:
+ month -= 1
+ prev_month_days = [d for d in cal.itermonthdays(year, month)]
+ if day not in prev_month_days:
+ day = max(prev_month_days)
+ return datetime.datetime(year, month, day)
+
+
+def next_month(cal, date):
+ year, month, day = date.timetuple()[:3]
+ if month == 12:
+ year += 1
+ month = 1
+ else:
+ month += 1
+ next_month_days = [d for d in cal.itermonthdays(year, month)]
+ if day not in next_month_days:
+ day = max(next_month_days)
+ return datetime.datetime(year, month, day)
+
+
+def event_days(event1, event2):
+ return time_delta(event1.start, event1.end).days \
+ - time_delta(event2.start, event2.end).days
+
+
+def event_intersects(event, start, end = None):
+ if end is None:
+ end = start
+ return (event.start >= start and event.start < end) \
+ or (event.end > start and event.end <= end) \
+ or (event.start < start and event.end > end)
+
+
+def get_intersection_list(list, start, end):
+ intersections = []
+ for event in list:
+ if event_intersects(event, start, end):
+ intersections.append(event)
+ return intersections
+
+
+def count_intersections(list, start, end):
+ intersections = 0
+ for event in list:
+ if event_intersects(event, start, end):
+ intersections += 1
+ return intersections
+
+
+def count_parallel_events(list, start, end):
+ """
+ Given a list of events, this function returns the maximum number of
+ parallel events in the given timeframe.
+ """
+ parallel = 0
+ i = 0
+ for i, event1 in enumerate(list):
+ if not event_intersects(event1, start, end):
+ continue
+ parallel = max(parallel, 1)
+ for f in range(i + 1, len(list)):
+ event2 = list[f]
+ new_start = max(event1.start, event2.start)
+ new_end = min(event1.end, event2.end)
+ if event_intersects(event2, start, end) \
+ and event_intersects(event2, new_start, new_end):
+ n = count_parallel_events(list[f:], new_start, new_end)
+ parallel = max(parallel, n + 1)
+ return parallel
=== added file 'bin/SpiffGtkWidgets/__init__.py'
--- bin/SpiffGtkWidgets/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import Calendar
+import color
+
+import inspect
+__all__ = [name for name, obj in locals().items()
+ if not (name.startswith('_') or inspect.ismodule(obj))]
=== added file 'bin/SpiffGtkWidgets/color.py'
--- bin/SpiffGtkWidgets/color.py 1970-01-01 00:00:00 +0000
+++ bin/SpiffGtkWidgets/color.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk.gdk
+
+########################
+# Explicit converters.
+########################
+def str2gdk(name):
+ return gtk.gdk.color_parse(name)
+
+def int2gdk(i):
+ red = (i >> 24) & 0xff
+ green = (i >> 16) & 0xff
+ blue = (i >> 8) & 0xff
+ return gtk.gdk.Color(red * 256, green * 256, blue * 256)
+
+def rgb2gdk(color):
+ red = color[0] * 65535.0
+ green = color[1] * 65535.0
+ blue = color[2] * 65535.0
+ return gtk.gdk.Color(red, green, blue)
+
+def rgba2gdk(color):
+ red = color[0] * 65535.0
+ green = color[1] * 65535.0
+ blue = color[2] * 65535.0
+ value = color[3] * 65535.0 # not supported by gdk.Color
+ return gtk.gdk.Color(red, green, blue)
+
+def gdk2int(color):
+ return (color.red / 256 << 24) \
+ | (color.green / 256 << 16) \
+ | (color.blue / 256 << 8) \
+ | 0xff
+
+def gdk2rgb(color):
+ return (color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0)
+
+def gdk2rgba(color):
+ return (color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0, 1)
+
+########################
+# Automatic converters.
+########################
+def to_gdk(color):
+ if isinstance(color, gtk.gdk.Color):
+ return color
+ elif type(color) == type(0) or type(color) == type(0l):
+ return int2gdk(color)
+ elif type(color) == type(''):
+ return str2gdk(color)
+ elif type(color) == type(()) and len(color) == 3:
+ return rgb2gdk(color)
+ elif type(color) == type(()) and len(color) == 4:
+ return rgba2gdk(color)
+ else:
+ raise TypeError('%s is not a known color type' % type(color))
+
+def to_int(color):
+ return gdk2int(to_gdk(color))
+
+def to_rgb(color):
+ return gdk2rgb(to_gdk(color))
+
+def to_rgba(color):
+ return gdk2rgba(to_gdk(color))
=== added file 'bin/__init__.py'
--- bin/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added directory 'bin/common'
=== added file 'bin/common/__init__.py'
--- bin/common/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/common/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from common import *
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/common/common.py'
--- bin/common/common.py 1970-01-01 00:00:00 +0000
+++ bin/common/common.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,714 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+from gtk import glade
+import gobject
+from cgi import escape
+import tools
+import gettext
+
+import os
+import sys
+import platform
+import release
+import common
+import logging
+from options import options
+import service
+import locale
+import ConfigParser
+
+import threading
+import time
+import pango
+import rpc
+
+class action_tips(object):
+ def __init__(self, help):
+ self.help = help
+ self.help_frame = False
+ self.create_action_tip()
+
+ def close_or_disable_tips(self, button, disable_all=False):
+ if self.help_frame:
+ if disable_all:
+ rpc.session.rpc_exec_auth('/object', 'execute', 'res.users', 'write',
+ [rpc.session.uid], {'menu_tips':False})
+ self.help_frame.destroy()
+ self.help_frame = False
+ return True
+
+ def create_action_tip(self):
+ if self.help.get('msg', False):
+ msg = self.help.get('msg', '')
+ msg = msg.replace('\n',' ').replace('\t',' ')
+ if len(msg) < 80:
+ msg = '\t\t \t \t' + msg
+ title = self.help.get('title', '')
+
+ help_label = gtk.Label()
+ help_label.set_use_markup(True)
+ def size_allocate(label, allocation):
+ label.set_size_request( allocation.width - 2, -1 )
+ help_label.connect( "size-allocate", size_allocate )
+ help_label.set_label('<span font_desc="italic" foreground="black">%s</span>'% (msg))
+
+ help_label.set_alignment(0.3, 1)
+ help_label.set_line_wrap(True)
+ help_label.set_justify(gtk.JUSTIFY_FILL)
+ layout = help_label.get_layout()
+ layout.set_wrap(pango.WRAP_WORD_CHAR)
+
+ table = gtk.Table(1, 8)
+ table.set_homogeneous(False)
+ table.set_col_spacings(40)
+ table.attach(help_label, 3, 6, 0, 1, ypadding=10)
+ label_box = gtk.EventBox()
+ label_box.add(table)
+
+ # Close current tip button
+ closebtn = gtk.Button(_('Close current tip'))
+ closebtn.set_tooltip_markup(_('''<span foreground="darkred"><b>Close Current Tip:</b></span>
+This will hide the current tip. It will be displayed again next time you open this menu item, unless you disable all tips using the <b>'Menu Tips'</b> option in the user preferences.'''))
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
+ closebtn.set_image(image)
+ closebtn.set_relief(gtk.RELIEF_NONE)
+ closebtn.unset_flags(gtk.CAN_FOCUS)
+ closebtn.connect('clicked', self.close_or_disable_tips)
+
+ # Disable button
+ disablebtn = gtk.Button(_('Disable all tips'))
+ disablebtn.set_tooltip_markup(_('''<span foreground="darkred"><b>Disable all tips:</b></span>
+This will disable the display of tips on all menu items.
+To re-enable tips you need to check the <b>'Menu Tips'</b> option in the user preferences.'''))
+ image1 = gtk.Image()
+ image1.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
+ disablebtn.set_image(image1)
+ disablebtn.set_relief(gtk.RELIEF_NONE)
+ disablebtn.unset_flags(gtk.CAN_FOCUS)
+ disablebtn.connect('clicked', self.close_or_disable_tips, True)
+
+ # frame Title with the two above created buttons
+ box = gtk.HBox()
+ box_label = gtk.Label()
+ box_label.set_use_markup(True)
+ tip_title = '<b>' + _('Tips') + '</b>'
+ if title:
+ tip_title = '<b> %s - %s</b>' % ( to_xml(title), _('Tips') )
+ box_label.set_label(tip_title)
+ box.pack_start(box_label, True, True)
+ box.pack_end(disablebtn, False, False)
+ box.pack_end(closebtn, False, False)
+ box.show_all()
+ # finally the frame
+ self.help_frame = gtk.Frame()
+ self.help_frame.set_label_widget(box)
+ self.help_frame.set_label_align(0.5,0.5)
+ self.help_frame.add(label_box)
+ self.help_frame.show_all()
+ return True
+ return False
+
+
+def OpenERP_Progressbar(parent=None, title=_('OpenERP Computing')):
+ if not parent:
+ parent = service.LocalService('gui.main').window
+
+ win = gtk.Dialog('OpenERP', parent, gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT)
+ win.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+ win.set_title(_(title))
+ win.set_resizable(False)
+ vbox = gtk.VBox(False, 0)
+
+ hbox = gtk.HBox(False, 13)
+ hbox.set_border_width(10)
+
+ img = gtk.Image()
+ img.set_from_stock('gtk-dialog-info', gtk.ICON_SIZE_DIALOG)
+ hbox.pack_start(img, expand=True, fill=False)
+
+ vbox2 = gtk.VBox(False, 0)
+ label = gtk.Label()
+ label.set_markup('<b>'+_('Operation in progress')+'</b>')
+ label.set_alignment(0.0, 0.5)
+ vbox2.pack_start(label, expand=True, fill=False)
+ vbox2.pack_start(gtk.HSeparator(), expand=True, fill=True)
+ vbox2.pack_start(gtk.Label(_("Please wait,\nthis operation may take a while...")), expand=True, fill=False)
+ hbox.pack_start(vbox2, expand=True, fill=True)
+ vbox.pack_start(hbox)
+
+ pb = gtk.ProgressBar()
+ pb.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
+ vbox.pack_start(pb, expand=True, fill=False)
+
+ win.vbox.pack_start(vbox, expand=True, fill=True)
+ win.set_has_separator(False)
+ win.set_transient_for(parent)
+ win.show_all()
+ return win, pb
+
+def _search_file(file, dir='path.share'):
+ tests = [
+ lambda x: os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), x),
+ lambda x: os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'pixmaps', x),
+ lambda x: os.path.join(options[dir], x),
+ ]
+ for func in tests:
+ x = func(file)
+ if os.path.exists(x):
+ return x
+ return file
+
+terp_path = _search_file
+terp_path_pixmaps = lambda x: _search_file(x, 'path.pixmaps')
+
+try:
+ OPENERP_ICON = gtk.gdk.pixbuf_new_from_file(terp_path_pixmaps('openerp-icon.png'))
+except gobject.GError, e:
+ log = logging.getLogger('init')
+ log.fatal(str(e))
+ log.fatal(_('Ensure that the file %s is correct') % options.rcfile)
+ exit(1)
+
+def selection(title, values, alwaysask=False, parent=None):
+ if not values or len(values)==0:
+ return None
+ elif len(values)==1 and (not alwaysask):
+ key = values.keys()[0]
+ return (key, values[key])
+
+ xml = glade.XML(terp_path("openerp.glade"), "win_selection", gettext.textdomain())
+ win = xml.get_widget('win_selection')
+ if not parent:
+ parent = service.LocalService('gui.main').window
+ win.set_icon(OPENERP_ICON)
+ win.set_transient_for(parent)
+
+ label = xml.get_widget('win_sel_title')
+ if title:
+ label.set_text(title)
+
+ list = xml.get_widget('win_sel_tree')
+ list.get_selection().set_mode('single')
+ cell = gtk.CellRendererText()
+ column = gtk.TreeViewColumn("Widget", cell, text=0)
+ list.append_column(column)
+ list.set_search_column(0)
+ model = gtk.ListStore(gobject.TYPE_STRING)
+ keys = values.keys()
+ keys.sort()
+
+ for val in keys:
+ model.append([val])
+
+ list.set_model(model)
+ list.connect('row-activated', lambda x,y,z: win.response(gtk.RESPONSE_OK) or True)
+
+ ok = False
+ while not ok:
+ response = win.run()
+ ok = True
+ res = None
+ if response == gtk.RESPONSE_OK:
+ sel = list.get_selection().get_selected()
+ if sel:
+ (model, iter) = sel
+ if iter:
+ res = model.get_value(iter, 0)
+ try:
+ res = (res, values[res.decode('utf8')])
+ except:
+ res = (res, values[res])
+ else:
+ ok = False
+ else:
+ ok = False
+ else:
+ res = None
+ parent.present()
+ win.destroy()
+ return res
+
+class upload_data_thread(threading.Thread):
+ def __init__(self, email, data, type, supportid):
+ self.args = [('email',email),('type',type),('supportid',supportid),('data',data)]
+ super(upload_data_thread,self).__init__()
+ def run(self):
+ try:
+ import urllib
+ args = urllib.urlencode(self.args)
+ fp = urllib.urlopen('http://www.openerp.com/scripts/survey.php', args)
+ fp.read()
+ fp.close()
+ except:
+ pass
+
+def upload_data(email, data, type='SURVEY', supportid=''):
+ a = upload_data_thread(email, data, type, supportid)
+ a.start()
+ return True
+
+def file_selection(title, filename='', parent=None, action=gtk.FILE_CHOOSER_ACTION_OPEN, preview=True, multi=False, filters=None):
+ if action == gtk.FILE_CHOOSER_ACTION_OPEN:
+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)
+ else:
+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)
+
+ win = gtk.FileChooserDialog(title, None, action, buttons)
+ if not parent:
+ parent = service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(OPENERP_ICON)
+ win.set_current_folder(options['client.default_path'])
+ win.set_select_multiple(multi)
+ win.set_default_response(gtk.RESPONSE_OK)
+ if filters is not None:
+ for filter in filters:
+ win.add_filter(filter)
+ if filename:
+ win.set_current_name(filename)
+
+ def update_preview_cb(win, img):
+ filename = win.get_preview_filename()
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 128, 128)
+ img.set_from_pixbuf(pixbuf)
+ have_preview = True
+ except:
+ have_preview = False
+ win.set_preview_widget_active(have_preview)
+ return
+
+ if preview:
+ img_preview = gtk.Image()
+ win.set_preview_widget(img_preview)
+ win.connect('update-preview', update_preview_cb, img_preview)
+
+ button = win.run()
+ if button!=gtk.RESPONSE_OK:
+ win.destroy()
+ return False
+ if not multi:
+ filepath = win.get_filename()
+ if filepath:
+ filepath = filepath.decode('utf-8')
+ try:
+ options['client.default_path'] = os.path.dirname(filepath)
+ except:
+ pass
+ parent.present()
+ win.destroy()
+ return filepath
+ else:
+ filenames = win.get_filenames()
+ if filenames:
+ filenames = [x.decode('utf-8') for x in filenames]
+ try:
+ options['client.default_path'] = os.path.dirname(filenames[0])
+ except:
+ pass
+ parent.present()
+ win.destroy()
+ return filenames
+
+def support(*args):
+ wid_list = ['email_entry','id_entry','name_entry','phone_entry','company_entry','error_details','explanation_textview','remark_textview']
+ required_wid = ['email_entry', 'name_entry', 'company_name', 'id_entry']
+ support_id = options['support.support_id']
+ recipient = options['support.recipient']
+
+ sur = glade.XML(terp_path("openerp.glade"), "dia_support",gettext.textdomain())
+ win = sur.get_widget('dia_support')
+ parent = service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(OPENERP_ICON)
+ win.show_all()
+ sur.get_widget('id_entry1').set_text(support_id)
+
+ response = win.run()
+ if response == gtk.RESPONSE_OK:
+ fromaddr = sur.get_widget('email_entry1').get_text()
+ id_contract = sur.get_widget('id_entry1').get_text()
+ name = sur.get_widget('name_entry1').get_text()
+ phone = sur.get_widget('phone_entry1').get_text()
+ company = sur.get_widget('company_entry1').get_text()
+
+ urgency = sur.get_widget('urgency_combo1').get_active_text()
+
+ buffer = sur.get_widget('explanation_textview1').get_buffer()
+ explanation = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
+
+ buffer = sur.get_widget('remark_textview').get_buffer()
+ remarks = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
+
+ content = name +"(%s, %s, %s)"%(id_contract, company, phone) + _(" has reported the following bug:\n") + explanation + "\n" + _("remarks") + ":\n" + remarks
+
+ if upload_data(fromaddr, content, 'support', id_contract):
+ common.message(_('Support request sent !'), parent=win)
+
+ parent.present()
+ win.destroy()
+ return True
+
+def error(title, message, details='', parent=None, disconnected_mode=False):
+ """
+ Show an error dialog with the support request or the maintenance
+ """
+ log = logging.getLogger('common.message')
+ details = get_client_environment() + details
+ log.error('Message %s: %s' % (str(message),details))
+
+ show_message = True
+
+ if not disconnected_mode:
+ try:
+ maintenance = rpc.session.rpc_exec_auth_try('/object', 'execute', 'maintenance.contract', 'status')
+ except:
+ maintenance = False
+ if maintenance:
+ if maintenance['status'] == 'none':
+ maintenance_contract_message=_("""
+<b>An unknown error has been reported.</b>
+
+<b>You do not have a valid OpenERP publisher warranty contract !</b>
+If you are using OpenERP in production, it is highly suggested to subscribe
+a publisher warranty program.
+
+The OpenERP publisher warranty contract provides you a bugfix guarantee and an
+automatic migration system so that we can fix your problems within a few
+hours. If you had a publisher warranty contract, this error would have been sent
+to the quality team of the OpenERP editor.
+
+The publisher warranty program offers you:
+* Automatic migrations on new versions,
+* A bugfix guarantee,
+* Monthly announces of potential bugs and their fixes,
+* Security alerts by email and automatic migration,
+* Access to the customer portal.
+
+You can use the link bellow for more information. The detail of the error
+is displayed on the second tab.
+""")
+ elif maintenance['status'] == 'partial':
+ maintenance_contract_message=_("""
+<b>An unknown error has been reported.</b>
+
+Your publisher warranty contract does not cover all modules installed in your system !
+If you are using OpenERP in production, it is highly suggested to upgrade your
+contract.
+
+If you have developed your own modules or installed third party module, we
+can provide you an additional publisher warranty contract for these modules. After
+having reviewed your modules, our quality team will ensure they will migrate
+automatically for all future stable versions of OpenERP at no extra cost.
+
+Here is the list of modules not covered by your publisher warranty contract:
+%s
+
+You can use the link bellow for more information. The detail of the error
+is displayed on the second tab.""") % (", ".join(maintenance['uncovered_modules']), )
+ else:
+ show_message = False
+
+ else:
+ maintenance_contract_message=_("""
+<b>An unknown error has been reported.</b>
+
+<b>You do not have a valid OpenERP publisher warranty contract !</b>
+If you are using OpenERP in production, it is highly suggested to subscribe
+a publisher warranty program.
+
+The OpenERP publisher warranty contract provides you a bugfix guarantee and an
+automatic migration system so that we can fix your problems within a few
+hours. If you had a publisher warranty contract, this error would have been sent
+to the quality team of the OpenERP editor.
+
+The publisher warranty program offers you:
+* Automatic migrations on new versions,
+* A bugfix guarantee,
+* Monthly announces of potential bugs and their fixes,
+* Security alerts by email and automatic migration,
+* Access to the customer portal.
+
+You can use the link bellow for more information. The detail of the error
+is displayed on the second tab.
+""")
+
+ xmlGlade = glade.XML(terp_path('win_error.glade'), 'dialog_error', gettext.textdomain())
+ win = xmlGlade.get_widget('dialog_error')
+ if not parent:
+ parent=service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(OPENERP_ICON)
+ win.set_title("OpenERP - %s" % title)
+
+ if not isinstance(message, basestring):
+ message = str(message)
+ xmlGlade.get_widget('title_error').set_markup("<i>%s</i>" % escape(message))
+
+ details_buffer = gtk.TextBuffer()
+ details_buffer.set_text(details)
+ xmlGlade.get_widget('details_explanation').set_buffer(details_buffer)
+ if show_message:
+ xmlGlade.get_widget('maintenance_explanation').set_markup(maintenance_contract_message)
+
+ xmlGlade.get_widget('notebook').remove_page(int(show_message))
+ if not show_message:
+ def send(widget):
+ def get_text_from_text_view(textView):
+ """Retrieve the buffer from a text view and return the content of this buffer"""
+ buffer = textView.get_buffer()
+ return buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
+
+ # Use details_buffer
+ tb = get_text_from_text_view(xmlGlade.get_widget('details_explanation'))
+ explanation = get_text_from_text_view(xmlGlade.get_widget('explanation_textview'))
+ remarks = get_text_from_text_view(xmlGlade.get_widget('remarks_textview'))
+ summary = xmlGlade.get_widget('summary_entry').get_text()
+
+ if rpc.session.rpc_exec_auth_try('/object', 'execute', 'maintenance.contract', 'send', tb, explanation, remarks, summary):
+ common.message(_('Your problem has been sent to the quality team !\nWe will recontact you after analysing the problem.'), parent=win)
+ win.destroy()
+ else:
+ common.message(_('Your problem could *NOT* be sent to the quality team !\nPlease report this error manually at:\n\t%s') % ('http://openerp.com/report_bug.html',), title=_('Error'), type=gtk.MESSAGE_ERROR, parent=win)
+
+ xmlGlade.signal_connect('on_button_send_clicked', send)
+ xmlGlade.signal_connect('on_closebutton_clicked', lambda x : win.destroy())
+ response = win.run()
+ parent.present()
+ win.destroy()
+ return True
+
+def message(msg, title=None, type=gtk.MESSAGE_INFO, parent=None):
+ if not parent:
+ parent=service.LocalService('gui.main').window
+ dialog = gtk.MessageDialog(parent,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+ type, gtk.BUTTONS_OK)
+ msg = to_xml(msg)
+ if title is not None:
+ msg = '<b>%s</b>\n\n%s' % (to_xml(title), msg)
+ dialog.set_icon(OPENERP_ICON)
+ dialog.set_markup(msg)
+ dialog.show_all()
+ dialog.run()
+ parent.present()
+ dialog.destroy()
+ return True
+
+def to_xml(s):
+ from cgi import escape
+ return escape(s)
+
+def message_box(title, msg, parent=None):
+ dia = glade.XML(terp_path("openerp.glade"), "dia_message_box",gettext.textdomain())
+ win = dia.get_widget('dia_message_box')
+ l = dia.get_widget('msg_title')
+ l.set_text(title)
+
+ msg_area = dia.get_widget('msg_tv')
+ buffer = msg_area.get_buffer()
+ iter_start = buffer.get_start_iter()
+ buffer.insert(iter_start, msg)
+ msg_area.set_sensitive(False)
+
+ if not parent:
+ parent=service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(OPENERP_ICON)
+
+ response = win.run()
+ parent.present()
+ win.destroy()
+ return True
+
+
+def warning(msg, title=None, parent=None):
+ return message(msg=msg, title=title, type=gtk.MESSAGE_WARNING, parent=parent)
+
+def sur(msg, parent=None):
+ if not parent:
+ parent=service.LocalService('gui.main').window
+ sur = glade.XML(terp_path("openerp.glade"), "win_sur",gettext.textdomain())
+ win = sur.get_widget('win_sur')
+ win.set_transient_for(parent)
+ win.show_all()
+ l = sur.get_widget('lab_question')
+ l.set_text(msg)
+
+ if not parent:
+ parent=service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(OPENERP_ICON)
+
+ response = win.run()
+ parent.present()
+ win.destroy()
+ return response == gtk.RESPONSE_OK
+
+def sur_3b(msg, parent=None):
+ sur = glade.XML(terp_path("openerp.glade"), "win_quest_3b",gettext.textdomain())
+ win = sur.get_widget('win_quest_3b')
+ l = sur.get_widget('label')
+ l.set_text(msg)
+
+ if not parent:
+ parent=service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(OPENERP_ICON)
+
+ response = win.run()
+ parent.present()
+ win.destroy()
+ if response == gtk.RESPONSE_YES:
+ return 'ok'
+ elif response == gtk.RESPONSE_NO:
+ return 'ko'
+ elif response == gtk.RESPONSE_CANCEL:
+ return 'cancel'
+ else:
+ return 'cancel'
+
+def ask(question, parent=None):
+ dia = glade.XML(terp_path('openerp.glade'), 'win_quest', gettext.textdomain())
+ win = dia.get_widget('win_quest')
+ label = dia.get_widget('label1')
+ label.set_text(question)
+ entry = dia.get_widget('entry')
+
+ if not parent:
+ parent=service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(OPENERP_ICON)
+
+ response = win.run()
+ parent.present()
+ # grab a safe copy of the entered text before destroy()
+ #to avoid GTK bug https://bugzilla.gnome.org/show_bug.cgi?id=613241
+ value = entry.get_text()
+ win.destroy()
+ if response == gtk.RESPONSE_CANCEL:
+ return None
+ else:
+ return value
+
+def concurrency(resource, id, context, parent=None):
+ dia = glade.XML(common.terp_path("openerp.glade"),'dialog_concurrency_exception',gettext.textdomain())
+ win = dia.get_widget('dialog_concurrency_exception')
+
+ if not parent:
+ parent=service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(OPENERP_ICON)
+
+ res= win.run()
+ parent.present()
+ win.destroy()
+
+ if res == gtk.RESPONSE_OK:
+ return True
+ if res == gtk.RESPONSE_APPLY:
+ obj = service.LocalService('gui.window')
+ obj.create(False, resource, id, [], 'form', None, context,'form,tree')
+ return False
+
+def open_file(value, parent):
+ filetype = {}
+ if options['client.filetype']:
+ if isinstance(options['client.filetype'], str):
+ filetype = eval(options['client.filetype'])
+ else:
+ filetype = options['client.filetype']
+ root, ext = os.path.splitext(value)
+ cmd = False
+ if ext[1:] in filetype:
+ cmd = filetype[ext[1:]] % (value)
+ if not cmd:
+ cmd = file_selection(_('Open with...'),
+ parent=parent)
+ if cmd:
+ cmd = cmd + ' %s'
+ filetype[ext[1:]] = cmd
+ options['client.filetype'] = filetype
+ options.save()
+ cmd = cmd % (value)
+ if cmd:
+ pid = os.fork()
+ if not pid:
+ pid = os.fork()
+ if not pid:
+ prog, args = cmd.split(' ', 1)
+ args = [os.path.basename(prog)] + args.split(' ')
+ try:
+ os.execvp(prog, args)
+ except:
+ pass
+ time.sleep(0.1)
+ sys.exit(0)
+ os.waitpid(pid, 0)
+
+
+# Color set
+
+colors = {
+ 'invalid':'#ff6969',
+ 'readonly':'#eeebe7',
+ 'required':'#d2d2ff',
+ 'normal':'white'
+}
+
+def get_client_environment():
+ try:
+ rev_id = os.popen('bzr revision-info').read()
+ if not rev_id:
+ rev_id = 'Bazaar Package not Found !'
+ except Exception,e:
+ rev_id = 'Exception: %s\n' % (tools.ustr(e))
+
+ os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] )
+ if not os_lang:
+ os_lang = 'NOT SET'
+
+ environment = '\nEnvironment Information : \n' \
+ 'System : %s\n' \
+ 'OS Name : %s\n' \
+ %(platform.platform(), platform.os.name)
+ if os.name == 'posix':
+ if platform.system() == 'Linux':
+ lsbinfo = os.popen('lsb_release -a').read()
+ environment += '%s'%(lsbinfo)
+ else:
+ environment += 'Your System is not lsb compliant\n'
+ environment += 'Operating System Release : %s\n' \
+ 'Operating System Version : %s\n' \
+ 'Operating System Architecture : %s\n' \
+ 'Operating System Locale : %s\n'\
+ 'Python Version : %s\n'\
+ 'OpenERP-Client Version : %s\n'\
+ 'Last revision No. & ID :%s'\
+ %(platform.release(), platform.version(), platform.architecture()[0],
+ os_lang, platform.python_version(),release.version,rev_id)
+ return environment
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added directory 'bin/docs'
=== added file 'bin/docs/README'
--- bin/docs/README 1970-01-01 00:00:00 +0000
+++ bin/docs/README 2011-04-25 08:38:27 +0000
@@ -0,0 +1,109 @@
+Screen
+------
+
+1 model
+Liste de vues - vue active
+
+ def sélection de la vue
+
+ screen.model
+ screel.view
+
+Parser_form
+ pour chaque form
+ cherche la vue
+
+
+Pouvoir faire:
+ <field name="order_lines">
+ <form>
+
+
+ </form>
+ </field>
+
+
+View (observater)
+-----------------
+
+view_widget
+ valeur par défaut
+ self.model_widget
+ self.view_form
+
+view_form
+ self.model_form
+
+
+Model (observateur)
+-------------------
+
+model_list
+ save
+ load
+ self.valid:
+ ok
+
+model_form
+ validate
+ save
+ reload
+
+model_field
+ set
+ set_client
+ get
+ get_client
+
+
+Observater
+----------
+
+ connect(nom signal, objet, **kwargs, *args2)
+ signal
+
+
+============================================================================
+Parent ou fils ?
+Bon voila, j'ai dessiné une structure de classe ultra-basique de comment les
+trucs devraient s'agencer dans le client (Ã mon avis).
+
+Les specs:
+
+ - Un changement dans un modèle doit se voir dans toutes les vues qui lui
+ sont associées
+
+ - On doit pouvoir composer les vues facilement, autrement dit on doit
+ pouvoir mixer vue en arbre et vue en forme.
+
+
+Comment je propose de le faire:
+
+ - MVC
+
+ - Un modèle quand il est mis à jour averti l'observateur qui à son tour
+ averti les vues qui surveille cet observateur.
+
+ - Une vue ou une mégavue, c'est la même interface : elles doivent donc être
+ interchangeable
+
+ - Une vue comprend deux objets: self.form et self.tree qui sont les deux
+ façons de la représenter dans le contexte définit par la mégavue.
+
+
+J'hésite sur les objets observés: modèle ou mégamodèle ? Sur les modèles c'est
+ma première idée car ils sont à la base des informations. Le problème c'est que
+fait on quand il y a création/effacement ? Passer par un observateur sur le
+mégamodèle ? C'est une solution, à la création/l'effacement le mégamodèle
+change et passe alors un message, les vues qui le doivent l'interceptant.
+
+Quel genre de messages sont envoyés via l'observateur:
+
+ - id de la ressource
+ - ressource (res.partner)
+ - champ (name, address_id)
+
+Ainsi les vues savent si elles doivent agir ou pas, et poum un petit reload
+fait l'affaire.
+
+
=== added file 'bin/docs/tiny-client.fig'
--- bin/docs/tiny-client.fig 1970-01-01 00:00:00 +0000
+++ bin/docs/tiny-client.fig 2011-04-25 08:38:27 +0000
@@ -0,0 +1,237 @@
+#FIG 3.2 Produced by xfig version 3.2.5-alpha5
+Landscape
+Center
+Metric
+A4
+100.00
+Single
+-2
+1200 2
+0 32 #cccccc
+0 33 #8e8e8e
+0 34 #7c7c7c
+0 35 #c9aaa1
+0 36 #b6b6b6
+0 37 #ac9478
+0 38 #808080
+0 39 #c6b797
+0 40 #eff8ff
+0 41 #dccba6
+0 42 #404040
+0 43 #c0c0c0
+0 44 #e0e0e0
+0 45 #8e8f8e
+0 46 #868286
+0 47 #c7c3c7
+0 48 #e7e3e7
+0 49 #444444
+0 50 #868686
+0 51 #c7c7c7
+0 52 #e7e7e7
+0 53 #f7f7f7
+0 54 #9e9e9e
+0 55 #dbdbdb
+0 56 #a1a1b7
+0 57 #9c0000
+0 58 #ededed
+0 59 #86acff
+0 60 #7070ff
+0 61 #bebebe
+0 62 #515151
+0 63 #000049
+0 64 #797979
+0 65 #303430
+0 66 #c7b696
+0 67 #d7d7d7
+0 68 #aeaeae
+0 69 #aaaaaa
+0 70 #555555
+0 71 #85807d
+0 72 #d2d2d2
+0 73 #3a3a3a
+0 74 #4573aa
+0 75 #000000
+0 76 #94949a
+0 77 #d6d7d6
+0 78 #7b79a5
+0 79 #666666
+0 80 #e2e2ee
+0 81 #565151
+0 82 #effbff
+0 83 #414141
+0 84 #414541
+0 85 #717571
+0 86 #73758c
+0 87 #635dce
+0 88 #8c8c8c
+0 89 #424242
+0 90 #8c8c8c
+0 91 #424242
+0 92 #8c8c8c
+0 93 #424242
+0 94 #8c8c8c
+0 95 #424242
+0 96 #8c8c8c
+0 97 #424242
+0 98 #8c8c8c
+0 99 #424242
+6 450 -2700 8415 -1845
+6 585 -2520 2880 -2070
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 585 -2225 2871 -2225
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 585 -2153 2871 -2153
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 585 -2510 2871 -2510 2871 -2082 585 -2082 585 -2510
+4 0 0 100 0 16 12 0.0000 4 150 525 585 -2296 View1\001
+-6
+6 3285 -2520 5625 -2070
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 3319 -2225 5605 -2225
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 3319 -2153 5605 -2153
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 3319 -2510 5605 -2510 5605 -2082 3319 -2082 3319 -2510
+4 0 0 100 0 16 12 0.0000 4 150 525 3390 -2296 View2\001
+-6
+6 5985 -2520 8325 -2070
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 6019 -2225 8305 -2225
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 6019 -2153 8305 -2153
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 6019 -2510 8305 -2510 8305 -2082 6019 -2082 6019 -2510
+4 0 0 100 0 16 12 0.0000 4 150 525 6090 -2296 View3\001
+-6
+2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5
+ 450 -2700 8415 -2700 8415 -1845 450 -1845 450 -2700
+-6
+6 5850 -450 8190 1800
+6 5850 -450 8190 0
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 5884 -155 8170 -155
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 5884 -83 8170 -83
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 5884 -440 8170 -440 8170 -12 5884 -12 5884 -440
+4 0 0 100 0 16 12 0.0000 4 150 630 5955 -226 Model1\001
+-6
+6 5850 450 8190 900
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 5884 745 8170 745
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 5884 817 8170 817
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 5884 460 8170 460 8170 888 5884 888 5884 460
+4 0 0 100 0 16 12 0.0000 4 150 630 5955 674 Model2\001
+-6
+6 5850 1350 8190 1800
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 5884 1645 8170 1645
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 5884 1717 8170 1717
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 5884 1360 8170 1360 8170 1788 5884 1788 5884 1360
+4 0 0 100 0 16 12 0.0000 4 150 630 5955 1574 Model3\001
+-6
+-6
+6 585 -1710 2880 -360
+6 585 -1710 2880 -1260
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 585 -1415 2871 -1415
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 585 -1343 2871 -1343
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 585 -1700 2871 -1700 2871 -1272 585 -1272 585 -1700
+4 0 0 100 0 16 12 0.0000 4 150 525 585 -1486 View1\001
+-6
+6 585 -810 2880 -360
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 585 -515 2871 -515
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 585 -443 2871 -443
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 585 -800 2871 -800 2871 -372 585 -372 585 -800
+4 0 0 100 0 16 12 0.0000 4 150 525 585 -586 View1\001
+-6
+-6
+6 5850 -1350 8190 -900
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 5884 -1055 8170 -1055
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 5884 -983 8170 -983
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 5884 -1340 8170 -1340 8170 -912 5884 -912 5884 -1340
+4 0 0 100 0 16 12 0.0000 4 150 960 5955 -1126 Observator\001
+-6
+6 9450 -1350 11790 -900
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 9484 -1055 11770 -1055
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 9484 -983 11770 -983
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 9484 -1340 11770 -1340 11770 -912 9484 -912 9484 -1340
+4 0 0 100 0 16 12 0.0000 4 150 825 9555 -1126 Controller\001
+-6
+6 9450 0 11790 450
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 9484 295 11770 295
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 9484 367 11770 367
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 9484 10 11770 10 11770 438 9484 438 9484 10
+4 0 0 100 0 16 12 0.0000 4 195 1110 9555 224 MegaModel1\001
+-6
+6 9450 -2700 11790 -2250
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 9484 -2405 11770 -2405
+2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2
+ 9484 -2333 11770 -2333
+2 2 0 1 0 7 101 0 20 4.000 0 0 7 0 0 5
+ 9484 -2690 11770 -2690 11770 -2262 9484 -2262 9484 -2690
+4 0 0 100 0 16 12 0.0000 4 195 1005 9555 -2476 MegaView1\001
+-6
+3 2 0 1 0 7 50 -1 -1 0.000 0 0 1 3
+ 1 1 3.00 30.00 60.00
+ 2880 -2340 4230 -1350 5850 -1170
+ 0.000 -1.000 0.000
+3 2 0 1 0 7 50 -1 -1 0.000 0 0 1 3
+ 1 1 3.00 30.00 60.00
+ 2880 -1575 4860 -945 5850 -1170
+ 0.000 -1.000 0.000
+3 2 0 1 0 7 50 -1 -1 0.000 0 0 1 3
+ 1 1 3.00 30.00 60.00
+ 2880 -630 4950 -585 5850 -1170
+ 0.000 -1.000 0.000
+3 2 0 1 0 7 50 -1 -1 0.000 0 1 0 3
+ 1 1 3.00 30.00 60.00
+ 8415 -2385 8865 -2655 9495 -2475
+ 0.000 -1.000 0.000
+3 2 0 1 0 7 50 -1 -1 4.000 0 1 0 2
+ 1 1 1.00 60.00 120.00
+ 10620 -2250 10620 -1350
+ 0.000 0.000
+3 2 0 1 0 7 50 -1 -1 4.000 0 1 0 2
+ 1 1 1.00 60.00 120.00
+ 10665 -900 10665 0
+ 0.000 0.000
+3 2 0 1 0 7 50 -1 -1 4.000 0 1 0 4
+ 1 1 1.00 60.00 120.00
+ 8190 -1170 9045 -900 9810 -450 10215 0
+ 0.000 -1.000 -1.000 0.000
+3 2 0 1 0 7 50 -1 -1 4.000 0 1 0 3
+ 1 1 1.00 60.00 120.00
+ 9450 180 8685 -270 8145 -270
+ 0.000 -1.000 0.000
+3 2 0 1 0 7 50 -1 -1 4.000 0 1 0 3
+ 1 1 1.00 60.00 120.00
+ 9450 225 8685 630 8145 675
+ 0.000 -1.000 0.000
+3 2 0 1 0 7 50 -1 -1 4.000 0 1 0 3
+ 1 1 1.00 60.00 120.00
+ 9450 225 9135 945 8190 1530
+ 0.000 -1.000 0.000
+3 2 0 1 0 7 50 -1 -1 4.000 0 1 0 2
+ 1 1 1.00 60.00 120.00
+ 6930 -450 6930 -900
+ 0.000 0.000
=== added file 'bin/environment_info.py'
--- bin/environment_info.py 1970-01-01 00:00:00 +0000
+++ bin/environment_info.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import os
+import sys
+import platform
+import locale
+import optparse
+import xmlrpclib
+import release
+import tools
+import logging
+
+class environment(object):
+ def __init__(self, login, password, dbname, host='localhost', port=8069):
+ self.login = login
+ self.passwd = password
+ self.db = dbname
+ self.host = host
+ self.port = port
+ self.log = logging.getLogger('environment')
+
+ def get_with_server_info(self):
+ try:
+ login_socket = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/common' % (self.host, self.port))
+ self.uid = login_socket.login(self.db, self.login, self.passwd)
+ if self.uid:
+ self.log.info(login_socket.get_server_environment() + self.get_client_info())
+ login_socket.logout(self.db, self.login, self.passwd)
+ else:
+ self.log.info("bad login or password from "+self.login+" using database "+self.db)
+ except Exception, e:
+ self.log.exception(e)
+ return True
+
+ def get_client_info(self):
+ try:
+ rev_id = os.popen('bzr revision-info').read()
+ if not rev_id:
+ rev_id = 'Bazaar Package not Found !'
+ except Exception,e:
+ rev_id = 'Exception: %s\n' % (tools.ustr(e))
+ environment = 'OpenERP-Client Version : %s\n'\
+ 'Last revision No. & ID :%s'\
+ %(release.version,rev_id)
+ return environment
+
+if __name__=="__main__":
+ uses ="""%prog [options]
+
+Note:
+ This script will provide you the full environment information of OpenERP-Client
+ If login,password and database are given then it will also give OpenERP-Server Information
+
+Examples:
+[1] python environment_info.py
+[2] python environment_info.py -l admin -p admin -d test
+"""
+
+ parser = optparse.OptionParser(uses)
+ parser.add_option("-l", "--login", dest="login", help="Login of the user in OpenERP")
+ parser.add_option("-p", "--password", dest="password", help="Password of the user in OpenERP")
+ parser.add_option("-d", "--database", dest="dbname", help="Database name")
+ parser.add_option("-P", "--port", dest="port", help="Port",default=8069)
+ parser.add_option("-H", "--host", dest="host", help="Host",default='localhost')
+
+ (options, args) = parser.parse_args()
+ parser = environment(options.login, options.password, dbname = options.dbname, host = options.host, port = options.port)
+ if not(options.login and options.password and options.dbname):
+ client_info = parser.get_client_info()
+
+ os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] )
+ if not os_lang:
+ os_lang = 'NOT SET'
+
+ environment = '\nEnvironment Information : \n' \
+ 'System : %s\n' \
+ 'OS Name : %s\n' \
+ %(platform.platform(), platform.os.name)
+ if os.name == 'posix':
+ if platform.system() == 'Linux':
+ lsbinfo = os.popen('lsb_release -a').read()
+ environment += '%s'%(lsbinfo)
+ else:
+ environment += 'Your System is not lsb compliant\n'
+ environment += 'Operating System Release : %s\n' \
+ 'Operating System Version : %s\n' \
+ 'Operating System Architecture : %s\n' \
+ 'Operating System Locale : %s\n'\
+ 'Python Version : %s\n'\
+ %(platform.release(), platform.version(), platform.architecture()[0],
+ os_lang, platform.python_version())
+
+ parser.log.info(environment + client_info)
+ parser.log.info('\nFor server Information you need to pass database(-d), login(-l),password(-p)')
+ sys.exit(1)
+ else:
+ parser.get_with_server_info()
=== added directory 'bin/icons'
=== added file 'bin/icons/accessories-archiver+.png'
Binary files bin/icons/accessories-archiver+.png 1970-01-01 00:00:00 +0000 and bin/icons/accessories-archiver+.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/accessories-archiver-minus.png'
Binary files bin/icons/accessories-archiver-minus.png 1970-01-01 00:00:00 +0000 and bin/icons/accessories-archiver-minus.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/accessories-archiver.png'
Binary files bin/icons/accessories-archiver.png 1970-01-01 00:00:00 +0000 and bin/icons/accessories-archiver.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/account.png'
Binary files bin/icons/account.png 1970-01-01 00:00:00 +0000 and bin/icons/account.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/administration.png'
Binary files bin/icons/administration.png 1970-01-01 00:00:00 +0000 and bin/icons/administration.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/calendar.png'
Binary files bin/icons/calendar.png 1970-01-01 00:00:00 +0000 and bin/icons/calendar.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/call-start.png'
Binary files bin/icons/call-start.png 1970-01-01 00:00:00 +0000 and bin/icons/call-start.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/camera_test.png'
Binary files bin/icons/camera_test.png 1970-01-01 00:00:00 +0000 and bin/icons/camera_test.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/check.png'
Binary files bin/icons/check.png 1970-01-01 00:00:00 +0000 and bin/icons/check.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/crm.png'
Binary files bin/icons/crm.png 1970-01-01 00:00:00 +0000 and bin/icons/crm.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/dialog-close.png'
Binary files bin/icons/dialog-close.png 1970-01-01 00:00:00 +0000 and bin/icons/dialog-close.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/document-new.png'
Binary files bin/icons/document-new.png 1970-01-01 00:00:00 +0000 and bin/icons/document-new.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/dolar.png'
Binary files bin/icons/dolar.png 1970-01-01 00:00:00 +0000 and bin/icons/dolar.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/dolar_ok!.png'
Binary files bin/icons/dolar_ok!.png 1970-01-01 00:00:00 +0000 and bin/icons/dolar_ok!.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/emblem-documents.png'
Binary files bin/icons/emblem-documents.png 1970-01-01 00:00:00 +0000 and bin/icons/emblem-documents.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/emblem-important.png'
Binary files bin/icons/emblem-important.png 1970-01-01 00:00:00 +0000 and bin/icons/emblem-important.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/face-plain.png'
Binary files bin/icons/face-plain.png 1970-01-01 00:00:00 +0000 and bin/icons/face-plain.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/folder-blue.png'
Binary files bin/icons/folder-blue.png 1970-01-01 00:00:00 +0000 and bin/icons/folder-blue.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/folder-green.png'
Binary files bin/icons/folder-green.png 1970-01-01 00:00:00 +0000 and bin/icons/folder-green.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/folder-orange.png'
Binary files bin/icons/folder-orange.png 1970-01-01 00:00:00 +0000 and bin/icons/folder-orange.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/folder-violet.png'
Binary files bin/icons/folder-violet.png 1970-01-01 00:00:00 +0000 and bin/icons/folder-violet.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/folder-yellow.png'
Binary files bin/icons/folder-yellow.png 1970-01-01 00:00:00 +0000 and bin/icons/folder-yellow.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/gdu-smart-failing.png'
Binary files bin/icons/gdu-smart-failing.png 1970-01-01 00:00:00 +0000 and bin/icons/gdu-smart-failing.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/gnome-cpu-frequency-applet+.png'
Binary files bin/icons/gnome-cpu-frequency-applet+.png 1970-01-01 00:00:00 +0000 and bin/icons/gnome-cpu-frequency-applet+.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/go-home.png'
Binary files bin/icons/go-home.png 1970-01-01 00:00:00 +0000 and bin/icons/go-home.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/go-month.png'
Binary files bin/icons/go-month.png 1970-01-01 00:00:00 +0000 and bin/icons/go-month.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/go-today.png'
Binary files bin/icons/go-today.png 1970-01-01 00:00:00 +0000 and bin/icons/go-today.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/go-week.png'
Binary files bin/icons/go-week.png 1970-01-01 00:00:00 +0000 and bin/icons/go-week.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/go-year.png'
Binary files bin/icons/go-year.png 1970-01-01 00:00:00 +0000 and bin/icons/go-year.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/graph.png'
Binary files bin/icons/graph.png 1970-01-01 00:00:00 +0000 and bin/icons/graph.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/gtk-go-back-ltr.png'
Binary files bin/icons/gtk-go-back-ltr.png 1970-01-01 00:00:00 +0000 and bin/icons/gtk-go-back-ltr.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/gtk-go-back-rtl.png'
Binary files bin/icons/gtk-go-back-rtl.png 1970-01-01 00:00:00 +0000 and bin/icons/gtk-go-back-rtl.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/gtk-jump-to-ltr.png'
Binary files bin/icons/gtk-jump-to-ltr.png 1970-01-01 00:00:00 +0000 and bin/icons/gtk-jump-to-ltr.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/gtk-jump-to-rtl.png'
Binary files bin/icons/gtk-jump-to-rtl.png 1970-01-01 00:00:00 +0000 and bin/icons/gtk-jump-to-rtl.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/gtk-media-pause.png'
Binary files bin/icons/gtk-media-pause.png 1970-01-01 00:00:00 +0000 and bin/icons/gtk-media-pause.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/gtk-select-all.png'
Binary files bin/icons/gtk-select-all.png 1970-01-01 00:00:00 +0000 and bin/icons/gtk-select-all.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/gtk-stop.png'
Binary files bin/icons/gtk-stop.png 1970-01-01 00:00:00 +0000 and bin/icons/gtk-stop.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/hr.png'
Binary files bin/icons/hr.png 1970-01-01 00:00:00 +0000 and bin/icons/hr.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/idea.png'
Binary files bin/icons/idea.png 1970-01-01 00:00:00 +0000 and bin/icons/idea.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/locked.png'
Binary files bin/icons/locked.png 1970-01-01 00:00:00 +0000 and bin/icons/locked.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/mail-.png'
Binary files bin/icons/mail-.png 1970-01-01 00:00:00 +0000 and bin/icons/mail-.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/mail-forward.png'
Binary files bin/icons/mail-forward.png 1970-01-01 00:00:00 +0000 and bin/icons/mail-forward.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/mail-message-new.png'
Binary files bin/icons/mail-message-new.png 1970-01-01 00:00:00 +0000 and bin/icons/mail-message-new.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/mail-replied.png'
Binary files bin/icons/mail-replied.png 1970-01-01 00:00:00 +0000 and bin/icons/mail-replied.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/mail_delete.png'
Binary files bin/icons/mail_delete.png 1970-01-01 00:00:00 +0000 and bin/icons/mail_delete.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/marketing.png'
Binary files bin/icons/marketing.png 1970-01-01 00:00:00 +0000 and bin/icons/marketing.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/mrp.png'
Binary files bin/icons/mrp.png 1970-01-01 00:00:00 +0000 and bin/icons/mrp.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/partner.png'
Binary files bin/icons/partner.png 1970-01-01 00:00:00 +0000 and bin/icons/partner.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/personal+.png'
Binary files bin/icons/personal+.png 1970-01-01 00:00:00 +0000 and bin/icons/personal+.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/personal-.png'
Binary files bin/icons/personal-.png 1970-01-01 00:00:00 +0000 and bin/icons/personal-.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/personal.png'
Binary files bin/icons/personal.png 1970-01-01 00:00:00 +0000 and bin/icons/personal.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/product.png'
Binary files bin/icons/product.png 1970-01-01 00:00:00 +0000 and bin/icons/product.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/project.png'
Binary files bin/icons/project.png 1970-01-01 00:00:00 +0000 and bin/icons/project.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/purchase.png'
Binary files bin/icons/purchase.png 1970-01-01 00:00:00 +0000 and bin/icons/purchase.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/rating-rated.png'
Binary files bin/icons/rating-rated.png 1970-01-01 00:00:00 +0000 and bin/icons/rating-rated.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/referer.png'
Binary files bin/icons/referer.png 1970-01-01 00:00:00 +0000 and bin/icons/referer.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/report.png'
Binary files bin/icons/report.png 1970-01-01 00:00:00 +0000 and bin/icons/report.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/sale.png'
Binary files bin/icons/sale.png 1970-01-01 00:00:00 +0000 and bin/icons/sale.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/stage.png'
Binary files bin/icons/stage.png 1970-01-01 00:00:00 +0000 and bin/icons/stage.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/stock.png'
Binary files bin/icons/stock.png 1970-01-01 00:00:00 +0000 and bin/icons/stock.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/stock_align_left_24.png'
Binary files bin/icons/stock_align_left_24.png 1970-01-01 00:00:00 +0000 and bin/icons/stock_align_left_24.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/stock_effects-object-colorize.png'
Binary files bin/icons/stock_effects-object-colorize.png 1970-01-01 00:00:00 +0000 and bin/icons/stock_effects-object-colorize.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/stock_format-default.png'
Binary files bin/icons/stock_format-default.png 1970-01-01 00:00:00 +0000 and bin/icons/stock_format-default.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/stock_format-scientific.png'
Binary files bin/icons/stock_format-scientific.png 1970-01-01 00:00:00 +0000 and bin/icons/stock_format-scientific.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/stock_symbol-selection.png'
Binary files bin/icons/stock_symbol-selection.png 1970-01-01 00:00:00 +0000 and bin/icons/stock_symbol-selection.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/stock_zoom.png'
Binary files bin/icons/stock_zoom.png 1970-01-01 00:00:00 +0000 and bin/icons/stock_zoom.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/tools.png'
Binary files bin/icons/tools.png 1970-01-01 00:00:00 +0000 and bin/icons/tools.png 2011-04-25 08:38:27 +0000 differ
=== added file 'bin/icons/translate.png'
Binary files bin/icons/translate.png 1970-01-01 00:00:00 +0000 and bin/icons/translate.png 2011-04-25 08:38:27 +0000 differ
=== added directory 'bin/modules'
=== added file 'bin/modules/__init__.py'
--- bin/modules/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/modules/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import spool
+import gui
+import action
+
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added directory 'bin/modules/action'
=== added file 'bin/modules/action/__init__.py'
--- bin/modules/action/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/modules/action/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import main
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/action/main.py'
--- bin/modules/action/main.py 1970-01-01 00:00:00 +0000
+++ bin/modules/action/main.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import os
+import copy
+import time
+import base64
+import datetime
+import service
+import rpc
+import wizard
+import printer
+import common
+import tools
+import options
+from widget.view.form_gtk.many2one import dialog
+from lxml import etree
+
+class main(service.Service):
+ def __init__(self, name='action.main'):
+ service.Service.__init__(self, name)
+
+ def exec_report(self, name, data, context={}):
+ datas = data.copy()
+ ids = datas['ids']
+ del datas['ids']
+ if not ids:
+ ids = rpc.session.rpc_exec_auth('/object', 'execute', datas['model'], 'search', datas.get('_domain',[]))
+ if ids == []:
+ common.message(_('Nothing to print!'))
+ return False
+ datas['id'] = ids[0]
+ ctx = rpc.session.context.copy()
+ ctx.update(context)
+ report_id = rpc.session.rpc_exec_auth('/report', 'report', name, ids, datas, ctx)
+ state = False
+ attempt = 0
+ max_attemps = int(options.options.get('client.timeout') or 0)
+ while not state:
+ val = rpc.session.rpc_exec_auth('/report', 'report_get', report_id)
+ if not val:
+ return False
+ state = val['state']
+ if not state:
+ time.sleep(1)
+ attempt += 1
+ if attempt>max_attemps:
+ common.message(_('Printing aborted, too long delay !'))
+ return False
+ printer.print_data(val)
+ return True
+
+ def execute(self, act_id, datas, type=None, context={}):
+ act_id = int(act_id)
+ ctx = rpc.session.context.copy()
+ ctx.update(context)
+ if type is None:
+ res = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.actions.actions', 'read', int(act_id), ['type'], ctx)
+ if not (res and len(res)):
+ raise Exception, 'ActionNotFound'
+ type=res['type']
+
+ res = rpc.session.rpc_exec_auth('/object', 'execute', type, 'read', act_id, False, ctx)
+ self._exec_action(res,datas,context)
+
+ def _exec_action(self, action, datas, context={}):
+ if isinstance(action, bool) or 'type' not in action:
+ return
+ # Updating the context : Adding the context of action in order to use it on Views called from buttons
+ if datas.get('id',False):
+ context.update( {'active_id': datas.get('id',False), 'active_ids': datas.get('ids',[]), 'active_model': datas.get('model',False)})
+ context.update(tools.expr_eval(action.get('context','{}'), context.copy()))
+ if action['type'] in ['ir.actions.act_window', 'ir.actions.submenu']:
+ for key in ('res_id', 'res_model', 'view_type', 'view_mode',
+ 'limit', 'auto_refresh', 'search_view', 'auto_search', 'search_view_id'):
+ datas[key] = action.get(key, datas.get(key, None))
+
+ datas['auto_search'] = action.get('auto_search', True)
+ if not datas['search_view'] and datas['search_view_id']:
+ datas['search_view'] = str(rpc.session.rpc_exec_auth('/object', 'execute', datas['res_model'], 'fields_view_get', isinstance(datas['search_view_id'], (tuple, list)) and datas['search_view_id'][0] or datas['search_view_id'], 'search', context))
+
+ if datas['limit'] is None or datas['limit'] == 0:
+ datas['limit'] = 100
+
+ view_ids=False
+ if action.get('views', []):
+ if isinstance(action['views'],list):
+ view_ids=[x[0] for x in action['views']]
+ datas['view_mode']=",".join([x[1] for x in action['views']])
+ else:
+ if action.get('view_id', False):
+ view_ids=[action['view_id'][0]]
+ elif action.get('view_id', False):
+ view_ids=[action['view_id'][0]]
+
+ if not action.get('domain', False):
+ action['domain']='[]'
+ domain_ctx = context.copy()
+ domain_ctx['time'] = time
+ domain_ctx['datetime'] = datetime
+ domain = tools.expr_eval(action['domain'], domain_ctx)
+ help = {}
+ if action.get('display_menu_tip', False):
+ msg = action.get('help', False)
+ title = action.get('name', False)
+ if msg and len(msg):
+ help['msg'] = msg
+ help['title'] = title or ''
+ if datas.get('domain', False):
+ domain.append(datas['domain'])
+ if action.get('target', False)=='new':
+ dia = dialog(datas['res_model'], id=datas.get('res_id',None),
+ window=datas.get('window',None), domain=domain,
+ context=context, view_ids=view_ids,target=True,
+ view_type=datas.get('view_mode', 'tree').split(','), help=help)
+ if dia.dia.get_has_separator():
+ dia.dia.set_has_separator(False)
+ dia.run()
+ dia.destroy()
+ else:
+ obj = service.LocalService('gui.window')
+ obj.create(view_ids, datas['res_model'], datas['res_id'], domain,
+ action['view_type'], datas.get('window',None), context,
+ datas['view_mode'], name=action.get('name', False), help=help,
+ limit=datas['limit'], auto_refresh=datas['auto_refresh'], auto_search = datas['auto_search'], search_view = datas['search_view'])
+
+ elif action['type']=='ir.actions.server':
+ res = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.actions.server', 'run', [action['id']], context)
+ if res:
+ if not isinstance(res, list):
+ res = [res]
+ for r in res:
+ self._exec_action(r, datas, context)
+
+ elif action['type']=='ir.actions.wizard':
+ win=None
+ if 'window' in datas:
+ win=datas['window']
+ del datas['window']
+ wizard.execute(action['wiz_name'], datas, parent=win, context=context)
+
+ elif action['type']=='ir.actions.report.custom':
+ if 'window' in datas:
+ win=datas['window']
+ del datas['window']
+ datas['report_id'] = action['report_id']
+ self.exec_report('custom', datas, context)
+
+ elif action['type']=='ir.actions.report.xml':
+ if 'window' in datas:
+ win=datas['window']
+ del datas['window']
+ if not datas:
+ datas = action.get('datas',[])
+ self.exec_report(action['report_name'], datas, context)
+
+ elif action['type']=='ir.actions.act_url':
+ tools.launch_browser(action.get('url',''))
+
+ def exec_keyword(self, keyword, data={}, adds={}, context={}, warning=True):
+ actions = None
+ if 'id' in data:
+ try:
+ id = data.get('id', False)
+ actions = rpc.session.rpc_exec_auth('/object', 'execute',
+ 'ir.values', 'get', 'action', keyword,
+ [(data['model'], id)], False, rpc.session.context)
+ actions = map(lambda x: x[2], actions)
+ except rpc.rpc_exception, e:
+# common.error(_('Error: ')+str(e.type), e.message, e.data)
+ return False
+ keyact = {}
+ for action in actions:
+ action_name = action.get('name') or ''
+ keyact[action_name.encode('utf8')] = action
+ keyact.update(adds)
+ res = common.selection(_('Select your action'), keyact)
+ if res:
+ (name,action) = res
+ context.update(rpc.session.context)
+ self._exec_action(action, data, context=context)
+ return (name, action)
+ return False
+
+main()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/action/wizard.py'
--- bin/modules/action/wizard.py 1970-01-01 00:00:00 +0000
+++ bin/modules/action/wizard.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,214 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+from gtk import glade
+import gettext
+import copy
+
+import service
+import rpc
+import common
+import thread
+import time
+
+from widget.screen import Screen
+
+
+class dialog(object):
+ def __init__(self, arch, fields, state, name, parent=None):
+ buttons = []
+ self.states=[]
+ default=-1
+ if not parent:
+ parent = service.LocalService('gui.main').window
+ self.dia = gtk.Dialog('OpenERP', parent,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
+ for x in state:
+ but = gtk.Button(x[1])
+ but.show()
+ if len(x) >= 3:
+ icon = gtk.Image()
+ icon.set_from_stock(x[2], gtk.ICON_SIZE_BUTTON)
+ but.set_image(icon)
+ self.dia.add_action_widget(but, len(self.states))
+ if len(x) >= 4 and x[3]:
+ but.set_flags(gtk.CAN_DEFAULT)
+ default = len(self.states)
+ self.states.append(x[0])
+ if default >= 0:
+ self.dia.set_default_response(default)
+
+ val = {}
+ for f in fields:
+ if 'value' in fields[f]:
+ val[f] = fields[f]['value']
+
+ self.screen = Screen('wizard.'+name, view_type=[], window=self.dia, is_wizard=True)
+ self.screen.new(default=False)
+ self.screen.add_view_custom(arch, fields, display=True)
+ self.screen.current_model.set(val)
+
+ x,y = self.screen.screen_container.size_get()
+ width, height = parent.get_size()
+ self.screen.widget.set_size_request(min(width - 20, x + 20),
+ min(height - 60, y + 25))
+ self.screen.widget.show()
+
+ self.dia.vbox.pack_start(self.screen.widget)
+ self.dia.set_title(self.screen.current_view.title)
+ self.dia.show()
+
+ def run(self, datas={}):
+ while True:
+ res = self.dia.run()
+ self.screen.current_view.set_value()
+ if self.screen.current_model.validate() or (res<0) or (self.states[res]=='end'):
+ break
+ self.screen.display()
+ if res<len(self.states) and res>=0:
+ datas.update(self.screen.get())
+ self.dia.destroy()
+ return (self.states[res], datas)
+ else:
+ self.dia.destroy()
+ self.screen.destroy()
+ return False
+
+def execute(action, datas, state='init', parent=None, context=None):
+ if context is None:
+ context = {}
+ if not 'form' in datas:
+ datas['form'] = {}
+ wiz_id = rpc.session.rpc_exec_auth('/wizard', 'create', action)
+
+ while state!='end':
+ class wizard_progress(object):
+ def __init__(self, parent=None):
+ self.res = None
+ self.error = False
+ self.parent = parent
+ self.exception = None
+
+ def run(self):
+ def go(wiz_id, datas, state):
+ ctx = context.copy()
+ ctx.update(rpc.session.context)
+ try:
+ self.res = rpc.session.rpc_exec_auth_try('/wizard', 'execute', wiz_id, datas, state, ctx)
+ except Exception, e:
+ self.error = True
+ self.res = False
+ self.exception = e
+ return True
+ if not self.res:
+ self.error = True
+ return True
+
+ thread.start_new_thread(go, (wiz_id, datas, state))
+
+ i = 0
+ win = None
+ pb = None
+ while (not self.res) and (not self.error):
+ time.sleep(0.1)
+ i += 1
+ if i > 10:
+ if not win or not pb:
+ win, pb = common.OpenERP_Progressbar(self.parent)
+ pb.pulse()
+ gtk.main_iteration()
+ if win:
+ win.destroy()
+ gtk.main_iteration()
+ if self.exception:
+ import xmlrpclib
+ import socket
+ from rpc import rpc_exception, CONCURRENCY_CHECK_FIELD
+ import tiny_socket
+ try:
+ raise self.exception
+ except socket.error, e:
+ common.message(str(e), title=_('Connection refused !'), type=gtk.MESSAGE_ERROR, parent=self.parent)
+ except xmlrpclib.Fault, err:
+ a = rpc_exception(err.faultCode, err.faultString)
+ if a.type in ('warning', 'UserError'):
+ if a.message in ('ConcurrencyException') and len(args) > 4:
+ if common.concurrency(args[0], args[2][0], args[4]):
+ if CONCURRENCY_CHECK_FIELD in args[4]:
+ del args[4][CONCURRENCY_CHECK_FIELD]
+ return self.rpc_exec_auth(obj, method, *args)
+ else:
+ common.warning(a.data, a.message, parent=self.parent)
+ else:
+ common.error(_('Application Error'), err.faultCode, err.faultString)
+ except tiny_socket.Myexception, err:
+ a = rpc_exception(err.faultCode, err.faultString)
+ if a.type in ('warning', 'UserError'):
+ common.warning(a.data, a.message, parent=self.parent)
+ else:
+ common.error(_('Application Error'), err.faultCode, err.faultString)
+ except Exception, e:
+ common.error(_('Application Error'), _('View details'), str(e))
+ return self.res
+
+ wp = wizard_progress(parent)
+ res = wp.run()
+ if not res:
+ return False
+
+ if 'datas' in res:
+ datas['form'].update( res['datas'] )
+ if res['type']=='form':
+ dia = dialog(res['arch'], res['fields'], res['state'], action, parent)
+ dia.screen.current_model.set( datas['form'] )
+ res = dia.run(datas['form'])
+ if not res:
+ break
+ (state, new_data) = res
+ for d in new_data:
+ if new_data[d]==None:
+ del new_data[d]
+ datas['form'].update(new_data)
+ del new_data
+ elif res['type']=='action':
+ obj = service.LocalService('action.main')
+ obj._exec_action(res['action'],datas)
+ state = res['state']
+ elif res['type']=='print':
+ obj = service.LocalService('action.main')
+ datas['report_id'] = res.get('report_id', False)
+ if res.get('get_id_from_action', False):
+ backup_ids = datas['ids']
+ datas['ids'] = datas['form']['ids']
+ win = obj.exec_report(res['report'], datas)
+ datas['ids'] = backup_ids
+ else:
+ win = obj.exec_report(res['report'], datas)
+ state = res['state']
+ elif res['type']=='state':
+ state = res['state']
+ #common.error('Wizard Error:'+ str(e.type), e.message, e.data)
+ #state = 'end'
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added directory 'bin/modules/gui'
=== added file 'bin/modules/gui/__init__.py'
--- bin/modules/gui/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import main
+import window
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/main.py'
--- bin/modules/gui/main.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/main.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,1607 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import time
+import os
+import gettext
+import urlparse
+
+import gobject
+import gtk
+from gtk import glade
+from pango import parse_markup
+import translate
+
+import rpc
+
+import service
+import options
+import common
+
+from window import win_preference, win_extension
+import tools
+import re
+import xmlrpclib
+import base64
+
+import thread
+import gc
+
+RESERVED_KEYWORDS=['absolute', 'action', 'all', 'alter', 'analyse', 'analyze', 'and', 'any', 'as', 'asc', 'authorization', 'between', 'binary', 'both',
+ 'case', 'cast', 'check', 'collate', 'column','constraint', 'create', 'cross', 'current_date', 'current_time', 'current_timestamp',
+ 'current_user','default', 'deferrable', 'desc', 'distinct', 'do', 'else', 'end', 'except', 'false', 'for', 'foreign', 'freeze',
+ 'from', 'full', 'grant', 'group', 'having', 'ilike', 'in', 'initially','inner', 'intersect', 'into', 'is', 'isnull', 'join', 'leading',
+ 'left', 'like', 'limit', 'localtime', 'localtimestamp', 'natural', 'new', 'not', 'notnull', 'null', 'off', 'offset', 'old',
+ 'on', 'only', 'or', 'order', 'outer', 'overlaps', 'placing', 'primary', 'references', 'right','select', 'session_user', 'similar',
+ 'some', 'sysid', 'table', 'then', 'to', 'trailing', 'true', 'union', 'unique', 'user', 'using', 'verbose', 'when', 'where']
+
+def check_ssl():
+ try:
+ from OpenSSL import SSL
+ import socket
+
+ return hasattr(socket, 'ssl')
+ except:
+ return False
+
+class StockButton(gtk.Button):
+ def __init__(self, label, stock):
+ gtk.Button.__init__(self, label)
+ self.icon = gtk.Image()
+ self.icon.set_from_stock(stock, gtk.ICON_SIZE_MENU)
+ self.set_image(self.icon)
+
+class DatabaseDialog(gtk.Dialog):
+ def __init__(self, label, parent):
+ gtk.Dialog.__init__(
+ self, label, parent,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
+ gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
+ )
+
+ self.set_icon(common.OPENERP_ICON)
+ self.set_default_response(gtk.RESPONSE_ACCEPT)
+ self.set_response_sensitive(gtk.RESPONSE_ACCEPT, False)
+
+ self.table = gtk.Table(3, 2, False)
+ self.table.set_row_spacings(5)
+ self.table.set_col_spacings(5)
+
+ self.messageLabel = gtk.Label('<b>'+_('Could not connect to server !')+'</b>')
+ self.messageLabel.set_use_markup(True)
+ self.messageLabel.hide()
+
+ lbl = gtk.Label(_("Server:"))
+ lbl.set_alignment(1.0, 0.5)
+ self.table.attach(lbl, 0, 1, 0, 1)
+ hbox = gtk.HBox(spacing=5)
+ self.serverEntry = gtk.Entry()
+ self.serverEntry.connect('changed', self.on_server_entry_changed, self.messageLabel)
+ self.serverEntry.set_text(self.default_server_url())
+ self.serverEntry.set_sensitive(False)
+
+ hbox.pack_start(self.serverEntry, False, False)
+
+ but_server = StockButton(_("Change"), gtk.STOCK_NETWORK)
+ but_server.connect_after('clicked', lambda *a: _server_ask(self.serverEntry, parent))
+ hbox.pack_start(but_server, False, False)
+ self.table.attach(hbox, 1, 2, 0, 1)
+
+ self.table.attach(self.messageLabel, 0, 2, 1, 2)
+
+ lbl = gtk.Label(_("Super Administrator Password:"))
+ lbl.set_alignment(1.0, 0.5)
+ self.table.attach(lbl, 0, 1, 2, 3)
+ self.adminPwdEntry = gtk.Entry()
+ self.adminPwdEntry.set_visibility(False)
+ self.adminPwdEntry.set_activates_default(True)
+ self.table.attach(self.adminPwdEntry, 1, 2, 2, 3)
+
+ self.vbox.add(self.table)
+
+ def run(self):
+ self.show_all()
+ self.messageLabel.hide()
+ res = super(DatabaseDialog, self).run()
+ if res == gtk.RESPONSE_ACCEPT:
+ self.run_thread()
+
+ self.destroy()
+
+ def on_server_entry_changed(self, entry, message):
+ try:
+ rpc.session.about(entry.get_text())
+ self.clear_screen()
+ self.on_server_entry_changed_after(entry)
+ self.set_response_sensitive(gtk.RESPONSE_ACCEPT, True)
+ except Exception, ex:
+ self.clear_screen()
+ message.show()
+
+ def default_server_url(self):
+ return "%(protocol)s%(host)s:%(port)d" % {
+ 'protocol' : options.options['login.protocol'],
+ 'host' : options.options['login.server'],
+ 'port' : int(options.options['login.port']),
+ }
+
+ def run_thread(self):
+ import thread
+ self.result = None
+ self.error = False
+ self.exception = None
+ def go():
+ try:
+ self.on_response_accept()
+ self.result = True
+ except Exception, e:
+ self.result = True
+ self.exception = e
+ return True
+
+ thread.start_new_thread(go, ())
+
+ i = 0
+ win = None
+ pb = None
+ while not self.result:
+ time.sleep(0.1)
+ i += 1
+
+ if i > 10:
+ if not win or not pb:
+ win, pb = common.OpenERP_Progressbar(self)
+ pb.pulse()
+ gtk.main_iteration()
+ if win:
+ win.destroy()
+ gtk.main_iteration()
+
+ if self.exception:
+ import xmlrpclib
+ import socket
+ import tiny_socket
+ from rpc import rpc_exception
+ try:
+ raise self.exception
+ except socket.error, e:
+ common.message(str(e), title=_('Connection refused !'), type=gtk.MESSAGE_ERROR)
+ except (tiny_socket.Myexception, xmlrpclib.Fault), err:
+ a = rpc_exception(err.faultCode, err.faultString)
+ if a.type in ('warning', 'UserError'):
+ common.warning(a.data, a.message, parent=self)
+ elif a.type == 'AccessDenied':
+ common.warning(_('Bad Super Administrator Password'), self.get_title(), parent=self)
+ else:
+ common.error(_('Application Error'), err.faultCode, err.faultString, disconnected_mode=True)
+ except Exception, e:
+ import sys
+ import traceback
+ tb = sys.exc_info()
+ tb_s = "".join(traceback.format_exception(*tb))
+ common.error(_('Application Error'), str(e), tb_s, disconnected_mode=True)
+ else:
+ if hasattr(self, 'message') and self.message:
+ common.message(self.message, self.get_title())
+
+ def on_response_accept(self):
+ pass
+
+ def on_server_entry_changed_after(self, entry):
+ pass
+
+ def clear_screen(self):
+ self.messageLabel.hide()
+
+class RetrieveMigrationScriptDialog(DatabaseDialog):
+ def __init__(self, parent):
+ DatabaseDialog.__init__(self, _("Migration Scripts"), parent)
+ self.table.resize(5, 2)
+
+ lbl = gtk.Label(_("Contract ID:"))
+ lbl.set_alignment(1.0, 0.5)
+ self.table.attach(lbl, 0, 1, 3, 4)
+ self.contractIdEntry = gtk.Entry()
+ self.table.attach(self.contractIdEntry, 1, 2, 3, 4)
+
+ lbl = gtk.Label(_("Contract Password:"))
+ lbl.set_alignment(1.0, 0.5)
+ self.table.attach(lbl, 0, 1, 4, 5)
+ self.contractPwdEntry = gtk.Entry()
+ self.contractPwdEntry.set_visibility(False)
+ self.table.attach(self.contractPwdEntry, 1, 2, 4, 5)
+
+ def on_response_accept(self):
+ au = rpc.session.get_available_updates(
+ self.serverEntry.get_text(),
+ self.adminPwdEntry.get_text(),
+ self.contractIdEntry.get_text(),
+ self.contractPwdEntry.get_text(),
+ )
+ if not au:
+ self.message = _("You already have the latest version")
+ return
+
+ au = ["%s: %s" % (k, v) for k, v in au.items()]
+ au.sort()
+ au.insert(0, _("The following updates are available:"))
+ msg = "\n * ".join(au)
+ if not common.sur(msg):
+ return
+
+ # The OpenERP server fetchs the migration scripts
+ rpc.session.get_migration_scripts(
+ self.serverEntry.get_text(),
+ self.adminPwdEntry.get_text(),
+ self.contractIdEntry.get_text(),
+ self.contractPwdEntry.get_text(),
+ )
+ self.message = _("You can now migrate your databases.")
+
+class MigrationDatabaseDialog(DatabaseDialog):
+ def __init__(self, parent):
+ self.model = gtk.ListStore(bool, str)
+ DatabaseDialog.__init__(self, _("Migrate Database"), parent)
+
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ treeview = gtk.TreeView(self.model)
+ treeview.set_rules_hint(True)
+ treeview.set_size_request(300, 380)
+
+ # Add the boolean column (apply)
+ renderer = gtk.CellRendererToggle()
+ renderer.set_property('activatable', True)
+ renderer.connect('toggled', self._on_toggle_renderer__toggled, 0)
+ col = gtk.TreeViewColumn("Apply", renderer, active=0)
+ treeview.append_column(col)
+
+ # Add the text column (database name)
+ renderer = gtk.CellRendererText()
+ col = gtk.TreeViewColumn(_("Database"), renderer, text=1)
+ treeview.append_column(col)
+ sw.add(treeview)
+ self.table.attach(sw, 0, 2, 3, 4)
+
+ def on_response_accept(self):
+ databases = [ item[1] for item in self.model if bool(item[0]) ]
+ if databases:
+ rpc.session.migrate_databases(self.serverEntry.get_text(),
+ self.adminPwdEntry.get_text(),
+ databases)
+ if len(databases) == 1:
+ self.message = _("Your database has been upgraded.")
+ else:
+ self.message = _("Your databases have been upgraded.")
+ else:
+ self.message = _("You have not selected a database")
+
+ def _on_toggle_renderer__toggled(self, renderer, path, col_index):
+ row = self.model[path]
+ row[col_index] = not row[col_index]
+
+ def clear_screen(self):
+ super(MigrationDatabaseDialog, self).clear_screen()
+ self.model.clear()
+
+ def on_server_entry_changed_after(self, entry):
+ self.clear_screen()
+ try:
+ result = rpc.session.list_db(entry.get_text())
+ except:
+ return
+ if result:
+ for db_num, db_name in enumerate(result):
+ self.model.set( self.model.append(), 0, False, 1, db_name)
+
+def _get_db_name_from_url(url):
+ if not url:
+ return ''
+ url = url.split('://', 1)[1].rsplit(':', 1)[0]
+ if '.' in url:
+ import socket
+ try:
+ socket.inet_aton(url)
+ except socket.error:
+ return url.split('.', 1)[0]
+ return ''
+
+def _refresh_dblist(db_widget, entry_db, label, butconnect, url, dbtoload=None):
+ if not dbtoload:
+ dbtoload = options.options['login.db'] or ''
+ if not dbtoload:
+ dbtoload = _get_db_name_from_url(url)
+
+ label.hide()
+
+ liststore = db_widget.get_model()
+ liststore.clear()
+ try:
+ result = rpc.session.list_db(url)
+ except:
+ label.set_label('<b>'+_('Could not connect to server !')+'</b>')
+ db_widget.hide()
+ entry_db.hide()
+ label.show()
+ if butconnect:
+ butconnect.set_sensitive(False)
+ return False
+
+ if result is None:
+ entry_db.show()
+ entry_db.set_text(dbtoload)
+ entry_db.grab_focus()
+ db_widget.hide()
+ if butconnect:
+ butconnect.set_sensitive(True)
+ else:
+ entry_db.hide()
+
+ if not result:
+ label.set_label('<b>'+_('No database found, you must create one !')+'</b>')
+ label.show()
+ db_widget.hide()
+ if butconnect:
+ butconnect.set_sensitive(False)
+ else:
+ db_widget.show()
+ index = 0
+ for db_num, db_name in enumerate(result):
+ liststore.append([db_name])
+ if db_name == dbtoload:
+ index = db_num
+ db_widget.set_active(index)
+ if butconnect:
+ butconnect.set_sensitive(True)
+
+ lm = rpc.session.login_message(url)
+ if lm:
+ try:
+ parse_markup(lm)
+ except:
+ pass
+ else:
+ label.set_label(lm)
+ label.show()
+
+ return True
+
+def _refresh_langlist(lang_widget, url):
+ liststore = lang_widget.get_model()
+ liststore.clear()
+ lang_list = rpc.session.db_exec_no_except(url, 'list_lang')
+ lang = rpc.session.context.get('lang', options.options.get('client.lang', 'en_US'))
+ active_idx = -1
+ for index, (key,val) in enumerate(lang_list):
+ if key == lang:
+ active_idx = index
+ liststore.append((val,key))
+ if active_idx != -1:
+ lang_widget.set_active(active_idx)
+ return lang_list
+
+def _server_ask(server_widget, parent=None):
+ result = False
+ win_gl = glade.XML(common.terp_path("openerp.glade"),"win_server",gettext.textdomain())
+ win = win_gl.get_widget('win_server')
+ if not parent:
+ parent = service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(common.OPENERP_ICON)
+ win.show_all()
+ win.set_default_response(gtk.RESPONSE_OK)
+ host_widget = win_gl.get_widget('ent_host')
+ port_widget = win_gl.get_widget('ent_port')
+ protocol_widget = win_gl.get_widget('protocol')
+
+ protocol = {
+ 'XML-RPC (port : 8069)': 'http://',
+ 'NET-RPC (faster)(port : 8070)': 'socket://',
+ }
+
+ if check_ssl():
+ protocol['XML-RPC secure'] ='https://'
+
+ listprotocol = gtk.ListStore(str)
+ protocol_widget.set_model(listprotocol)
+
+ m = re.match('^(http[s]?://|socket://)([\w.-]+):(\d{1,5})$', server_widget.get_text())
+ if m:
+ host_widget.set_text(m.group(2))
+ port_widget.set_text(m.group(3))
+
+ index = 0
+ i = 0
+ for p in protocol:
+ listprotocol.append([p])
+ if m and protocol[p] == m.group(1):
+ index = i
+ i += 1
+ protocol_widget.set_active(index)
+
+ res = win.run()
+ if res == gtk.RESPONSE_OK:
+ protocol = protocol[protocol_widget.get_active_text()]
+ url = '%s%s:%s' % ((protocol).strip(), (host_widget.get_text()).strip(), (port_widget.get_text()).strip())
+ server_widget.set_text(url)
+ result = url
+ parent.present()
+ win.destroy()
+ return result
+
+
+class db_login(object):
+ def __init__(self):
+ self.win_gl = glade.XML(common.terp_path("openerp.glade"),"win_login",gettext.textdomain())
+ self.win = self.win_gl.get_widget('win_login')
+
+ def refreshlist(self, widget, db_widget, entry_db, label, url, butconnect=False):
+
+ def check_server_version(url):
+ try:
+ import release
+ full_server_version = rpc.session.db_exec_no_except(url, 'server_version')
+ server_version = full_server_version.split('.')
+ client_version = release.version.split('.')
+ return (server_version[:2] == client_version[:2], full_server_version, release.version)
+ except:
+ # the server doesn't understand the request. It's mean that it's an old version of the server
+ return (False, _('Unknown'), release.version)
+
+ if _refresh_dblist(db_widget, entry_db, label, butconnect, url):
+ is_same_version, server_version, client_version = check_server_version(url)
+ if not is_same_version:
+ common.warning(_('The versions of the server (%s) and the client (%s) missmatch. The client may not work properly. Use it at your own risks.') % (server_version, client_version,),parent=self.win)
+
+ def refreshlist_ask(self,widget, server_widget, db_widget, entry_db, label, butconnect = False, url=False, parent=None):
+ url = _server_ask(server_widget, parent) or url
+ return self.refreshlist(widget, db_widget, entry_db, label, url, butconnect)
+
+ def run(self, dbname=None, parent=None):
+ uid = 0
+ win = self.win_gl.get_widget('win_login')
+ if not parent:
+ parent = service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.set_icon(common.OPENERP_ICON)
+ win.set_resizable(False)
+ win.show_all()
+ img = self.win_gl.get_widget('image_tinyerp')
+ img.set_from_file(common.terp_path_pixmaps('openerp.png'))
+ login = self.win_gl.get_widget('ent_login')
+ passwd = self.win_gl.get_widget('ent_passwd')
+ server_widget = self.win_gl.get_widget('ent_server')
+ but_connect = self.win_gl.get_widget('button_connect')
+ combo_db = self.win_gl.get_widget('combo_db')
+ entry_db = self.win_gl.get_widget('ent_db')
+ change_button = self.win_gl.get_widget('but_server')
+ label = self.win_gl.get_widget('combo_label')
+# db_entry = self.win_gl.get_widget('ent_db')
+ label.hide()
+ entry_db.hide()
+
+ host = options.options['login.server']
+ port = options.options['login.port']
+ protocol = options.options['login.protocol']
+
+ url = '%s%s:%s' % (protocol, host, port)
+ server_widget.set_text(url)
+ login.set_text(options.options['login.login'])
+
+ # construct the list of available db and select the last one used
+ liststore = gtk.ListStore(str)
+ combo_db.set_model(liststore)
+ cell = gtk.CellRendererText()
+ combo_db.pack_start(cell, True)
+ combo_db.add_attribute(cell, 'text', 0)
+
+ res = self.refreshlist(None, combo_db, entry_db, label, url, but_connect)
+ change_button.connect_after('clicked', self.refreshlist_ask, server_widget, combo_db, entry_db, label, but_connect, url, win)
+
+ if dbname:
+ iter = liststore.get_iter_root()
+ while iter:
+ if liststore.get_value(iter, 0)==dbname:
+ combo_db.set_active_iter(iter)
+ break
+ iter = liststore.iter_next(iter)
+
+ res = win.run()
+ m = re.match('^(http[s]?://|socket://)([\w.\-]+):(\d{1,5})$', server_widget.get_text() or '')
+ if m:
+ if combo_db.flags() & gtk.VISIBLE:
+ dbname = combo_db.get_active_text()
+ else:
+ dbname = entry_db.get_text()
+
+ options.options['login.server'] = m.group(2)
+ options.options['login.login'] = login.get_text()
+ options.options['login.port'] = m.group(3)
+ options.options['login.protocol'] = m.group(1)
+ options.options['login.db'] = dbname
+ result = (login.get_text(), passwd.get_text(), m.group(2), m.group(3), m.group(1), dbname)
+ else:
+ parent.present()
+ win.destroy()
+ raise Exception('QueryCanceled')
+ if res <> gtk.RESPONSE_OK:
+ parent.present()
+ win.destroy()
+ raise Exception('QueryCanceled')
+ parent.present()
+ win.destroy()
+ return result
+
+class db_create(object):
+ def set_sensitive(self, sensitive):
+ self.dialog.get_widget('button_db_ok').set_sensitive(False)
+ return sensitive
+
+ def server_change(self, widget=None, parent=None):
+ url = _server_ask(self.server_widget)
+ if self.lang_widget and url:
+ _refresh_langlist(self.lang_widget, url)
+ return url
+
+ def __init__(self, sig_login, terp_main):
+ self.dialog = glade.XML(common.terp_path("openerp.glade"), "win_createdb", gettext.textdomain())
+ self.sig_login = sig_login
+ self.terp_main = terp_main
+
+ def entry_changed(self, *args):
+ up1 = self.dialog.get_widget('ent_user_pass1').get_text()
+ up2 = self.dialog.get_widget('ent_user_pass2').get_text()
+ self.dialog.get_widget('button_db_ok').set_sensitive(bool(up1 and (up1==up2)))
+
+ def run(self, parent=None):
+ win = self.dialog.get_widget('win_createdb')
+ self.dialog.signal_connect('on_ent_user_pass1_changed', self.entry_changed)
+ self.dialog.signal_connect('on_ent_user_pass2_changed', self.entry_changed)
+ win.set_default_response(gtk.RESPONSE_OK)
+ if not parent:
+ parent = service.LocalService('gui.main').window
+ win.set_transient_for(parent)
+ win.show_all()
+ lang_dict = {}
+ pass_widget = self.dialog.get_widget('ent_password_new')
+ self.server_widget = self.dialog.get_widget('ent_server_new')
+ change_button = self.dialog.get_widget('but_server_new')
+ self.lang_widget = self.dialog.get_widget('db_create_combo')
+ self.db_widget = self.dialog.get_widget('ent_db_new')
+ demo_widget = self.dialog.get_widget('check_demo')
+ demo_widget.set_active(True)
+
+ change_button.connect_after('clicked', self.server_change, win)
+ protocol = options.options['login.protocol']
+ url = '%s%s:%s' % (protocol, options.options['login.server'], options.options['login.port'])
+
+ self.server_widget.set_text(url)
+ liststore = gtk.ListStore(str, str)
+ self.lang_widget.set_model(liststore)
+ try:
+ _refresh_langlist(self.lang_widget, url)
+ except:
+ self.set_sensitive(False)
+
+ while True:
+ res = win.run()
+ db_name = self.db_widget.get_text().lower()
+ if (res==gtk.RESPONSE_OK) and (db_name in RESERVED_KEYWORDS):
+ common.warning(_("Sorry,'" +db_name + "' cannot be the name of the database,it's a Reserved Keyword."), _('Bad database name !'), parent=win)
+ continue
+ if (res==gtk.RESPONSE_OK) and ((not db_name) or (not re.match('^[a-zA-Z0-9][a-zA-Z0-9_]+$', db_name))):
+ common.warning(_('The database name must contain only normal characters or "_".\nYou must avoid all accents, space or special characters.'), _('Bad database name !'), parent=win)
+
+ else:
+ break
+ demo_data = demo_widget.get_active()
+
+ langidx = self.lang_widget.get_active_iter()
+ langreal = langidx and self.lang_widget.get_model().get_value(langidx,1)
+ passwd = pass_widget.get_text()
+ user_pass = self.dialog.get_widget('ent_user_pass1').get_text()
+ url = self.server_widget.get_text()
+ m = re.match('^(http[s]?://|socket://)([\w.\-]+):(\d{1,5})$', url or '')
+ if m:
+ options.options['login.server'] = m.group(2)
+ options.options['login.port'] = m.group(3)
+ options.options['login.protocol'] = m.group(1)
+ options.options['login.db'] = db_name
+ parent.present()
+ win.destroy()
+
+ if res == gtk.RESPONSE_OK:
+ try:
+ id = rpc.session.db_exec(url, 'list')
+ if db_name in id:
+ raise Exception('DbExist')
+ id = rpc.session.db_exec(url, 'create', passwd, db_name, demo_data, langreal, user_pass)
+ win, pb = common.OpenERP_Progressbar(parent, title='OpenERP Database Installation')
+ self.timer = gobject.timeout_add(1000, self.progress_timeout, pb, url, passwd, id, win, db_name, parent)
+ self.terp_main.glade.get_widget('but_menu').set_sensitive(True)
+ self.terp_main.glade.get_widget('user').set_sensitive(True)
+ self.terp_main.glade.get_widget('form').set_sensitive(True)
+ self.terp_main.glade.get_widget('plugins').set_sensitive(True)
+ except Exception, e:
+ if e.args == ('DbExist',):
+ common.warning(_("Could not create database."),_('Database already exists !'), parent=parent)
+ elif (getattr(e,'faultCode',False)=='AccessDenied') or str(e)=='AccessDenied':
+ common.warning(_('Bad database administrator password !'), _("Could not create database."), parent=parent)
+ else:
+ common.warning(_("Could not create database."),_('Error during database creation !'), parent=parent)
+
+ def progress_timeout(self, pbar, url, passwd, id, win, dbname, parent=None):
+ try:
+ progress,users = rpc.session.db_exec_no_except(url, 'get_progress', passwd, id)
+ except:
+ win.destroy()
+ common.warning(_("The server crashed during installation.\nWe suggest you to drop this database."),_("Error during database creation !"))
+ return False
+
+ pbar.pulse()
+ if progress == 1.0:
+ win.destroy()
+ m = re.match('^(http[s]?://|socket://)([\w.]+):(\d{1,5})$', url)
+ ok = False
+ for x in users:
+ if x['login']=='admin' and m:
+ res = [x['login'], x['password']]
+ res.append( m.group(2) )
+ res.append( m.group(3) )
+ res.append( m.group(1) )
+ res.append( dbname )
+ log_response = rpc.session.login(*res)
+ if log_response == 1:
+ options.options['login.login'] = x['login']
+ id = self.terp_main.sig_win_menu(quiet=False)
+ ok = True
+ break
+ if not ok:
+ self.sig_login(dbname=dbname)
+ return False
+ return True
+
+ def process(self):
+ return False
+
+
+class terp_main(service.Service):
+ def __init__(self, name='gui.main', audience='gui.*'):
+
+ service.Service.__init__(self, name, audience)
+ self.exportMethod(self.win_add)
+
+ self._handler_ok = True
+ self.glade = glade.XML(common.terp_path("openerp.glade"),"win_main",gettext.textdomain())
+ self.status_bar_main = self.glade.get_widget('hbox_status_main')
+ self.status_bar_main.show()
+ self.toolbar = self.glade.get_widget('main_toolbar')
+ self.sb_company = self.glade.get_widget('sb_company')
+ self.sb_requests = self.glade.get_widget('sb_requests')
+ self.sb_username = self.glade.get_widget('sb_user_name')
+ self.sb_servername = self.glade.get_widget('sb_user_server')
+ id = self.sb_servername.get_context_id('message')
+ self.sb_servername.push(id, _('Press Ctrl+O to login'))
+ self.secure_img = self.glade.get_widget('secure_img')
+ self.secure_img.hide()
+
+ window = self.glade.get_widget('win_main')
+ window.connect("destroy", self.sig_quit)
+ window.connect("delete_event", self.sig_delete)
+ self.window = window
+ self.window.set_icon(common.OPENERP_ICON)
+
+ self.notebook = gtk.Notebook()
+ self.notebook.set_scrollable(True)
+ self.sig_id = self.notebook.connect_after('switch-page', self._sig_page_changed)
+ if gtk.pygtk_version >= (2, 10, 0):
+ self.notebook.connect('page-reordered', self._sig_page_reordered)
+ vbox = self.glade.get_widget('vbox_main')
+ vbox.pack_start(self.notebook, expand=True, fill=True)
+
+ self.shortcut_menu = self.glade.get_widget('shortcut')
+
+ #
+ # Default Notebook
+ #
+
+ self.notebook.show()
+ self.pages = []
+ self.current_page = 0
+ self.last_page = 0
+
+ callbacks_dict = {
+ 'on_login_activate': self.sig_login,
+ 'on_logout_activate': self.sig_logout,
+ 'on_win_next_activate': self.sig_win_next,
+ 'on_win_prev_activate': self.sig_win_prev,
+ 'on_plugin_execute_activate': self.sig_plugin_execute,
+ 'on_quit_activate': self.sig_close,
+ 'on_but_menu_clicked': self.sig_win_menu,
+ 'on_win_new_activate': self.sig_win_menu,
+ 'on_win_home_activate': self.sig_home_new,
+ 'on_win_close_activate': self.sig_win_close,
+ 'on_support_activate': common.support,
+ 'on_preference_activate': self.sig_user_preferences,
+ 'on_change_passwd_activate':lambda x:self.sig_db_password('user'),
+ 'on_read_requests_activate': self.sig_request_open,
+ 'on_send_request_activate': self.sig_request_new,
+ 'on_request_wait_activate': self.sig_request_wait,
+ 'on_opt_save_activate': lambda x: options.options.save(),
+ 'on_menubar_icons_activate': lambda x: self.sig_menubar('icons'),
+ 'on_menubar_text_activate': lambda x: self.sig_menubar('text'),
+ 'on_menubar_both_activate': lambda x: self.sig_menubar('both'),
+ 'on_opt_form_tab_top_activate': lambda x: self.sig_form_tab('top'),
+ 'on_opt_form_tab_left_activate': lambda x: self.sig_form_tab('left'),
+ 'on_opt_form_tab_right_activate': lambda x: self.sig_form_tab('right'),
+ 'on_opt_form_tab_bottom_activate': lambda x: self.sig_form_tab('bottom'),
+ 'on_opt_form_tab_orientation_horizontal_activate': lambda x: self.sig_form_tab_orientation(0),
+ 'on_opt_form_tab_orientation_vertical_activate': lambda x: self.sig_form_tab_orientation(90),
+ 'on_opt_debug_mode_activate':self.sig_debug_mode_tooltip,
+ 'on_help_index_activate': self.sig_help_index,
+ 'on_help_contextual_activate': self.sig_help_context,
+ 'on_help_licence_activate': self.sig_licence,
+ 'on_about_activate': self.sig_about,
+ 'on_shortcuts_activate' : self.sig_shortcuts,
+ 'on_db_new_activate': self.sig_db_new,
+ 'on_db_restore_activate': self.sig_db_restore,
+ 'on_db_backup_activate': self.sig_db_dump,
+ 'on_db_drop_activate': self.sig_db_drop,
+ 'on_admin_password_activate': lambda x:self.sig_db_password('admin'),
+ 'on_extension_manager_activate': self.sig_extension_manager,
+ 'on_db_migrate_retrieve_script_activate': self.sig_db_migrate_retrieve_script,
+ 'on_db_migrate_activate' : self.sig_db_migrate,
+ }
+
+ self.glade.signal_autoconnect(callbacks_dict)
+
+ self.buttons = {}
+ for button in ('but_new', 'but_save', 'but_remove', 'but_search', 'but_previous', 'but_next', 'but_action', 'but_open', 'but_print', 'but_close', 'but_reload', 'but_switch','but_attach',
+ 'radio_tree','radio_form','radio_graph','radio_calendar','radio_diagram', 'radio_gantt'):
+ self.glade.signal_connect('on_'+button+'_clicked', self._sig_child_call, button)
+ self.buttons[button]=self.glade.get_widget(button)
+
+ menus = {
+ 'form_del': 'but_remove',
+ 'form_new': 'but_new',
+ 'form_copy': 'but_copy',
+ 'form_reload': 'but_reload',
+ 'form_log': 'but_log',
+ 'form_open': 'but_open',
+ 'form_search': 'but_search',
+ 'form_previous': 'but_previous',
+ 'form_next': 'but_next',
+ 'form_save': 'but_save',
+ 'goto_id': 'but_goto_id',
+ 'form_print': 'but_print',
+ 'form_print_html': 'but_print_html',
+ 'form_save_as': 'but_save_as',
+ 'form_import': 'but_import',
+ 'form_filter': 'but_filter',
+ 'form_repeat': 'but_print_repeat'
+ }
+ for menu in menus:
+ self.glade.signal_connect('on_'+menu+'_activate', self._sig_child_call, menus[menu])
+
+ spool = service.LocalService('spool')
+ spool.subscribe('gui.window', self.win_add)
+
+
+ # we now create the icon for the attachment button when there are attachments
+ self.__img_no_attachments = gtk.Image()
+ pxbf = self.window.render_icon(self.buttons['but_attach'].get_stock_id(), self.toolbar.get_icon_size())
+ self.__img_no_attachments.set_from_pixbuf(pxbf)
+ self.__img_no_attachments.show()
+
+ pxbf = pxbf.copy()
+ w, h = pxbf.get_width(), pxbf.get_height()
+ overlay = self.window.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU)
+ ow, oh = overlay.get_width(), overlay.get_height()
+ overlay.composite(pxbf,
+ 0, h - oh,
+ ow, oh,
+ 0, h - oh,
+ 1.0, 1.0,
+ gtk.gdk.INTERP_NEAREST,
+ 255)
+
+ self.__img_attachments = gtk.Image()
+ self.__img_attachments.set_from_pixbuf(pxbf)
+ self.__img_attachments.show()
+
+ self.sb_set()
+
+ settings = gtk.settings_get_default()
+ settings.set_long_property('gtk-button-images', 1, 'OpenERP:gui.main')
+
+ def fnc_menuitem(menuitem, opt_name):
+ options.options[opt_name] = menuitem.get_active()
+ dict = {
+ 'on_opt_print_preview_activate': (fnc_menuitem, 'printer.preview', 'opt_print_preview'),
+ 'on_opt_form_toolbar_activate': (fnc_menuitem, 'form.toolbar', 'opt_form_toolbar'),
+ }
+ self.glade.get_widget('menubar_'+(options.options['client.toolbar'] or 'both')).set_active(True)
+ self.sig_menubar(options.options['client.toolbar'] or 'both')
+ self.glade.get_widget('opt_form_tab_'+(options.options['client.form_tab'] or 'left')).set_active(True)
+ self.sig_form_tab(options.options['client.form_tab'] or 'left')
+ self.glade.get_widget('opt_form_tab_orientation_'+(str(options.options['client.form_tab_orientation']) or '0')).set_active(True)
+ self.sig_form_tab_orientation(options.options['client.form_tab_orientation'] or 0)
+ self.sig_debug_mode_tooltip()
+ for signal in dict:
+ self.glade.signal_connect(signal, dict[signal][0], dict[signal][1])
+ self.glade.get_widget(dict[signal][2]).set_active(int(bool(options.options[dict[signal][1]])))
+
+ # Adding a timer the check to requests
+ gobject.timeout_add(15 * 60 * 1000, self.request_set)
+
+
+ def shortcut_edit(self, widget, model='ir.ui.menu'):
+ obj = service.LocalService('gui.window')
+ domain = [('user_id', '=', rpc.session.uid), ('resource', '=', model)]
+ obj.create(False, 'ir.ui.view_sc', res_id=None, domain=domain, view_type='form', mode='tree,form')
+
+ def shortcut_set(self, sc=None):
+ def _action_shortcut(widget, action):
+ if action:
+ ctx = rpc.session.context.copy()
+ obj = service.LocalService('action.main')
+ if not isinstance(action, int):
+ action = action[0]
+ obj.exec_keyword('tree_but_open', {'model': 'ir.ui.menu', 'id': action,
+ 'ids': [action], 'report_type': 'pdf', 'window': self.window}, context=ctx)
+
+ if sc is None:
+ uid = rpc.session.uid
+ sc = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.ui.view_sc', 'get_sc', uid, 'ir.ui.menu', rpc.session.context) or []
+
+ menu = gtk.Menu()
+ for s in sc:
+ menuitem = gtk.MenuItem(s['name'])
+ menuitem.connect('activate', _action_shortcut, s['res_id'])
+ menu.add(menuitem)
+
+ menu.add(gtk.SeparatorMenuItem())
+ menuitem = gtk.MenuItem(_('Edit'))
+ menuitem.connect('activate', self.shortcut_edit)
+ menu.add(menuitem)
+
+ menu.show_all()
+ self.shortcut_menu.set_submenu(menu)
+ self.shortcut_menu.set_sensitive(True)
+
+ def shortcut_unset(self):
+ menu = gtk.Menu()
+ menu.show_all()
+ self.shortcut_menu.set_submenu(menu)
+ self.shortcut_menu.set_sensitive(False)
+
+ def sig_menubar(self, option):
+ options.options['client.toolbar'] = option
+ if option=='both':
+ self.toolbar.set_style(gtk.TOOLBAR_BOTH)
+ elif option=='text':
+ self.toolbar.set_style(gtk.TOOLBAR_TEXT)
+ elif option=='icons':
+ self.toolbar.set_style(gtk.TOOLBAR_ICONS)
+
+ def sig_form_tab(self, option):
+ options.options['client.form_tab'] = option
+
+ def sig_form_tab_orientation(self, option):
+ options.options['client.form_tab_orientation'] = option
+
+ def sig_win_next(self, args):
+ pn = self.notebook.get_current_page()
+ if pn == len(self.pages)-1:
+ pn = -1
+ self.notebook.set_current_page(pn+1)
+
+ def sig_win_prev(self, args):
+ pn = self.notebook.get_current_page()
+ self.notebook.set_current_page(pn-1)
+
+ def sig_user_preferences(self, *args):
+ win = win_preference.win_preference(parent=self.window)
+ win.run()
+ id = self.sb_company.get_context_id('message')
+ comp = self.company_set()
+ if comp:
+ self.sb_company.push(id, comp)
+ else:
+ self.sb_company.push(id, '')
+ return True
+
+ def sig_win_close(self, *args):
+ if len(args) >= 2:
+ button = args[1].button
+ if (isinstance(args[0], gtk.Button) and button in [1,2]) \
+ or (isinstance(args[0], gtk.EventBox) and button == 2):
+ page_num = self.notebook.page_num(args[2])
+ self._sig_child_call(args[0], 'but_close', page_num)
+ elif len(args) and isinstance(args[0], gtk.ImageMenuItem):
+ self._sig_child_call(args[0], 'but_close', None)
+
+ def sig_request_new(self, args=None):
+ obj = service.LocalService('gui.window')
+ try:
+ return obj.create(None, 'res.request', False,
+ [('act_from', '=', rpc.session.uid)], 'form',
+ mode='form,tree', window=self.window,
+ context={'active_test': False})
+ except:
+ return False
+
+ def sig_request_open(self, args=None):
+ ids,ids2 = self.request_set()
+ obj = service.LocalService('gui.window')
+ try:
+ return obj.create(False, 'res.request', ids,
+ [('act_to', '=', rpc.session.uid), ('active', '=', True)],
+ 'form', mode='tree,form', window=self.window,
+ context={'active_test': False})
+ except:
+ return False
+
+ def sig_request_wait(self, args=None):
+ ids,ids2 = self.request_set()
+ obj = service.LocalService('gui.window')
+ try:
+ return obj.create(False, 'res.request', ids,
+ [('act_from', '=', rpc.session.uid),
+ ('state', '=', 'waiting'), ('active', '=', True)],
+ 'form', mode='tree,form', window=self.window,
+ context={'active_test': False})
+ except:
+ return False
+
+ def company_set(self):
+ try:
+ uid = rpc.session.uid
+ ids = rpc.session.rpc_exec_auth_try('/object', 'execute',
+ 'res.users', 'get_current_company')
+ if len(ids):
+ message = _('%s') % ids[0][1]
+ else:
+ message = _('No Company')
+ id = self.sb_company.get_context_id('message')
+ self.sb_company.push(id, message)
+ return ids[0][1]
+ except:
+ return []
+
+ def request_set(self):
+ try:
+ uid = rpc.session.uid
+ ids,ids2 = rpc.session.rpc_exec_auth_try('/object', 'execute',
+ 'res.request', 'request_get')
+ if len(ids):
+ message = _('%s request(s)') % len(ids)
+ else:
+ message = _('No request')
+ if len(ids2):
+ message += _(' - %s request(s) sent') % len(ids2)
+ id = self.sb_requests.get_context_id('message')
+ self.sb_requests.push(id, message)
+ return (ids,ids2)
+ except:
+ return ([],[])
+
+ def sig_login(self, widget=None, dbname=False):
+ RES_OK = 1
+ RES_BAD_PASSWORD = -2
+ RES_CNX_ERROR = -1
+ RES_NO_DATABASE = 0
+ try:
+ log_response = RES_BAD_PASSWORD
+ res = None
+ while log_response == RES_BAD_PASSWORD:
+ try:
+ l = db_login()
+ res = l.run(dbname=dbname, parent=self.window)
+ except Exception, e:
+ if e.args == ('QueryCanceled',):
+ return False
+ raise
+ service.LocalService('gui.main').window.present()
+ self.sig_logout(widget)
+ log_response = rpc.session.login(*res)
+ if log_response == RES_OK:
+ options.options.save()
+ id = self.sig_win_menu(quiet=False)
+ if id:
+ self.sig_home_new(quiet=True, except_id=id)
+ if res[4] == 'https://':
+ self.secure_img.show()
+ else:
+ self.secure_img.hide()
+ self.request_set()
+ self.company_set()
+ elif log_response == RES_NO_DATABASE:
+ common.warning( _('Please double-check the database name or contact your administrator to verify the database status.'), _('Database cannot be accessed or does not exist'))
+ self.sig_login(dbname=dbname)
+ return True
+ elif log_response == RES_CNX_ERROR:
+ common.message(_('Connection error !\nUnable to connect to the server !'))
+ elif log_response == RES_BAD_PASSWORD:
+ common.message(_('Authentication error !\nBad Username or Password !'))
+
+ except rpc.rpc_exception:
+ rpc.session.logout()
+ raise
+ self.glade.get_widget('but_menu').set_sensitive(True)
+ self.glade.get_widget('user').set_sensitive(True)
+ self.glade.get_widget('form').set_sensitive(True)
+ self.glade.get_widget('plugins').set_sensitive(True)
+
+ title = tools.format_connection_string(*res)
+ sbid = self.sb_servername.get_context_id('message')
+ self.sb_servername.push(sbid, title)
+ self.window.set_title(_('OpenERP - %s') % title )
+ return True
+
+ def sig_logout(self, widget):
+ res = True
+ while res:
+ wid = self._wid_get()
+ if wid:
+ if 'but_close' in wid.handlers:
+ res = wid.handlers['but_close']()
+ if not res:
+ return False
+ res = self._win_del()
+ else:
+ res = False
+ id = self.sb_requests.get_context_id('message')
+ self.sb_requests.push(id, '')
+ id = self.sb_company.get_context_id('message')
+ self.sb_company.push(id, '')
+ id = self.sb_username.get_context_id('message')
+ self.sb_username.push(id, _('Not logged !'))
+ id = self.sb_servername.get_context_id('message')
+ self.sb_servername.push(id, _('Press Ctrl+O to login'))
+ self.secure_img.hide()
+ self.shortcut_unset()
+ self.glade.get_widget('but_menu').set_sensitive(False)
+ self.glade.get_widget('user').set_sensitive(False)
+ self.glade.get_widget('form').set_sensitive(False)
+ self.glade.get_widget('plugins').set_sensitive(False)
+ self.window.set_title(_('OpenERP') )
+ rpc.session.logout()
+ return True
+
+ def sig_debug_mode_tooltip(self, widget=None):
+ if widget:
+ options.options['debug_mode_tooltips'] = widget.get_active()
+ else:
+ mode = options.options['logging.level']
+ if mode in ('debug', 'debug_rpc','debug_rpc_answer'):
+ options.options['debug_mode_tooltips'] = True
+ self.glade.get_widget('opt_debug_mode_tooltip').set_active(True)
+
+
+ def sig_help_index(self, widget):
+ tools.launch_browser(options.options['help.index'])
+
+ def sig_help_context(self, widget):
+ model = self._wid_get().model
+ l = rpc.session.context.get('lang','en_US')
+ getvar = {
+ 'model': model,
+ 'lang': l,
+ }
+ tools.launch_browser(options.options['help.context'] % getvar)
+
+ def sig_licence(self, widget):
+ dialog = glade.XML(common.terp_path("openerp.glade"), "win_licence", gettext.textdomain())
+ dialog.signal_connect("on_but_ok_pressed", lambda obj: dialog.get_widget('win_licence').destroy())
+
+ win = dialog.get_widget('win_licence')
+ win.set_transient_for(self.window)
+ win.show_all()
+
+ def sig_about(self, widget):
+ about = glade.XML(common.terp_path("openerp.glade"), "win_about", gettext.textdomain())
+ buffer = about.get_widget('textview2').get_buffer()
+ about_txt = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
+ buffer.set_text(about_txt % openerp_version)
+ about.signal_connect("on_but_ok_pressed", lambda obj: about.get_widget('win_about').destroy())
+
+ win = about.get_widget('win_about')
+ win.set_transient_for(self.window)
+ win.show_all()
+
+ def sig_shortcuts(self, widget):
+ shortcuts_win = glade.XML(common.terp_path('openerp.glade'), 'shortcuts_dia', gettext.textdomain())
+ shortcuts_win.signal_connect("on_but_ok_pressed", lambda obj: shortcuts_win.get_widget('shortcuts_dia').destroy())
+
+ win = shortcuts_win.get_widget('shortcuts_dia')
+ win.set_transient_for(self.window)
+ win.show_all()
+
+ def sig_win_menu(self, widget=None, quiet=True):
+ for p in range(len(self.pages)):
+ if self.pages[p].model=='ir.ui.menu':
+ self.notebook.set_current_page(p)
+ return True
+ res = self.sig_win_new(widget, type='menu_id', quiet=quiet)
+ if not res:
+ return self.sig_win_new(widget, type='action_id', quiet=quiet)
+ return res
+
+ def sig_win_new(self, widget=None, type='menu_id', quiet=True, except_id=False):
+ try:
+ act_id = rpc.session.rpc_exec_auth('/object', 'execute', 'res.users',
+ 'read', [rpc.session.uid], [type,'name'], rpc.session.context)
+ except:
+ return False
+ id = self.sb_username.get_context_id('message')
+ self.sb_username.push(id, act_id[0]['name'] or '')
+ if not act_id[0][type]:
+ if quiet:
+ return False
+ common.warning(_("You can not log into the system !\nAsk the administrator to verify\nyou have an action defined for your user."),'Access Denied !')
+ rpc.session.logout()
+ return False
+ act_id = act_id[0][type][0]
+ if except_id and act_id == except_id:
+ return act_id
+ obj = service.LocalService('action.main')
+ obj.execute(act_id, {'window':self.window})
+ try:
+ user = rpc.session.rpc_exec_auth_wo('/object', 'execute', 'res.users',
+ 'read', [rpc.session.uid], [type,'name'], rpc.session.context)
+ if user[0][type]:
+ act_id = user[0][type][0]
+ except:
+ pass
+ return act_id
+
+ def sig_home_new(self, widget=None, quiet=True, except_id=False):
+ open_menu = self.sig_win_new(widget, type='action_id', quiet=quiet,
+ except_id=except_id)
+ if not open_menu and widget:
+ self.sig_win_menu()
+
+ def sig_plugin_execute(self, widget):
+ import plugins
+ pn = self.notebook.get_current_page()
+ datas = {'model': self.pages[pn].model, 'ids':self.pages[pn].ids_get(), 'id' : self.pages[pn].id_get()}
+ plugins.execute(datas)
+
+ def sig_quit(self, widget):
+ options.options.save()
+ gtk.main_quit()
+
+ def sig_close(self, widget):
+ if common.sur(_("Do you really want to quit ?"), parent=self.window):
+ if not self.sig_logout(widget):
+ return False
+ options.options.save()
+ gtk.main_quit()
+
+ def sig_delete(self, widget, event, data=None):
+ if common.sur(_("Do you really want to quit ?"), parent=self.window):
+ if not self.sig_logout(widget):
+ return True
+ return False
+ return True
+
+ def win_add(self, win, datas):
+ """
+ Add a tab in client
+ """
+ self.pages.append(win)
+ box = gtk.HBox(False, 0)
+
+ # Draw the close button on the right
+ closebtn = gtk.Button()
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
+ w, h = image.size_request()
+
+ closebtn.set_image(image)
+ closebtn.set_relief(gtk.RELIEF_NONE)
+ closebtn.set_size_request(w + 8, h + 4)
+ closebtn.unset_flags(gtk.CAN_FOCUS)
+
+ box_label = gtk.Label(win.name)
+ event_box = gtk.EventBox()
+ event_box.add(box_label)
+ event_box.set_visible_window(False)
+ event_box.set_events(gtk.gdk.BUTTON_PRESS_MASK)
+ box.pack_start(event_box, True, True)
+ box.pack_end(closebtn, False, False)
+
+ self.notebook.append_page(win.widget, box)
+ if hasattr(self.notebook, 'set_tab_reorderable' ):
+ # since pygtk 2.10
+ self.notebook.set_tab_reorderable(win.widget, True)
+
+ event_box.connect("button_press_event", self.sig_win_close, win.widget)
+ closebtn.connect("button-press-event", self.sig_win_close, win.widget)
+ pagenum = self.notebook.page_num(win.widget)
+ pagenum = self.notebook.page_num(image)
+
+ box.show_all()
+
+ self.notebook.set_tab_label_packing(image, True, True, gtk.PACK_START)
+ self.notebook.set_tab_label(image, box)
+ image.show_all()
+ self.notebook.set_current_page(-1)
+
+ def message(self, message):
+ id = self.status_bar.get_context_id('message')
+ self.status_bar.push(id, message)
+
+ def __attachment_callback(self, view, objid):
+ current_view = self._wid_get()
+ current_id = current_view and current_view.id_get()
+ if current_view == view and objid == current_id:
+ cpt = None
+ if objid and view.screen.current_view.view_type == 'form':
+ cpt = rpc.session.rpc_exec_auth('/object', 'execute',
+ 'ir.attachment', 'search_count',
+ [('res_model', '=', view.model), ('res_id', '=', objid)])
+ if cpt:
+ self.buttons['but_attach'].set_icon_widget(self.__img_attachments)
+ self.buttons['but_attach'].set_label(_('Attachments (%d)') % cpt)
+
+
+ def _update_attachment_button(self, view = None):
+ """
+ Update the attachment icon for display the number of attachments
+ """
+ if not view:
+ view = self._wid_get()
+
+ id = view and view.id_get()
+ gobject.timeout_add(1500, self.__attachment_callback, view, id)
+ self.buttons['but_attach'].set_icon_widget(self.__img_no_attachments)
+ self.buttons['but_attach'].set_label(_('Attachments'))
+
+
+ def sb_set(self, view=None):
+ if not view:
+ view = self._wid_get()
+ if view and hasattr(view, 'screen'):
+ self._handler_ok = False
+ type = view.screen.current_view.view_type
+ if type == 'dummycalendar':
+ type = 'calendar'
+ self.glade.get_widget('radio_'+type).set_active(True)
+ self._handler_ok = True
+ self._update_attachment_button(view)
+ for x in self.buttons:
+ if self.buttons[x]:
+ self.buttons[x].set_sensitive(view and (x in view.handlers))
+
+ def _win_del(self,page_num=None):
+ """
+ Del tab in Client
+ """
+ if page_num is not None:
+ pn = page_num
+ else:
+ pn = self.notebook.get_current_page()
+ if pn != -1:
+
+ self.notebook.disconnect(self.sig_id)
+ page = self.pages.pop(pn)
+
+ self.notebook.remove_page(pn)
+ self.sig_id = self.notebook.connect_after('switch-page', self._sig_page_changed)
+ self.sb_set()
+
+ page.destroy()
+ del page
+ gc.collect()
+
+ return self.notebook.get_current_page() != -1
+
+ def _wid_get(self,page_num=None):
+ if page_num is not None:
+ pn = page_num
+ else:
+ pn = self.notebook.get_current_page()
+ if pn == -1:
+ return False
+ return self.pages[pn]
+
+ def _sig_child_call(self, widget, button_name, *args):
+ page_num = None
+ if len(args):
+ page_num = args[0]
+ if not self._handler_ok:
+ return
+ wid = self._wid_get(page_num)
+ if wid:
+ res = True
+ if button_name.startswith('radio_'):
+ act = self.glade.get_widget(button_name).get_active()
+ if not act: return False
+
+ if button_name in wid.handlers:
+ res = wid.handlers[button_name]()
+ # for those buttons, we refresh the attachment button.
+ # for the "switch view" button, the action has already
+ # been called by the Screen object of the view (wid)
+ if button_name in ('but_new', 'but_remove', 'but_search', \
+ 'but_previous', 'but_next', 'but_open', \
+ 'but_close', 'but_reload', 'but_attach', 'but_goto_id'):
+ self._update_attachment_button(wid)
+ if button_name=='but_close' and res:
+ self._win_del(page_num)
+
+
+ def _sig_page_changed(self, widget=None, *args):
+ self.last_page = self.current_page
+ self.current_page = self.notebook.get_current_page()
+ self.sb_set()
+
+ def _sig_page_reordered(self, notebook, child, page_num, user_param=None):
+ widget = self.pages[self.current_page]
+ self.pages.remove(widget)
+ self.pages.insert(page_num, widget)
+ self.current_page = page_num
+
+ def sig_db_new(self, widget):
+ if not self.sig_logout(widget):
+ return False
+ dia = db_create(self.sig_login, self)
+ res = dia.run(self.window)
+ if res:
+ options.options.save()
+ return res
+
+ def sig_db_drop(self, widget):
+ if not self.sig_logout(widget):
+ return False
+ url, db_name, passwd = self._choose_db_select(_('Delete a database'))
+ if not db_name:
+ return
+
+ try:
+ rpc.session.db_exec(url, 'drop', passwd, db_name)
+ common.message(_("Database dropped successfully !"), parent=self.window)
+ except Exception, e:
+ if (getattr(e,'faultCode',False)=='AccessDenied') or str(e)=='AccessDenied':
+ common.warning(_('Bad database administrator password !'),_("Could not drop database."), parent=self.window)
+ else:
+ common.warning(_("Couldn't drop database"), parent=self.window)
+
+ def sig_db_restore(self, widget):
+ filename = common.file_selection(_('Open...'), parent=self.window, preview=False)
+ if not filename:
+ return
+
+ url, db_name, passwd = self._choose_db_ent()
+ if db_name:
+ try:
+ f = file(filename, 'rb')
+ data_b64 = base64.encodestring(f.read())
+ f.close()
+ res = rpc.session.db_exec(url, 'restore', passwd, db_name, data_b64)
+ if res:
+ common.message(_("Database restored successfully !"), parent=self.window)
+ except Exception,e:
+ if (getattr(e,'faultCode',False)=='AccessDenied') or str(e)=='AccessDenied':
+ common.warning(_('Bad database administrator password !'),_("Could not restore database."), parent=self.window)
+ else:
+ common.warning(_("Couldn't restore database"), parent=self.window)
+
+ def sig_db_migrate_retrieve_script(self, widget):
+ RetrieveMigrationScriptDialog(self.window).run()
+
+ def sig_db_migrate(self, widget):
+ MigrationDatabaseDialog(self.window).run()
+
+ def sig_extension_manager(self,widget):
+ win = win_extension.win_extension(self.window)
+ win.run()
+
+ def sig_db_password(self, type):
+ dialog = glade.XML(common.terp_path("openerp.glade"), "dia_passwd_change",
+ gettext.textdomain())
+ win = dialog.get_widget('dia_passwd_change')
+ win.set_icon(common.OPENERP_ICON)
+ win.set_transient_for(self.window)
+ win.show_all()
+ server_widget = dialog.get_widget('ent_server2')
+ ser_label = dialog.get_widget('label298')
+ old_pass_widget = dialog.get_widget('old_passwd')
+ new_pass_widget = dialog.get_widget('new_passwd')
+ new_pass2_widget = dialog.get_widget('new_passwd2')
+ dia_label = dialog.get_widget('label294')
+ change_button = dialog.get_widget('but_server_change1')
+ old_pass_widget.grab_focus()
+
+ if type == 'admin':
+ host = options.options['login.server']
+ port = options.options['login.port']
+ protocol = options.options['login.protocol']
+ url = '%s%s:%s' % (protocol, host, port)
+ server_widget.set_text(url)
+ change_button.connect_after('clicked', lambda a,b: _server_ask(b, win), server_widget)
+ else:
+ dia_label.set_label(_('<b>Change your password</b>'))
+ server_widget.hide()
+ ser_label.hide()
+ change_button.hide()
+ end = False
+ while not end:
+ res = win.run()
+ if res == gtk.RESPONSE_OK:
+ old_passwd = old_pass_widget.get_text()
+ new_passwd = new_pass_widget.get_text()
+ new_passwd2 = new_pass2_widget.get_text()
+ if new_passwd != new_passwd2:
+ new_pass_widget.set_text('')
+ new_pass_widget.grab_focus()
+ new_pass2_widget.set_text('')
+ common.warning(_("Confirmation password does not match " \
+ "new password, operation cancelled!"),
+ _("Validation Error."), parent=win)
+ else:
+ try:
+ if type == 'user':
+ rpc.session.rpc_exec_auth_wo('/object', 'execute', 'res.users', 'change_password',
+ old_passwd, new_passwd)
+ rpc.session._passwd = new_passwd
+ else:
+ url = server_widget.get_text()
+ rpc.session.db_exec(url, 'change_admin_password',
+ old_passwd, new_passwd)
+ end = True
+ except Exception, e:
+
+ if type == 'admin':
+ if ('faultCode' in dir(e) and e.faultCode=="AccessDenied") \
+ or 'AccessDenied' in str(e):
+ common.warning(_("Could not change the Super Admin password."),
+ _('Bad password provided !'), parent=win)
+ else:
+ if e.type == 'warning':
+ common.warning(e.data, e.message, parent=win)
+ elif e.type == 'AccessDenied':
+ common.warning(_("Changing password failed, please verify old password."),
+ _('Bad password provided !'), parent=win)
+ else:
+ end = True
+ self.window.present()
+ win.destroy()
+
+ def sig_db_dump(self, widget):
+ url, db_name, passwd = self._choose_db_select(_('Backup a database'))
+ if not db_name:
+ return
+ filename = common.file_selection(_('Save As...'),
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ parent=self.window,
+ preview=False,
+ filename=('%s_%s.sql' % (db_name, time.strftime('%Y%m%d_%H:%M'),)).replace(':','_'))
+
+ if filename:
+ try:
+ dump_b64 = rpc.session.db_exec(url, 'dump', passwd, db_name)
+ dump = base64.decodestring(dump_b64)
+ f = file(filename, 'wb')
+ f.write(dump)
+ f.close()
+ common.message(_("Database backed up successfully !"), parent=self.window)
+ except Exception,e:
+ if getattr(e,'faultCode',False)=='AccessDenied':
+ common.warning(_('Bad database administrator password !'), _("Could not backup the database."),parent=self.window)
+ else:
+ common.warning(_("Couldn't backup database."), parent=self.window)
+
+ def _choose_db_select(self, title=_("Backup a database")):
+
+ def refreshlist_ask(widget, server_widget, db_widget, entry_db, label, parent=None):
+ url = _server_ask(server_widget, parent)
+ if not url:
+ return None
+ _refresh_dblist(db_widget, entry_db, label, False, url)
+
+ dialog = glade.XML(common.terp_path("openerp.glade"), "win_db_select",
+ gettext.textdomain())
+ win = dialog.get_widget('win_db_select')
+ win.set_icon(common.OPENERP_ICON)
+ win.set_default_response(gtk.RESPONSE_OK)
+ win.set_transient_for(self.window)
+ win.show_all()
+
+ pass_widget = dialog.get_widget('ent_passwd_select')
+ server_widget = dialog.get_widget('ent_server_select')
+ db_widget = dialog.get_widget('combo_db_select')
+ entry_db = dialog.get_widget('entry_db_select')
+ label = dialog.get_widget('label_db_select')
+ entry_db.hide()
+
+ dialog.get_widget('db_select_label').set_markup('<b>'+title+'</b>')
+
+ protocol = options.options['login.protocol']
+ url = '%s%s:%s' % (protocol, options.options['login.server'], options.options['login.port'])
+ server_widget.set_text(url)
+
+ liststore = gtk.ListStore(str)
+ db_widget.set_model(liststore)
+
+ _refresh_dblist(db_widget, entry_db, label, False, url)
+ change_button = dialog.get_widget('but_server_select')
+ change_button.connect_after('clicked', refreshlist_ask, server_widget, db_widget, entry_db, label, win)
+
+ cell = gtk.CellRendererText()
+ db_widget.pack_start(cell, True)
+ db_widget.add_attribute(cell, 'text', 0)
+
+ res = win.run()
+
+ db = False
+ url = False
+ passwd = False
+ if res == gtk.RESPONSE_OK:
+ if (not db_widget.get_property('visible')) and entry_db.get_property('visible'):
+ db = entry_db.get_text()
+ else:
+ db = db_widget.get_active_text()
+ url = server_widget.get_text()
+ passwd = pass_widget.get_text()
+ self.window.present()
+ win.destroy()
+ return (url,db,passwd)
+
+ def _choose_db_ent(self):
+ dialog = glade.XML(common.terp_path("openerp.glade"), "win_db_ent", gettext.textdomain())
+ win = dialog.get_widget('win_db_ent')
+ win.set_icon(common.OPENERP_ICON)
+ win.set_transient_for(self.window)
+ win.show_all()
+
+ db_widget = dialog.get_widget('ent_db')
+ widget_pass = dialog.get_widget('ent_password')
+ widget_url = dialog.get_widget('ent_server1')
+
+ protocol = options.options['login.protocol']
+ url = '%s%s:%s' % (protocol, options.options['login.server'],
+ options.options['login.port'])
+ widget_url.set_text(url)
+
+ change_button = dialog.get_widget('but_server_change')
+ change_button.connect_after('clicked', lambda a,b: _server_ask(b, win),
+ widget_url)
+
+ res = win.run()
+
+ db = False
+ passwd = False
+ url = False
+ if res == gtk.RESPONSE_OK:
+ db = db_widget.get_text()
+ url = widget_url.get_text()
+ passwd = widget_pass.get_text()
+ self.window.present()
+ win.destroy()
+ return url, db, passwd
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added directory 'bin/modules/gui/window'
=== added file 'bin/modules/gui/window/__init__.py'
--- bin/modules/gui/window/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import service
+import rpc
+
+import common
+import form
+import tree
+
+
+class window_int(object):
+ def __init__(self, view, datas):
+ self.name = datas.get('name', _('Unknown Window'))
+
+
+class window(service.Service):
+ def __init__(self, name='gui.window'):
+ service.Service.__init__(self, name)
+ def create(self, view_ids, model, res_id=False, domain=None,
+ view_type='form', window=None, context=None, mode=None, name=False,help={},
+ limit=100, auto_refresh=False, auto_search=True, search_view=None):
+ if context is None:
+ context = {}
+ context.update(rpc.session.context)
+
+ if view_type=='form':
+ mode = (mode or 'form,tree').split(',')
+ win = form.form(model, res_id, domain, view_type=mode,
+ view_ids = (view_ids or []), window=window,
+ context=context, name=name, help=help, limit=limit,
+ auto_refresh=auto_refresh, auto_search=auto_search, search_view=search_view)
+ spool = service.LocalService('spool')
+ spool.publish('gui.window', win, {})
+ elif view_type=='tree':
+ if view_ids and view_ids[0]:
+ view_base = rpc.session.rpc_exec_auth('/object', 'execute',
+ 'ir.ui.view', 'read', [view_ids[0]],
+ ['model', 'type'], context)[0]
+ model = view_base['model']
+ view = rpc.session.rpc_exec_auth('/object', 'execute',
+ view_base['model'], 'fields_view_get', view_ids[0],
+ view_base['type'],context)
+ else:
+ view = rpc.session.rpc_exec_auth('/object', 'execute', model,
+ 'fields_view_get', False, view_type, context)
+
+ win = tree.tree(view, model, res_id, domain, context,help=help,
+ window=window, name=name)
+ spool = service.LocalService('spool')
+ spool.publish('gui.window', win, {})
+ else:
+ import logging
+ log = logging.getLogger('view')
+ log.error('unknown view type: '+view_type)
+ del log
+
+window()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/form.py'
--- bin/modules/gui/window/form.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/form.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,479 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import types
+import gettext
+
+import gtk
+import gobject
+from gtk import glade
+
+import rpc
+import win_selection
+import win_search
+import win_export
+import win_import
+import win_list
+
+from gtk.gdk import Color
+
+import common
+import service
+import options
+import copy
+
+
+from observator import oregistry
+from widget.screen import Screen
+
+class form(object):
+ def __init__(self, model, res_id=False, domain=None, view_type=None,
+ view_ids=None, window=None, context=None, name=False, help={}, limit=100,
+ auto_refresh=False, auto_search=True, search_view=None):
+ if not view_type:
+ view_type = ['form','tree']
+ if domain is None:
+ domain = []
+ if view_ids is None:
+ view_ids = []
+ if context is None:
+ context = {}
+
+ fields = {}
+ self.model = model
+ self.window = window
+ self.previous_action = None
+ self.glade = glade.XML(common.terp_path("openerp.glade"),'win_form_container',gettext.textdomain())
+ self.widget = self.glade.get_widget('win_form_container')
+ self.widget.show_all()
+ self.fields = fields
+ self.domain = domain
+ self.context = context
+ self.screen = Screen(self.model, view_type=view_type,
+ context=self.context, view_ids=view_ids, domain=domain,help=help,
+ hastoolbar=options.options['form.toolbar'], hassubmenu=options.options['form.submenu'],
+ show_search=True, window=self.window, limit=limit, readonly=bool(auto_refresh), auto_search=auto_search, search_view=search_view)
+ self.screen.signal_connect(self, 'record-message', self._record_message)
+ self.screen.widget.show()
+ oregistry.add_receiver('misc-message', self._misc_message)
+
+ if not name:
+ self.name = self.screen.current_view.title
+ else:
+ self.name = name
+ vp = gtk.Viewport()
+ vp.set_shadow_type(gtk.SHADOW_NONE)
+ vp.add(self.screen.widget)
+ vp.show()
+ self.sw = gtk.ScrolledWindow()
+ self.sw.set_shadow_type(gtk.SHADOW_NONE)
+ self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.sw.add(vp)
+ self.sw.show()
+
+ self.has_backup = False
+ self.backup = {}
+
+ self.widget.pack_start(self.sw)
+ self.handlers = {
+ 'but_new': self.sig_new,
+ 'but_copy': self.sig_copy,
+ 'but_save': self.sig_save,
+ 'but_save_as': self.sig_save_as,
+ 'but_import': self.sig_import,
+ 'but_print_repeat': self.sig_print_repeat,
+ 'but_remove': self.sig_remove,
+ 'but_search': self.sig_search,
+ 'but_previous': self.sig_previous,
+ 'but_next': self.sig_next,
+ 'but_goto_id': self.sig_goto,
+ 'but_log': self.sig_logs,
+ 'but_print': self.sig_print,
+ 'but_reload': self.sig_reload,
+ 'but_print_html': self.sig_print_html,
+ 'but_action': self.sig_action,
+ 'but_switch': self.sig_switch,
+ 'but_attach': self.sig_attach,
+ 'but_close': self.sig_close,
+ }
+ if 'tree' in view_type:
+ self.handlers['radio_tree'] = self.sig_switch_tree
+ if 'form' in view_type:
+ self.handlers['radio_form'] = self.sig_switch_form
+ if 'graph' in view_type:
+ self.handlers['radio_graph'] = self.sig_switch_graph
+ if 'calendar' in view_type:
+ self.handlers['radio_calendar'] = self.sig_switch_calendar
+ if 'diagram' in view_type:
+ self.handlers['radio_diagram'] = self.sig_switch_diagram
+ if res_id:
+ if isinstance(res_id, (int, long,)):
+ res_id = [res_id]
+ self.screen.load(res_id)
+ else:
+ if self.screen.current_view.view_type == 'form':
+ self.sig_new(autosave=False)
+ if self.screen.current_view.view_type in ('tree', 'graph', 'calendar'):
+ self.screen.search_filter()
+
+ if auto_refresh and int(auto_refresh):
+ gobject.timeout_add(int(auto_refresh) * 1000, self.sig_reload)
+
+ def sig_switch_diagram(self, widget=None):
+ return self.sig_switch(widget, 'diagram')
+
+ def sig_switch_form(self, widget=None):
+ return self.sig_switch(widget, 'form')
+
+ def sig_switch_tree(self, widget=None):
+ return self.sig_switch(widget, 'tree')
+
+ def sig_switch_calendar(self, widget=None):
+ return self.sig_switch(widget, 'calendar')
+
+ def sig_switch_graph(self, widget=None):
+ return self.sig_switch(widget, 'graph')
+
+ def get_resource(self, widget=None, get_id=None):
+ ## This has been done due to virtual ids coming from
+ ## crm meeting. like '3-20101012155505' which are not in existence
+ ## and needed to be converted to real ids
+ if isinstance(get_id, str):
+ get_id = int(get_id.split('-')[0])
+ all_ids = rpc.session.rpc_exec_auth('/object', 'execute', self.model, 'search', [])
+ if widget:
+ get_id = int(widget.get_value())
+ if get_id in all_ids:
+ current_ids = self.screen.ids_get()
+ if get_id in current_ids:
+ self.screen.display(get_id)
+ else:
+ self.screen.load([get_id])
+ self.screen.current_view.set_cursor()
+ else:
+ if widget:
+ common.message(_('Resource ID does not exist for this object!'))
+
+ def get_event(self, widget, event, win):
+ if event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
+ win.destroy()
+ self.get_resource(widget)
+
+ def sig_goto(self, *args):
+ if not self.modified_save():
+ return
+
+ glade2 = glade.XML(common.terp_path("openerp.glade"),'dia_goto_id',gettext.textdomain())
+ widget = glade2.get_widget('goto_spinbutton')
+ win = glade2.get_widget('dia_goto_id')
+ widget.connect('key_press_event',self.get_event,win)
+
+ win.set_transient_for(self.window)
+ win.show_all()
+
+ response = win.run()
+ win.destroy()
+
+ if response == gtk.RESPONSE_OK:
+ self.get_resource(widget)
+
+ def destroy(self):
+
+ """
+ Destroy the page object and all the child
+ (or at least should do this)
+ """
+ oregistry.remove_receiver('misc-message', self._misc_message)
+ self.screen.signal_unconnect(self)
+ self.screen.destroy()
+ self.widget.destroy()
+ self.sw.destroy()
+ del self.screen
+ del self.handlers
+
+ def ids_get(self):
+ return self.screen.ids_get()
+
+ def id_get(self):
+ return self.screen.id_get()
+
+ def sig_attach(self, widget=None):
+ id = self.id_get()
+ if id:
+ ctx = self.context.copy()
+ ctx.update(rpc.session.context)
+ action = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.attachment', 'action_get', ctx)
+ action['domain'] = [('res_model', '=', self.model), ('res_id', '=', id)]
+ ctx['default_res_model'] = self.model
+ ctx['default_res_id'] = id
+ obj = service.LocalService('action.main')
+ obj._exec_action(action, {}, ctx)
+ else:
+ self.message_state(_('No record selected ! You can only attach to existing record.'), color='red')
+ return True
+
+ def sig_switch(self, widget=None, mode=None):
+ if not self.modified_save():
+ return
+ id = self.screen.id_get()
+ if mode<>self.screen.current_view.view_type:
+ self.screen.switch_view(mode=mode)
+ if id:
+ self.sig_reload()
+ self.get_resource(get_id=id)
+
+ def sig_logs(self, widget=None):
+ id = self.id_get()
+ if not id:
+ self.message_state(_('You have to select a record !'), color='red')
+ return False
+ res = rpc.session.rpc_exec_auth('/object', 'execute', self.model, 'perm_read', [id])
+ message = ''
+ for line in res:
+ todo = [
+ ('id', _('ID')),
+ ('create_uid', _('Creation User')),
+ ('create_date', _('Creation Date')),
+ ('write_uid', _('Latest Modification by')),
+ ('write_date', _('Latest Modification Date')),
+ ('xmlid', _('Internal Module Data ID'))
+ ]
+ for (key,val) in todo:
+ if line[key] and key in ('create_uid','write_uid','uid'):
+ line[key] = line[key][1]
+ message+=val+': '+str(line[key] or '/')+'\n'
+ common.message(message)
+ return True
+
+ def sig_remove(self, widget=None):
+ if not self.id_get():
+ msg = _('Record is not saved ! \n Do you want to clear current record ?')
+ else:
+ if self.screen.current_view.view_type == 'form':
+ msg = _('Are you sure to remove this record ?')
+ else:
+ msg = _('Are you sure to remove those records ?')
+ if common.sur(msg):
+ id = self.screen.remove(unlink=True)
+ if not id:
+ self.message_state(_('Resources cleared.'), color='darkgreen')
+ else:
+ self.message_state(_('Resources successfully removed.'), color='darkgreen')
+ self.sig_reload()
+
+ def sig_import(self, widget=None):
+ fields = []
+ while(self.screen.view_to_load):
+ self.screen.load_view_to_load()
+ screen_fields = copy.deepcopy(self.screen.fields)
+ win = win_import.win_import(self.model, screen_fields, fields, parent=self.window,local_context= self.screen.context)
+ res = win.go()
+
+ def sig_save_as(self, widget=None):
+ fields = []
+ while(self.screen.view_to_load):
+ self.screen.load_view_to_load()
+ screen_fields = copy.deepcopy(self.screen.fields)
+ win = win_export.win_export(self.model, self.screen.ids_get(), screen_fields, fields, parent=self.window, context=self.context)
+ res = win.go()
+
+ def sig_new(self, widget=None, autosave=True):
+ if autosave:
+ if not self.modified_save():
+ return
+ self.screen.create_new = True
+ self.screen.new()
+ self.message_state('')
+
+ def sig_copy(self, *args):
+ if not self.modified_save():
+ return
+ res_id = self.id_get()
+ ctx = self.context.copy()
+ ctx.update(rpc.session.context)
+ new_id = rpc.session.rpc_exec_auth('/object', 'execute', self.model, 'copy', res_id, {}, ctx)
+ if new_id:
+ self.screen.load([new_id])
+ self.screen.current_view.set_cursor()
+ self.message_state(_('Working now on the duplicated document !'))
+ self.sig_reload()
+
+ def _form_save(self, auto_continue=True):
+ pass
+
+ def sig_save(self, widget=None, sig_new=True, auto_continue=True):
+ res = self.screen.save_current()
+ warning = False
+ if isinstance(res,dict):
+ id = res.get('id',False)
+ warning = res.get('warning',False)
+ else:
+ id = res
+ if id:
+ self.message_state(_('Document Saved.'), color="darkgreen")
+ elif len(self.screen.models.models) and res != None:
+ common.warning(_('Invalid form, correct red fields !'),_('Error !'), parent=self.screen.current_view.window)
+ self.message_state(_('Invalid form, correct red fields !'), color="red")
+ if warning:
+ common.warning(warning,_('Warning !'), parent=self.screen.current_view.window)
+ return bool(id)
+
+ def sig_previous(self, widget=None):
+ if not self.modified_save():
+ return
+ self.screen.display_prev()
+ self.message_state('')
+
+ def sig_next(self, widget=None):
+ if not self.modified_save():
+ return
+ self.screen.display_next()
+ self.message_state('')
+
+ def sig_reload(self, test_modified=True):
+ if not hasattr(self, 'screen'):
+ return False
+ if test_modified and self.screen.is_modified():
+ res = common.sur_3b(_('This record has been modified\n' \
+ 'do you want to save it ?'))
+ if res == 'ok':
+ self.sig_save()
+ elif res == 'ko':
+ pass
+ else:
+ return False
+ if self.screen.current_view.view_type == 'form':
+ self.screen.cancel_current()
+ self.screen.display()
+ else:
+ id = self.screen.id_get()
+ self.screen.search_filter()
+ for model in self.screen.models:
+ if model.id == id:
+ self.screen.current_model = model
+ self.screen.display()
+ break
+ self.message_state('')
+ return True
+
+ def sig_action(self, keyword='client_action_multi', previous=False, report_type='pdf', adds={}):
+ ids = self.screen.ids_get()
+ group_by = self.screen.context.get('group_by')
+ if self.screen.current_model:
+ id = self.screen.current_model.id
+ else:
+ id = False
+ if self.screen.current_view.view_type == 'form':
+ id = self.screen.save_current()
+ if not id:
+ return False
+ ids = [id]
+ if self.screen.current_view.view_type == 'tree':
+ self.modified_save()
+ sel_ids = self.screen.sel_ids_get()
+ if sel_ids:
+ ids = sel_ids
+ if len(ids) or group_by:
+ obj = service.LocalService('action.main')
+ data = {'model':self.screen.resource,
+ 'id': id or False,
+ 'ids':ids,
+ 'report_type': report_type,
+ '_domain':self.screen.domain
+ }
+ # When group by header is selected add it's children as a active_ids
+ if group_by:
+ self.screen.context.update({'active_id':id, 'active_ids':ids})
+ if previous and self.previous_action:
+ obj._exec_action(self.previous_action[1], data, self.screen.context)
+ else:
+ res = obj.exec_keyword(keyword, data, adds, self.screen.context)
+ if res:
+ self.previous_action = res
+ self.sig_reload(test_modified=False)
+ else:
+ self.message_state(_('You must select one or several records !'),color='red')
+
+ def sig_print_repeat(self):
+ self.sig_action('client_print_multi', True)
+
+ def sig_print_html(self):
+ self.sig_action('client_print_multi', report_type='html')
+
+ def sig_print(self):
+ self.sig_action('client_print_multi', adds={_('Print Screen').encode('utf8'): {'report_name':'printscreen.list', 'name':_('Print Screen'), 'type':'ir.actions.report.xml'}})
+
+ def sig_search(self, widget=None):
+ if not self.modified_save():
+ return
+ dom = self.domain
+ win = win_search.win_search(self.model, domain=self.domain, context=self.context, parent=self.window)
+ res = win.go()
+ if res:
+ self.screen.clear()
+ self.screen.load(res)
+
+ def message_state(self, message, context='message', color=None):
+ sb = self.glade.get_widget('stat_state')
+ if color is not None:
+ message = '<span foreground="%s">%s</span>' % (color, message)
+ sb.set_label(message)
+
+ def _record_message(self, screen, signal_data):
+ if not signal_data[3]:
+ msg = _('No record selected')
+ else:
+ name = '_'
+ if signal_data[0]>=0:
+ name = str(signal_data[0]+1)
+ name2 = _('New document')
+ if signal_data[3]:
+ name2 = _('Editing document (id: ')+str(signal_data[3])+')'
+ # Total Records should never change
+ tot_count = signal_data[2] < signal_data[1] and str(signal_data[1]) or str(signal_data[2])
+ msg = _('Record: ') + name + ' / ' + str(signal_data[1]) + \
+ _(' of ') + str(tot_count) + ' - ' + name2
+ sb = self.glade.get_widget('stat_form')
+ cid = sb.get_context_id('message')
+ sb.push(cid, msg)
+
+ def _misc_message(self, obj, message, color=None):
+ self.message_state(message, color=color)
+
+ def modified_save(self, reload=True):
+ if self.screen.is_modified():
+ value = common.sur_3b(_('This record has been modified\ndo you want to save it ?'))
+ if value == 'ok':
+ return self.sig_save()
+ elif value == 'ko':
+ if reload:
+ self.sig_reload(test_modified=False)
+ return True
+ else:
+ return False
+ return True
+
+ def sig_close(self, urgent=False):
+ res = self.modified_save(reload=False)
+ return res
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/tree.py'
--- bin/modules/gui/window/tree.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/tree.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,328 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+from gtk import glade
+import gettext
+import xmlrpclib
+
+import common
+import service
+import view_tree
+import rpc
+import options
+import win_export
+import copy
+
+class tree(object):
+ def __init__(self, view, model, res_id=False, domain=[], context={}, help={}, window=None, name=False):
+ self.glade = glade.XML(common.terp_path("openerp.glade"),'win_tree_container',gettext.textdomain())
+ self.widget = self.glade.get_widget('win_tree_container')
+ self.widget.show_all()
+ self.model = view['model']
+ self.domain2 = domain
+ if view.get('field_parent', False):
+ self.domain = []
+ else:
+ self.domain = domain
+ self.view = view
+ self.window=window
+
+ self.context=context
+
+ self.tree_res = view_tree.view_tree(view, [], res_id, True, context=context)
+ self.tree_res.view.connect('row-activated', self.sig_open)
+
+ sel = self.tree_res.view.get_selection()
+ sel.connect('changed', self.expand_one)
+
+ if not name:
+ self.name = self.tree_res.name
+ else:
+ self.name = name
+ self.vp = self.glade.get_widget('main_tree_sw')
+
+ wid = self.glade.get_widget('widget_vbox')
+ wid.show()
+
+ widget_sc = self.glade.get_widget('win_tree_sc')
+
+ widget_sc.connect('row-activated', self.sc_go)
+ self.tree_sc = view_tree.view_tree_sc(widget_sc, self.model)
+ self.handlers = {
+ 'but_reload': self.sig_reload,
+ 'but_switch': self.sig_edit,
+ 'but_chroot': self.sig_chroot,
+ 'but_open': self.sig_action,
+ 'but_action': self.sig_action,
+ 'but_print': self.sig_print,
+ 'but_print_html': self.sig_print_html,
+ 'but_close': self.sig_close,
+ 'but_save_as': self.sig_save_as,
+ }
+ dict = {
+ 'on_but_sc_go_clicked': self.sc_go,
+ 'on_but_sc_add_clicked': self.sc_add,
+ 'on_but_sc_del_clicked': self.sc_del,
+ 'on_but_expand_collapse_clicked': self.expand_collapse_all,
+ 'on_tbsc_clicked': self.sc_btn,
+ }
+
+ self.help = help
+ self.help_frame = False
+ wid = self.tree_res.widget_get()
+ if self.help:
+ action_tips = common.action_tips(self.help)
+ self.help_frame = action_tips.help_frame
+ if self.help_frame:
+ vbox = gtk.VBox()
+ vbox.pack_start(self.help_frame, expand=False, fill=False, padding=2)
+ vbox.pack_end(wid)
+ vbox.show_all()
+ wid = vbox
+ if self.help_frame:
+ self.vp.add_with_viewport(wid)
+ else:
+ self.vp.add(wid)
+ self.sig_reload()
+
+ for signal in dict:
+ self.glade.signal_connect(signal, dict[signal])
+ self.expand = True
+
+ def sig_reload(self, widget=None):
+ self.tree_sc.update()
+ ids = rpc.session.rpc_exec_auth('/object', 'execute', self.model, 'search', self.domain2)
+ if self.tree_res.toolbar:
+ icon_name = 'icon'
+ wid = self.glade.get_widget('tree_toolbar')
+ for w in wid.get_children():
+ wid.remove(w)
+ c = {}
+ c.update(rpc.session.context)
+ res_ids = rpc.session.rpc_exec_auth_try('/object', 'execute', self.view['model'], 'read', ids, ['name',icon_name], c)
+ rb = None
+ for r in res_ids:
+ rb = gtk.RadioToolButton(group=rb)
+ l = gtk.Label(r['name'])
+ rb.set_label_widget(l)
+
+ icon = gtk.Image()
+ if icon_name in r:
+ if hasattr(r[icon_name], 'startswith') and r[icon_name].startswith('STOCK_'):
+ icon.set_from_stock(getattr(gtk, r[icon_name]), gtk.ICON_SIZE_BUTTON)
+ else:
+ try:
+ icon.set_from_stock(r[icon_name], gtk.ICON_SIZE_BUTTON)
+ except:
+ pass
+
+ hb = gtk.HBox(spacing=6)
+ hb.pack_start(icon)
+ hb.pack_start(gtk.Label(r['name']))
+ rb.set_icon_widget(hb)
+ rb.show_all()
+ rb.set_data('id', r['id'])
+ rb.connect('clicked', self.menu_main_clicked)
+ self.menu_main_clicked(rb)
+ wid.insert(rb, -1)
+ else:
+ self.tree_res.ids = ids
+ self.tree_res.reload()
+ wid = self.glade.get_widget('widget_vbox')
+ wid.hide()
+
+ def menu_main_clicked(self, widget):
+ if widget.get_active():
+ id = widget.get_data('id')
+
+ ids = rpc.session.rpc_exec_auth('/object', 'execute', self.model, 'read', [id], [self.view['field_parent']])[0][self.view['field_parent']]
+
+ self.tree_res.ids = ids
+ self.tree_res.reload()
+
+ self.expand = False
+ self.expand_collapse_all( self.glade.get_widget('button7') )
+
+ return False
+
+ def expand_collapse_all(self, widget):
+ if self.expand:
+ self.tree_res.view.expand_all()
+ else:
+ self.tree_res.view.collapse_all()
+ self.expand = not self.expand
+ if self.expand:
+ widget.set_stock_id('gtk-goto-bottom')
+ else:
+ widget.set_stock_id('gtk-goto-top')
+
+ def expand_one(self, selection):
+ model,iter = selection.get_selected_rows()
+ if iter:
+ self.tree_res.view.expand_row(iter[0],False)
+
+ def sig_print_html(self, widget=None, keyword='client_print_multi', id=None):
+ self.sig_action(keyword='client_print_multi', report_type='html')
+
+ def sig_print(self, widget=None, keyword='client_print_multi', id=None):
+ self.sig_action(keyword='client_print_multi')
+
+ def sig_action(self, widget=None, keyword='tree_but_action', id=None, report_type='pdf', warning=True):
+ ids = self.ids_get()
+
+ if not id and ids and len(ids):
+ id = ids[0]
+ if id:
+ ctx = self.context.copy()
+ if 'active_ids' in ctx:
+ del ctx['active_ids']
+ if 'active_id' in ctx:
+ del ctx['active_id']
+ obj = service.LocalService('action.main')
+ return obj.exec_keyword(keyword, {'model':self.model, 'id':id,
+ 'ids':ids, 'report_type':report_type, 'window': self.window}, context=ctx,
+ warning=warning)
+ else:
+ common.message(_('No resource selected!'))
+ return False
+
+ def sig_open(self, widget, iter, path):
+ if not self.sig_action(widget, 'tree_but_open', warning=False):
+ if self.tree_res.view.row_expanded(iter):
+ self.tree_res.view.collapse_row(iter)
+ else:
+ self.tree_res.view.expand_row(iter, False)
+
+
+ def sig_remove(self, widget=None):
+ ids = self.ids_get()
+ if len(ids):
+ if common.sur(_('Are you sure you want\nto remove this record?')):
+ try:
+ rpc.session.rpc_exec_auth('/object', 'execute', self.model, 'unlink', ids)
+ self.sig_reload()
+ except xmlrpclib.Fault, err:
+ common.message(_('Error removing resource!'))
+
+ # TODO: improve with domain expr
+ def sig_chroot(self, widget=None):
+ ids = self.ids_get()
+ if len(ids) and self.domain:
+ id = ids[0]
+ datas = {'domain_field': self.domain[0][0], 'domain_value': id[0], 'res_id':id[0]}
+ obj = service.LocalService('gui.window')
+ obj.create(self.view, self.model, id[0], (self.domain[0],id[0]) )
+ else:
+ common.message(_('Unable to chroot: no tree resource selected'))
+
+ def sig_new(self, widget=None):
+ #datas = {'res_model':self.model, 'domain_field': self.domain[0], 'domain_value': self.id_get(), 'res_id':None}
+# domain = self.domain
+# if self.domain:
+# id = self.id_get()
+# if id:
+# domain=(domain[0],id)
+ obj = service.LocalService('gui.window')
+ obj.create(None, self.model, None, self.domain)
+
+ def sig_edit(self, widget=None):
+ id = False
+ ids = self.ids_get()
+ if ids:
+ id = ids[0]
+ elif self.tree_res.toolbar:
+ wid = self.glade.get_widget('tree_toolbar')
+ for w in wid.get_children():
+ if w.get_active():
+ id = w.get_data('id')
+ if id:
+ obj = service.LocalService('gui.window')
+ obj.create(None, self.model, id, self.domain)
+ else:
+ common.message(_('No resource selected!'))
+
+ def domain_id_get(self, tree=False):
+ filter = []
+ if self.domain and self.view.get('field_parent', False):
+ filter = self.domain
+ res = rpc.session.rpc_exec_auth('/object', 'execute', self.model, 'search', filter)
+ return res
+
+ def sig_printscreen(self, widget=None):
+ ids = self.tree_res.ids
+ pass
+
+ def sc_btn(self, widget):
+ main = service.LocalService('gui.main')
+ main.shortcut_edit(widget, self.model)
+
+ def sc_del(self, widget):
+ id = self.tree_sc.sel_id_get()
+ if id!=None:
+ sc_id = int(self.tree_sc.value_get(2))
+ rpc.session.rpc_exec_auth('/object', 'execute', 'ir.ui.view_sc', 'unlink', [sc_id])
+ self.tree_sc.update()
+
+ def sc_add(self, widget):
+ ids = self.tree_res.sel_ids_get()
+ if len(ids):
+ res = rpc.session.rpc_exec_auth('/object', 'execute', self.model, 'name_get', ids, rpc.session.context)
+ for (id,name) in res:
+ uid = rpc.session.uid
+ rpc.session.rpc_exec_auth('/object', 'execute', 'ir.ui.view_sc', 'create', {'resource':self.model, 'user_id':uid, 'res_id':id, 'name':name})
+ self.tree_sc.update()
+
+ def sc_go(self, widget=None, *args):
+ id = self.tree_sc.sel_id_get()
+ if id!=None:
+ self.sig_action(None, 'tree_but_open', id)
+
+ def ids_get(self):
+ res = self.tree_res.sel_ids_get()
+ return res
+
+ def id_get(self):
+ try:
+ if hasattr(self, 'search'):
+ return self.search[self.search_pos]
+ else:
+ return None
+ except IndexError:
+ return None
+
+ def destroy(self):
+ #TODO destroy gui.window.tree
+ pass
+
+ def sig_close(self, urgent=False):
+ return True
+
+ def sig_save_as(self, widget=None):
+ fields = []
+ tree_fields = copy.deepcopy(self.tree_res.fields)
+ win = win_export.win_export(self.model, self.tree_res.sel_ids_get(),
+ tree_fields, [], parent=self.window, context=self.context)
+ res = win.go()
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added directory 'bin/modules/gui/window/view_sel'
=== added file 'bin/modules/gui/window/view_sel/__init__.py'
--- bin/modules/gui/window/view_sel/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/view_sel/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added directory 'bin/modules/gui/window/view_tree'
=== added file 'bin/modules/gui/window/view_tree/__init__.py'
--- bin/modules/gui/window/view_tree/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/view_tree/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+from view_tree import *
+from view_tree_sc import *
+
+import parse
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/view_tree/parse.py'
--- bin/modules/gui/window/view_tree/parse.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/view_tree/parse.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+import gobject
+from xml.parsers import expat
+
+import gettext
+
+class parse(object):
+ def __init__(self, fields):
+ self.fields = fields
+ self.pixbufs = {}
+
+ def _psr_start(self, name, attrs):
+ if name == 'tree':
+ self.title = attrs.get('string',_('Tree'))
+ self.toolbar = bool(attrs.get('toolbar',False))
+ self.colors = {}
+ for color_spec in attrs.get('colors', '').split(';'):
+ if color_spec:
+ colour, test = color_spec.split(':')
+ self.colors.setdefault(colour,[])
+ self.colors[colour].append(test)
+ elif name == 'field':
+ if attrs.get('invisible', False):
+ self.invisible_fields.append(str(attrs['name']))
+ return True
+ type = self.fields[attrs['name']]['type']
+ field_name = attrs.get('string', self.fields[attrs['name']]['string'])
+ if type!='boolean':
+ column = gtk.TreeViewColumn(field_name)
+ if 'icon' in attrs:
+ render_pixbuf = gtk.CellRendererPixbuf()
+ column.pack_start(render_pixbuf, expand=False)
+ column.add_attribute(render_pixbuf, 'pixbuf', self.pos)
+ self.fields_order.append(str(attrs['icon']))
+ self.pixbufs[self.pos] = True
+ self.pos += 1
+ cell = gtk.CellRendererText()
+ cell.set_fixed_height_from_font(1)
+ if type=='float':
+ cell.set_property('xalign', 1.0)
+ column.pack_start(cell, expand=False)
+ column.add_attribute(cell, 'text', self.pos)
+ else:
+ cell = gtk.CellRendererToggle()
+ column = gtk.TreeViewColumn (field_name, cell, active=self.pos)
+ self.pos += 1
+ column.set_resizable(1)
+ self.fields_order.append(str(attrs['name']))
+ self.tree.append_column(column)
+ else:
+ import logging
+ log = logging.getLogger('view')
+ log.error('unknown tag: '+str(name))
+ del log
+ def _psr_end(self, name):
+ pass
+ def _psr_char(self, char):
+ pass
+ def parse(self, xml_data, tree):
+ cell = gtk.CellRendererText()
+ cell.set_fixed_height_from_font(1)
+ column = gtk.TreeViewColumn('ID', cell, text=0)
+ column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+ column.set_fixed_width(60)
+ column.set_visible(False)
+ tree.append_column(column)
+ self.tree = tree
+ self.pos = 1
+
+ self.fields_order = []
+ self.invisible_fields = []
+
+ psr = expat.ParserCreate()
+ psr.StartElementHandler = self._psr_start
+ psr.EndElementHandler = self._psr_end
+ psr.CharacterDataHandler = self._psr_char
+ psr.Parse(xml_data)
+ return self.pos
+
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/view_tree/view_tree.py'
--- bin/modules/gui/window/view_tree/view_tree.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/view_tree/view_tree.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,432 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+import gobject
+
+import time
+import datetime as DT
+import copy
+import math
+import locale
+import gettext
+
+from xml.parsers import expat
+
+import options
+import rpc
+import parse
+
+import tools
+from tools import user_locale_format, datetime_util
+
+DT_FORMAT = '%Y-%m-%d'
+DHM_FORMAT = '%Y-%m-%d %H:%M:%S'
+
+# BUG: ids = []
+#
+# Tree struct: [ id, values, children, children_id ]
+#
+# values: [...]
+# children: [ tree_struct ]
+# [] for no children
+# None for undevelopped (with children!)
+# assert: no children => []
+#
+# Node struct: [list of (pos, list) ]
+#
+class view_tree_model(gtk.GenericTreeModel, gtk.TreeSortable):
+ def __init__(self, ids, view, fields, fields_type, invisible_fields=[], context={}, pixbufs={}, treeview=None, colors='black'):
+ gtk.GenericTreeModel.__init__(self)
+ self.fields = fields
+ self.fields_type = fields_type
+ self.invisible_fields = invisible_fields
+ self.view = view
+ self.roots = ids
+ self.colors = colors
+ self.color_ids = {}
+ self.context = context
+ self.tree = self._node_process(self.roots)
+ self.pixbufs = pixbufs
+ self.treeview = treeview
+
+ def get_color(self,result):
+ color_ids = {}
+ for res in result:
+ color_ids[res['id']] = 'black'
+ res_lower = {}
+ for key, vals in res.iteritems():
+ if self.fields_type.get(key, False) and vals != 'False':
+ type = self.fields_type[key]['type']
+ if type == 'date':
+ res_lower[key] = datetime_util.local_to_server_timestamp(vals,
+ user_locale_format.get_date_format(), DT_FORMAT, tz_offset=False)
+ continue
+ elif type == 'datetime':
+ res_lower[key] = datetime_util.local_to_server_timestamp(vals,
+ user_locale_format.get_datetime_format(True), DT_FORMAT)
+ continue
+ if isinstance(vals, (str, unicode)):
+ res_lower[key]= vals.lower()
+ else:
+ res_lower[key] = vals
+ for color, expt in self.colors.iteritems():
+ val = False
+ for cond in expt:
+ if isinstance(cond, basestring):
+ val = tools.expr_eval(cond, res_lower)
+ if val:
+ color_ids[res_lower['id']] = color
+ break
+ if val:
+ break
+ return color_ids
+
+ def _read(self, ids, fields):
+ c = {}
+ c.update(rpc.session.context)
+ c.update(self.context)
+ if self.invisible_fields:
+ fields += self.invisible_fields
+ try:
+ res_ids = rpc.session.rpc_exec_auth_try('/object', 'execute',
+ self.view['model'], 'read', ids, fields, c)
+ except:
+ res_ids = []
+ for id in ids:
+ val = {'id': id}
+ for f in fields:
+ if self.fields_type[f]['type'] in ('one2many','many2many'):
+ val[f] = []
+ else:
+ val[f] = ''
+ res_ids.append(val)
+ for field in self.fields + self.invisible_fields:
+ for x in res_ids:
+ if self.fields_type[field]['type'] in ('date',):
+ display_format = user_locale_format.get_date_format()
+ if x[field]:
+ x[field] = datetime_util.server_to_local_timestamp(x[field],
+ DT_FORMAT, display_format, tz_offset=False)
+ else:
+ x[field] = str(x[field])
+ elif self.fields_type[field]['type'] in ('datetime',):
+ display_format = user_locale_format.get_datetime_format(True)
+ if x[field]:
+ x[field] = datetime_util.server_to_local_timestamp(x[field],
+ DHM_FORMAT, display_format)
+ else:
+ x[field] = str(x[field])
+ elif self.fields_type[field]['type'] in ('one2one','many2one'):
+ if x[field]:
+ x[field] = x[field][1]
+ elif self.fields_type[field]['type'] in ('selection'):
+ if x[field]:
+ x[field] = dict(self.fields_type[field]['selection']).get(x[field],'')
+ elif self.fields_type[field]['type'] in ('float',):
+ interger, digit = self.fields_type[field].get('digits', (16,2))
+ x[field] = user_locale_format.format('%.' + str(digit) + 'f', x[field] or 0.0)
+ elif self.fields_type[field]['type'] in ('integer',):
+ x[field] = int(user_locale_format.format('%d', int(x[field]) or 0))
+ elif self.fields_type[field]['type'] in ('float_time',):
+ val = datetime_util.float_time_convert(x[field])
+ if x[field] < 0:
+ val = '-' + val
+ x[field] = val
+ return res_ids
+
+ def _node_process(self, ids):
+ tree = []
+ if self.view.get('field_parent', False):
+ res = self._read(ids, self.fields+[self.view['field_parent']])
+ self.color_ids.update(self.get_color(res))
+ for x in res:
+ tree.append( [ x['id'], None, [], x[self.view['field_parent']] ] )
+ tree[-1][1] = [ x[ y ] for y in self.fields]
+ if len(x[self.view['field_parent']]):
+ tree[-1][2] = None
+ else:
+ res = self._read(ids, self.fields)
+ for x in res:
+ tree.append( [ x['id'], [ x[y] for y in self.fields], [] ])
+ return tree
+
+ def _node_expand(self, node):
+ node[2] = self._node_process(node[3])
+ del node[3]
+
+ def on_get_flags(self):
+ return 0
+
+ def on_get_n_columns(self):
+ return len(self.fields)+1
+
+ def on_get_column_type(self, index):
+ if index in self.pixbufs:
+ return gtk.gdk.Pixbuf
+ return fields_list_type.get(self.fields_type[self.fields[index-1]]['type'],
+ gobject.TYPE_STRING)
+
+ def on_get_path(self, node):
+ '''returns the tree path (a tuple of indices)'''
+ return tuple([ x[0] for x in node ])
+
+ def on_get_iter(self, path):
+ '''returns the node corresponding to the given path.'''
+ node = []
+ tree = self.tree
+ if self.tree==[]:
+ return None
+ for x in path:
+ node.append( (x, tree) )
+ if x <= (len(tree)-1):
+ tree = tree[x] and tree[x][2] or None
+ return node
+
+ def on_get_value(self, node, column):
+ (n, list) = node[-1]
+ if column:
+ value = list[n][1][column-1]
+ else:
+ value = list[n][0]
+
+ if value==None or (value==False and type(value)==bool):
+ res = ''
+ else:
+ res = value
+ if (column in self.pixbufs) and res:
+ if res.startswith('STOCK_'):
+ res = getattr(gtk, res)
+ return self.treeview.render_icon(stock_id=res, size=gtk.ICON_SIZE_MENU, detail=None)
+ return res
+
+ def on_iter_next(self, node):
+ '''returns the next node at this level of the tree'''
+ node = node[:]
+ (n, list) = node[-1]
+ if n<len(list)-1:
+ node[-1] = (n+1, list)
+ return node
+ return None
+
+ def on_iter_children(self, node):
+ '''returns the first child of this node'''
+ if node==None: # added
+ return [ (0, self.tree) ] # added
+ node = node[:]
+ (n, list) = node[-1]
+ if list[n][2]==None:
+ self._node_expand(list[n])
+ if list[n][2]==[]:
+ return None
+ node.append( (0, list[n][2]) )
+ return node
+
+ def on_iter_has_child(self, node):
+ '''returns true if this node has children'''
+ (n, list) = node[-1]
+ return list[n][2]!=[]
+
+ def on_iter_n_children(self, node):
+ '''returns the number of children of this node'''
+ if node==None: # changed
+ return len(self.tree) # changed
+ (n, list) = node[-1]
+ if list[n][2]==None:
+ self._node_expand(list[n])
+ return len(list[n][2])
+
+ def on_iter_nth_child(self, node, child):
+ '''returns the nth child of this node'''
+ if node==None:
+ if child<len(self.tree):
+ return [ (child, self.tree) ]
+ return None
+ else:
+ (n, list) = node[-1]
+ if list[n][2]==None:
+ self._node_expand(list[n])
+ if child<len(list[n][2]):
+ node = node[:]
+ node.append( (child, list[n][2]) )
+ return node
+ return None
+
+ def on_iter_parent(self, node):
+ '''returns the parent of this node'''
+ if node==None:
+ return None
+ return node[:-1]
+
+ def cus_refresh(self):
+ tree = self.tree
+ tree[0][2] = None
+
+ def _cus_row_find(self, ids_res):
+ tree = self.tree
+ try:
+ ids = ids_res[:]
+ while len(ids)>0:
+ if ids[-1] in self.roots:
+ ids.pop()
+ break
+ ids.pop()
+ path = []
+ while ids!=[]:
+ path.append(0)
+ val = ids.pop()
+ i = iter(tree)
+ while True:
+ node = i.next()
+ if node[0]==val:
+ break
+ path[-1]+=1
+ if (node[2]==None) and (ids!=[]):
+ return None
+ tree = node[2]
+ return (tuple(path), node)
+ except:
+ return None
+
+class view_tree(object):
+ def __init__(self, view_info, ids, res_id=None, sel_multi=False, context={}):
+ self.view = gtk.TreeView()
+ self.view.set_headers_visible(True)
+ self.context = {}
+ self.context.update(rpc.session.context)
+ self.context.update(context)
+ self.fields = rpc.session.rpc_exec_auth('/object', 'execute', view_info['model'], 'fields_get', False, self.context)
+ p = parse.parse(self.fields)
+ p.parse(view_info['arch'], self.view)
+ self.toolbar = p.toolbar
+ self.pixbufs = p.pixbufs
+ self.colors = p.colors
+ self.name = p.title
+ self.sel_multi = sel_multi
+
+ if sel_multi:
+ self.view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+ else:
+ self.view.get_selection().set_mode(gtk.SELECTION_SINGLE)
+ self.view.set_expander_column(self.view.get_column(1))
+ self.view.set_enable_search(False)
+ self.view.get_column(0).set_visible(False)
+
+ self.ids=ids
+ self.view_info = view_info
+ self.fields_order = p.fields_order
+ self.invisible_fields = p.invisible_fields
+ self.model = None
+ self.reload()
+
+ self.view.show_all()
+ self.search=[]
+ self.next=0
+
+ def reload(self):
+ del self.model
+ self.context.update(rpc.session.context)
+ self.model = view_tree_model(self.ids, self.view_info, self.fields_order, self.fields, self.invisible_fields, context=self.context, pixbufs=self.pixbufs, treeview=self.view , colors=self.colors)
+ self.view.set_model(self.model)
+ local ={}
+ def render_column(column, cell, model, iter):
+ if not isinstance(cell, gtk.CellRendererPixbuf):
+ list = zip(self.model.color_ids.keys(),self.model.color_ids.values())
+ color_by ={}
+ for k,v in list:
+ color_by.setdefault(v,[]).append(str(k))
+ for k ,v in color_by.items():
+ if model.get_value(iter,0) in v and not isinstance(cell,gtk.CellRendererToggle):
+ cell.set_property('foreground',k)
+
+ for column in self.view.get_columns():
+ renderers = column.get_cell_renderers()
+ for ren in renderers:
+ column.set_cell_data_func(ren, render_column)
+
+ def widget_get(self):
+ return self.view
+
+ def sel_ids_get(self):
+ sel = self.view.get_selection()
+ if not sel:
+ return None
+ sel = sel.get_selected_rows()
+ if not sel:
+ return []
+ (model, iters) = sel
+ return map(lambda x: int(model.get_value(model.get_iter(x), 0)), iters)
+
+ def sel_id_get(self):
+ sel = self.view.get_selection().get_selected()
+ if sel==None:
+ return None
+ (model, iter) = sel
+ if not iter:
+ return None
+ res = model.get_value(iter, 0)
+ if res!=None:
+ return int(res)
+ return res
+
+ def value_get(self, col):
+ sel = self.view.get_selection().get_selected_rows()
+ if sel==None:
+ return None
+ (model, iter) = sel
+ if not iter:
+ return None
+ return model.get_value(iter, col)
+
+ def go(self, id):
+ return
+ ids = com_rpc.xrpc.exec_auth('res_path_get', id, self.root)
+ if not len(ids):
+ raise Exception, 'IdNotFound'
+ self.view.collapse_all()
+ model = self.view.get_model()
+ iter = model.get_iter_root()
+ while len(ids)>0:
+ if ids[-1]==model.root:
+ break
+ ids.pop()
+ if ids!=[]:
+ ids.pop()
+ while ids!=[]:
+ val = ids.pop()
+ while True:
+ if int(model.get_value(iter,0)) == val:
+ self.view.expand_row( model.get_path(iter), False)
+ break
+ if not model.iter_next(iter):
+ raise Exception, 'IdNotFound'
+ if ids!=[]:
+ iter = model.iter_children(iter)
+ self.view.get_selection().select_iter(iter)
+
+fields_list_type = {
+ 'boolean': gobject.TYPE_BOOLEAN,
+# 'integer': gobject.TYPE_INT,
+}
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/view_tree/view_tree_sc.py'
--- bin/modules/gui/window/view_tree/view_tree_sc.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/view_tree/view_tree_sc.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import rpc
+import gobject
+import gtk
+
+import gettext
+import service
+
+class view_tree_sc(object):
+ ( COLUMN_RES_ID, COLUMN_NAME, COLUMN_ID ) = range(3)
+ def __init__(self, tree, model):
+ self.last_iter = None
+ self.model = model
+ self.tree = tree
+ self.tree.connect( 'key-press-event', self.on_key_press_event )
+ self.tree.get_selection().set_mode('single')
+
+ self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, [("shortcuts", 0, 0)], gtk.gdk.ACTION_COPY)
+ self.tree.enable_model_drag_dest([("shortcuts", 0, 0)], gtk.gdk.ACTION_COPY)
+ self.tree.connect("drag_data_received", self.on_drag_data_received)
+
+ column = gtk.TreeViewColumn (_('ID'), gtk.CellRendererText(), text=0)
+ self.tree.append_column(column)
+ column.set_visible(False)
+ cell = gtk.CellRendererText()
+ cell.connect( 'edited', self.on_cell_edited )
+
+ column = gtk.TreeViewColumn (_('Description'), cell, text=1)
+ self.tree.append_column(column)
+# self.update()
+
+ def update(self):
+ store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
+ uid = rpc.session.uid
+ sc = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.ui.view_sc', 'get_sc', uid, self.model, rpc.session.context) or []
+ for s in sc:
+ num = store.append()
+ store.set(num,
+ self.COLUMN_RES_ID, s['res_id'],
+ self.COLUMN_NAME, s['name'],
+ self.COLUMN_ID, s['id']
+ )
+ self.last_iter = num
+
+ self.tree.set_model(store)
+ if self.model == 'ir.ui.menu':
+ service.LocalService('gui.main').shortcut_set(sc)
+
+ def remove(self, id):
+ self.update()
+
+ def add(self, id):
+ self.update()
+
+ def value_get(self, col):
+ sel = self.tree.get_selection().get_selected()
+ if not sel:
+ return None
+ (model, iter) = sel
+ if not iter:
+ return None
+ return model.get_value(iter, col)
+
+ def sel_id_get(self):
+ res = self.value_get(0)
+ res = eval(str(res))
+ if res:
+ return int(res[0]) if isinstance(res,(list,tuple)) else res
+ return None
+
+ def serv_update(self, ids, action):
+ if (action==2):
+ self.update()
+
+ def on_cell_edited(self, cell, path_string, new_text):
+ model = self.tree.get_model()
+ iter = model.get_iter_from_string(path_string)
+ old_text = model.get_value( iter, self.COLUMN_NAME )
+ if old_text <> new_text:
+ res_id = int( model.get_value( iter, self.COLUMN_ID ) )
+ rpc.session.rpc_exec_auth('/object', 'execute', 'ir.ui.view_sc', 'write', res_id, { 'name' : new_text }, rpc.session.context )
+ model.set(iter, self.COLUMN_NAME, new_text)
+
+ cell.set_property( 'editable', False )
+
+ def on_key_press_event( self, widget, event ):
+ if event.keyval == gtk.keysyms.F2:
+ column = self.tree.get_column( self.COLUMN_NAME )
+ cell = column.get_cell_renderers()[0]
+ cell.set_property( 'editable', True )
+
+ selected_row = widget.get_selection().get_selected()
+ if selected_row and selected_row[1]:
+ (model, iter) = selected_row
+ path = model.get_path( iter )
+ self.tree.set_cursor_on_cell( path, column, cell, True )
+
+ def check_sanity(self, model, iter_to_copy, target_iter):
+ path_of_iter_to_copy = model.get_path(iter_to_copy)
+ path_of_target_iter = model.get_path(target_iter)
+ return not ( path_of_target_iter[0:len(path_of_iter_to_copy)] == path_of_iter_to_copy )
+
+ def iter_copy(self, treeview, model, iter_to_copy, target_iter, pos):
+ data_column_0 = model.get_value(iter_to_copy, self.COLUMN_RES_ID)
+ data_column_1 = model.get_value(iter_to_copy, self.COLUMN_NAME)
+ data_column_2 = model.get_value(iter_to_copy, self.COLUMN_ID)
+ if (pos == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE) or (pos == gtk.TREE_VIEW_DROP_INTO_OR_AFTER):
+ new_iter = model.prepend(None)
+ elif pos == gtk.TREE_VIEW_DROP_BEFORE:
+ new_iter = model.insert_before(target_iter, None)
+ elif pos == gtk.TREE_VIEW_DROP_AFTER:
+ new_iter = model.insert_after(target_iter, None)
+ else:
+ new_iter = model.append()
+ model.set_value(new_iter, self.COLUMN_RES_ID, data_column_0)
+ model.set_value(new_iter, self.COLUMN_NAME, data_column_1)
+ model.set_value(new_iter, self.COLUMN_ID, data_column_2)
+
+ def on_drag_data_received(self, treeview, drag_context, x, y, selection, info, eventtime):
+ drop_info = treeview.get_dest_row_at_pos(x,y)
+ modified = False
+ if drop_info:
+ path, pos = drop_info
+ model, iter_to_copy = treeview.get_selection().get_selected()
+ target_iter = model.get_iter(path)
+ if target_iter <> iter_to_copy:
+ if self.check_sanity(model, iter_to_copy, target_iter):
+ self.iter_copy(treeview, model, iter_to_copy, target_iter, pos)
+ drag_context.finish(True, True, eventtime)
+ treeview.expand_all()
+ modified = True
+ else:
+ drag_context.finish(False, False, eventtime)
+ else:
+ drag_context.finish(False, False, eventtime)
+ else:
+ model, iter_to_copy = treeview.get_selection().get_selected()
+ if iter_to_copy <> self.last_iter:
+ self.iter_copy( treeview, model, iter_to_copy, None, None )
+ drag_context.finish(True, True, eventtime)
+ modified = True
+ else:
+ drag_context.finish(False, False, eventtime)
+
+ if modified == True:
+ model = treeview.get_model()
+ iter = model.get_iter_first()
+ counter = 0
+ while iter:
+ res_id = int(model.get_value( iter, self.COLUMN_ID ))
+ rpc.session.rpc_exec_auth('/object', 'execute', 'ir.ui.view_sc', 'write', res_id, { 'sequence' : counter }, rpc.session.context )
+ counter = counter + 1
+ iter = model.iter_next( iter )
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/win_export.py'
--- bin/modules/gui/window/win_export.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/win_export.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,367 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+from gtk import glade
+import gobject
+import gettext
+import common
+
+import rpc
+
+import service
+import types
+import os
+
+def export_csv(fname, fields, result, write_title=False):
+ import csv
+ try:
+ fp = file(fname, 'wb+')
+ writer = csv.writer(fp, quoting=csv.QUOTE_ALL)
+ if write_title:
+ writer.writerow(fields)
+ for data in result:
+ row = []
+ for d in data:
+ if type(d)==types.StringType:
+ row.append(d.replace('\n',' ').replace('\t',' '))
+ else:
+ row.append(d or '')
+ writer.writerow(row)
+ fp.close()
+ common.message(str(len(result))+_(' record(s) saved !'))
+ return True
+ except IOError, (errno, strerror):
+ common.message(_("Operation failed !\nI/O error")+"(%s)" % (errno,))
+ return False
+
+def open_excel(fields, result):
+ if os.name == 'nt':
+ try:
+ if len(fields) > 0:
+ from win32com.client import Dispatch
+ import pywintypes
+ xlApp = Dispatch("Excel.Application")
+ xlApp.Workbooks.Add()
+ for col in range(len(fields)):
+ xlApp.ActiveSheet.Cells(1,col+1).Value = fields[col]
+ sht = xlApp.ActiveSheet
+ for a in result:
+ for b in range(len(a)):
+ if type(a[b]) == type(''):
+ a[b]=a[b].decode('utf-8','replace')
+ elif type(a[b]) == type([]):
+ if len(a[b])==2:
+ a[b] = a[b][1].decode('utf-8','replace')
+ else:
+ a[b] = ''
+ sht.Range(sht.Cells(2, 1), sht.Cells(len(result)+1, len(fields))).Value = result
+ xlApp.Visible = 1
+ except:
+ common.error(_('Error Opening Excel !'),'')
+ else:
+# common.message(_("Function only available for MS Office !\nSorry, OOo users :("))
+ try:
+ import subprocess
+ import time
+ retcode = subprocess.call(["soffice", "-accept=socket,host=localhost,port=2002;urp;", "-nodefault"], shell=False)
+ from oootools import OOoTools
+ for i in range(10):
+ ooo = OOoTools('localhost', 2002)
+ if ooo and ooo.desktop:
+ break
+ time.sleep(1)
+ doc = ooo.desktop.loadComponentFromURL("private:factory/scalc",'_blank',0,())
+ sheet = doc.CurrentController.ActiveSheet
+ for col in range(len(fields)):
+ cell = sheet.getCellByPosition(col, 0)
+ cell.String = fields[col]
+ if len(fields) > 0:
+ cellrange = sheet.getCellRangeByPosition(0, 1, len(fields) - 1, len(result))
+ tresult = []
+ for i in range(len(result)):
+ tresult.append(tuple(result[i]))
+ tresult = tuple(tresult)
+ cellrange.setDataArray(tresult)
+ except:
+ common.error(_('Error Opening Excel !'),'')
+
+def datas_read(ids, model, fields, fields_view, prefix='', context=None):
+ ctx = context.copy()
+ ctx.update(rpc.session.context)
+ datas = rpc.session.rpc_exec_auth('/object', 'execute', model, 'export_data', ids, fields, ctx)
+ return datas
+
+class win_export(object):
+ def __init__(self, model, ids, fields, preload = [], parent=None, context=None):
+ self.glade = glade.XML(common.terp_path("openerp.glade"), 'win_save_as',
+ gettext.textdomain())
+ self.win = self.glade.get_widget('win_save_as')
+ self.ids = ids
+ self.model = model
+ self.fields_data = {}
+ self.fields = {}
+ if context is None:
+ context = {}
+ self.context = context
+
+ if parent is None:
+ parent = service.LocalService('gui.main').window
+ self.win.set_transient_for(parent)
+ self.win.set_icon(common.OPENERP_ICON)
+ self.parent = parent
+
+ self.view1 = gtk.TreeView()
+ self.view1.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+ self.glade.get_widget('exp_vp1').add(self.view1)
+ self.view2 = gtk.TreeView()
+ self.view2.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+ self.glade.get_widget('exp_vp2').add(self.view2)
+ self.view1.set_headers_visible(False)
+ self.view2.set_headers_visible(False)
+
+ cell = gtk.CellRendererText()
+ column = gtk.TreeViewColumn(_('Field Name'), cell, text=0, background=2)
+ self.view1.append_column(column)
+
+ cell = gtk.CellRendererText()
+ column = gtk.TreeViewColumn(_('Field Name'), cell, text=0)
+ self.view2.append_column(column)
+
+ #for f in preload:
+ # self.model2.set(self.model2.append(), 0, f[1], 1, f[0])
+ self.wid_import_compatible = self.glade.get_widget('import_compatible')
+
+ self.model1 = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
+ self.model2 = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+
+ self.fields_original = fields
+ self.model_populate(self.fields_original)
+
+ self.view1.set_model(self.model1)
+ self.view2.set_model(self.model2)
+ self.view1.show_all()
+ self.view2.show_all()
+
+
+ self.wid_action = self.glade.get_widget('win_saveas_combo')
+ self.wid_write_field_names = self.glade.get_widget('add_field_names_cb')
+ action = self.wid_action.set_active(os.name!='nt')
+
+ if os.name != 'nt':
+ self.wid_action.remove_text(0)
+ else:
+ try:
+ from win32com.client import Dispatch
+ import pywintypes
+ xlApp = Dispatch("Excel.Application")
+ except Exception,e:
+ if isinstance(e, pywintypes.com_error):
+ action = self.wid_action.set_active(isinstance(e, pywintypes.com_error))
+ self.wid_action.remove_text(0)
+ else:
+ pass
+
+ self.glade.signal_connect('on_but_unselect_all_clicked', self.sig_unsel_all)
+ self.glade.signal_connect('on_but_select_all_clicked', self.sig_sel_all)
+ self.glade.signal_connect('on_but_select_clicked', self.sig_sel)
+ self.glade.signal_connect('on_but_unselect_clicked', self.sig_unsel)
+ self.glade.signal_connect('on_but_predefined_clicked', self.add_predef)
+ self.glade.signal_connect('on_but_delpredefined_clicked', self.del_export_list_btn)
+ self.glade.signal_connect('on_import_toggled', self.import_toggled)
+
+ # Creating the predefined export view
+ self.pref_export = gtk.TreeView()
+ self.pref_export.append_column(gtk.TreeViewColumn('Export name', gtk.CellRendererText(), text=1))
+ self.pref_export.append_column(gtk.TreeViewColumn('Exported fields', gtk.CellRendererText(), text=2))
+ self.glade.get_widget('predefined_exports').add(self.pref_export)
+
+ self.pref_export.connect("row-activated", self.sel_predef)
+ self.pref_export.connect('key_press_event', self.del_export_list_key)
+
+ # Fill the predefined export tree view and show everything
+ self.fill_predefwin()
+ self.pref_export.show_all()
+
+ def model_populate(self, fields, prefix_node='', prefix=None, prefix_value='', level=2):
+ import_comp = self.wid_import_compatible.get_active()
+ fields = fields.copy()
+ fields.update({'id':{'string':_('ID')}})
+ fields.update({'.id':{'string':_('Database ID')}})
+
+ fields_order = fields.keys()
+ fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', '')))
+ for field in fields_order:
+ if import_comp and fields[field].get('readonly', False):
+ ok = False
+ for sl in fields[field].get('states', {}).values():
+ for s in sl:
+ ok = ok or (s==('readonly',False))
+ if not ok: continue
+ self.fields_data[prefix_node+field] = fields[field]
+ if prefix_node:
+ self.fields_data[prefix_node + field]['string'] = '%s%s' % (prefix_value, self.fields_data[prefix_node + field]['string'])
+ st_name = fields[field]['string'] or field
+ node = self.model1.insert(prefix, 0, [st_name, prefix_node+field, (fields[field].get('required', False) and '#ddddff') or 'white'])
+ self.fields[prefix_node+field] = (st_name, fields[field].get('relation', False))
+ if fields[field].get('relation', False) and level>0:
+ if (fields[field]['type'] in ['many2many']) and import_comp:
+ pass
+ else:
+ if (not import_comp) or (fields[field]['type'] in ['one2many']):
+ fields2 = rpc.session.rpc_exec_auth('/object', 'execute', fields[field]['relation'], 'fields_get', False, rpc.session.context)
+ self.model_populate(fields2, prefix_node+field+'/', node, st_name+'/', level-1)
+ else:
+ self.model_populate({}, prefix_node+field+'/', node, st_name+'/', level-1)
+
+ def del_export_list_key(self,widget, event, *args):
+ if event.keyval==gtk.keysyms.Delete:
+ self.del_selected_export_list()
+
+ def del_export_list_btn(self, widget=None):
+ self.del_selected_export_list()
+
+ def del_selected_export_list(self):
+ store, paths = self.pref_export.get_selection().get_selected_rows()
+ for p in paths:
+ export_fields= store.get_value(store.__getitem__(p[0]).iter,0)
+ export_name= store.get_value(store.__getitem__(p[0]).iter,1)
+
+ ir_export = rpc.RPCProxy('ir.exports')
+ ir_export_line = rpc.RPCProxy('ir.exports.line')
+
+ export_ids=ir_export.search([('name','=',export_name)])
+
+ for id in export_ids:
+ fields=[]
+ line_ids=ir_export_line.search([('export_id','=',id)])
+
+ obj_line=ir_export_line.read(line_ids)
+ for i in range(0,len(obj_line)):
+ fields.append(obj_line[i]['name'])
+
+ if fields==export_fields:
+ ir_export.unlink(id)
+ ir_export_line.unlink(line_ids)
+ store.remove(store.get_iter(p))
+ break
+
+ def sig_sel_all(self, widget=None):
+ self.model2.clear()
+ for field, relation in self.fields.keys():
+ if not relation:
+ self.model2.set(self.model2.append(), 0, self.fields[field], 1, field)
+
+ def sig_sel(self, widget=None):
+ sel = self.view1.get_selection()
+ sel.selected_foreach(self._sig_sel_add)
+
+ def _sig_sel_add(self, store, path, iter):
+ name, relation = self.fields[store.get_value(iter,1)]
+ #if relation:
+ # return
+ num = self.model2.append()
+ self.model2.set(num, 0, store.get_value(iter,0), 1, store.get_value(iter,1))
+
+ def sig_unsel(self, widget=None):
+ store, paths = self.view2.get_selection().get_selected_rows()
+ for p in paths:
+ store.remove(store.get_iter(p))
+
+ def import_toggled(self, widget=None):
+ self.model1.clear()
+ self.model2.clear()
+ self.model_populate(self.fields_original)
+
+ def sig_unsel_all(self, widget=None):
+ self.model2.clear()
+
+ def fill_predefwin(self):
+ self.predef_model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_STRING)
+ ir_export = rpc.RPCProxy('ir.exports')
+ ir_export_line = rpc.RPCProxy('ir.exports.line')
+ export_ids = ir_export.search([('resource', '=', self.model)])
+ for export in ir_export.read(export_ids):
+ fields = ir_export_line.read(export['export_fields'])
+ try:
+ self.predef_model.append(([f['name'] for f in fields], export['name'], ', '.join([self.fields_data[f['name']]['string'] for f in fields])))
+ except:
+ pass
+ self.pref_export.set_model(self.predef_model)
+
+ def add_predef(self, button):
+ name = common.ask(_('What is the name of this export ?'))
+ if not name:
+ return
+ ir_export = rpc.RPCProxy('ir.exports')
+ iter = self.model2.get_iter_root()
+ fields = []
+ while iter:
+ field_name = self.model2.get_value(iter, 1)
+ fields.append(field_name)
+ iter = self.model2.iter_next(iter)
+ ir_export.create({'name' : name, 'resource' : self.model, 'export_fields' : [(0, 0, {'name' : f}) for f in fields]})
+ self.predef_model.append((fields, name, ','.join([self.fields_data[f]['string'] for f in fields])))
+
+ def sel_predef(self, treeview, path, column):
+ self.model2.clear()
+ for field in self.predef_model[path[0]][0]:
+ self.model2.append((self.fields_data[field]['string'], field))
+
+ def go(self):
+ button = self.win.run()
+ if button==gtk.RESPONSE_OK:
+ fields = []
+ fields2 = []
+ iter = self.model2.get_iter_root()
+ while iter:
+ fields.append(self.model2.get_value(iter, 1).replace('/.id','.id'))
+ fields2.append(self.model2.get_value(iter, 0))
+ iter = self.model2.iter_next(iter)
+ action = self.wid_action.get_active()
+ self.parent.present()
+ self.win.destroy()
+ import_comp = self.wid_import_compatible.get_active()
+ result = datas_read(self.ids, self.model, fields, self.fields_data, context=self.context)
+ if result.get('warning',False):
+ common.message_box(_('Exportation Error !'), unicode(result.get('warning',False)))
+ return False
+ result = result.get('datas',[])
+ if import_comp:
+ fields2 = fields
+ if self.wid_action.get_active_text() == "Open in Excel":
+ open_excel(fields2, result)
+ else:
+ fname = common.file_selection(_('Save As...'),
+ parent=self.parent, action=gtk.FILE_CHOOSER_ACTION_SAVE)
+ if fname:
+ export_csv(fname, fields2, result, self.wid_write_field_names.get_active())
+ return True
+ else:
+ self.parent.present()
+ self.win.destroy()
+ return False
+
+
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/win_extension.py'
--- bin/modules/gui/window/win_extension.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/win_extension.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import os
+import gettext
+import gobject
+import gtk
+import gtk.glade
+
+import service
+import common
+import options
+
+class win_extension(object):
+ def __init__(self, parent=None):
+ glade = gtk.glade.XML(common.terp_path('openerp.glade'), 'win_extension', gettext.textdomain())
+ self.win = glade.get_widget('win_extension')
+ self.win.set_transient_for(parent)
+ self.win.set_icon(common.OPENERP_ICON)
+ model = gtk.ListStore( str, str, str )
+
+ self.treeview = glade.get_widget('treeview_extension')
+ self.treeview.set_model(model)
+
+ for index, text in enumerate([_('Extension'), _('Application'), _('Print Processor')]):
+ renderer = gtk.CellRendererText()
+ renderer.set_property( 'editable', True )
+ renderer.connect( 'edited', self._on_cell_renderer_edited )
+ column = gtk.TreeViewColumn( text, renderer, text=index )
+ column.set_resizable( True )
+ self.treeview.append_column( column )
+
+ dict = {
+ 'on_button5_clicked' : self._sig_add,
+ 'on_button6_clicked' : self._sig_remove,
+ }
+
+ for signal in dict:
+ glade.signal_connect( signal, dict[signal] )
+
+ self.load_from_file()
+
+ def load_from_file(self):
+ try:
+ filetypes = eval(options.options['extensions.filetype'][:])
+ for ext, (app, app_print) in filetypes.items():
+ self.add_to_model( ext, app, app_print )
+ except Exception, ex:
+ pass
+
+ def save_to_file(self):
+ value = dict( [ [ ext, ( app, printable ) ] for ext, app, printable in self.treeview.get_model() ] )
+ options.options['extensions.filetype'] = str(value)
+ options.options.save()
+
+ def add_to_model( self, extension, application, printable ):
+ model = self.treeview.get_model()
+ iter = model.append()
+ model.set( iter, 0, extension, 1, application, 2, printable )
+ return model.get_path( iter )
+
+ def _sig_add( self, button, data = None ):
+ path = self.add_to_model( '', '', '' )
+ self.treeview.set_cursor( path, self.treeview.get_column(0), True )
+
+ def _sig_remove( self, button, data = None ):
+ selection = self.treeview.get_selection()
+ model, iter = selection.get_selected()
+
+ if iter:
+ path = model.get_path(iter)
+ model.remove( iter )
+ selection.select_path( path )
+
+ if not selection.path_is_selected( path ):
+ row = path[0]-1
+ if row >= 0:
+ selection.select_path((row,))
+
+ def _on_cell_renderer_edited( self, cell, path_string, new_text ):
+ model = self.treeview.get_model()
+ iter = model.get_iter_from_string(path_string)
+
+ (path,column) = self.treeview.get_cursor()
+
+ column_id = self.treeview.get_columns().index(column)
+
+ old_text = model.get_value( iter, column_id )
+
+ if column_id == 0:
+ old_text = old_text.lower()
+ new_text = new_text.lower()
+ if old_text <> new_text:
+ if column_id == 0:
+ if new_text in [ ext for ext, app, app_print in model ]:
+ common.warning(_('This extension is already defined'), _('Extension Manager'), parent=self.win)
+ return
+ else:
+ model.set(iter, column_id, new_text)
+ else:
+ model.set(iter, column_id, new_text)
+
+ def run(self):
+ res = self.win.run()
+ if res == -5:
+ self.save_to_file()
+ self.win.destroy()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added file 'bin/modules/gui/window/win_import.py'
--- bin/modules/gui/window/win_import.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/win_import.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,268 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+from gtk import glade
+import gobject
+import gettext
+import common
+
+import rpc
+
+import csv
+import cStringIO
+
+import options
+import service
+
+#
+# TODO: make it works with references
+#
+def import_csv(csv_data, f, model, fields, context=None, parent=None):
+ fname = csv_data['fname']
+ content = file(fname,'rb').read()
+ input=cStringIO.StringIO(content)
+ input.seek(0)
+ data = list(csv.reader(input, quotechar=csv_data['del'] or '"', delimiter=csv_data['sep']))[int(csv_data['skip']):]
+ datas = []
+ for line in data:
+ if not line:
+ continue
+ datas.append(map(lambda x:x.decode(csv_data['combo']).encode('utf-8'), line))
+ if not datas:
+ common.warning(_('The file is empty !'), _('Importation !'), parent=parent)
+ return False
+ try:
+ res = rpc.session.rpc_exec_auth('/object', 'execute', model, 'import_data', f, datas, 'init', '', False, context)
+ except Exception, e:
+ common.warning(str(e), _('XML-RPC error !'), parent=parent)
+ return False
+ result = res[0]
+ if result>=0:
+ if result == 1:
+ common.message(_('Imported one object !'))
+ else:
+ common.message(_('Imported %d objects !') % (result,))
+ else:
+ d = ''
+ for key,val in res[1].items():
+ d+= ('\t%s: %s\n' % (str(key),str(val)))
+ error = _(u'Error trying to import this record:\n%s\nError Message:\n%s\n\n%s') % (d,res[2],res[3])
+ common.message_box(_('Importation Error !'), unicode(error))
+ return True
+
+class win_import(object):
+ def __init__(self, model, fields, preload = [], parent=None, local_context=None):
+ self.glade = glade.XML(common.terp_path("openerp.glade"), 'win_import',
+ gettext.textdomain())
+ self.glade.get_widget('import_csv_combo').set_active(0)
+ self.win = self.glade.get_widget('win_import')
+ self.model = model
+ self.fields_data = {}
+ self.invert = False
+ self.context = local_context or {}
+ if parent is None:
+ parent = service.LocalService('gui.main').window
+ self.win.set_transient_for(parent)
+ self.win.set_icon(common.OPENERP_ICON)
+ self.parent = parent
+ self.autodetect_btn = self.glade.get_widget('button_autodetect')
+ self.filechooser = self.glade.get_widget('import_csv_file')
+ self.filechooser.set_current_folder(
+ options.options['client.default_path'])
+ self.filechooser.connect('selection-changed',self.file_changed)
+ self.view1 = gtk.TreeView()
+ self.view1.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+ self.glade.get_widget('import_vp_left').add(self.view1)
+ self.view2 = gtk.TreeView()
+ self.view2.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+ self.glade.get_widget('import_vp_right').add(self.view2)
+ self.view1.set_headers_visible(False)
+ self.view2.set_headers_visible(False)
+
+ cell = gtk.CellRendererText()
+ column = gtk.TreeViewColumn(_('Field name'), cell, text=0, background=2)
+ self.view1.append_column(column)
+
+ cell = gtk.CellRendererText()
+ column = gtk.TreeViewColumn(_('Field name'), cell, text=0)
+ self.view2.append_column(column)
+
+ self.model1 = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
+ self.model2 = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+
+ for f in preload:
+ self.model2.set(self.model2.append(), 0, f[1], 1, f[0])
+
+ self.fields = {}
+ self.fields_invert = {}
+ def model_populate(fields, prefix_node='', prefix=None, prefix_value='', level=2):
+ fields_order = fields.keys()
+ fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', '')))
+ for field in fields_order:
+ if (fields[field].get('type','') not in ('reference',)) \
+ and (not fields[field].get('readonly', False) \
+ or not dict(fields[field].get('states', {}).get(
+ 'draft', [('readonly', True)])).get('readonly', True)\
+ or not dict(fields[field].get('states', {}).get(
+ field, [('readonly', True)])).get('readonly', True)):
+ self.fields_data[prefix_node+field] = fields[field]
+ st_name = prefix_value+fields[field]['string'] or field
+ node = self.model1.insert(prefix, 0, [st_name, prefix_node+field,
+ (fields[field].get('required', False) and '#ddddff') or 'white'])
+ self.fields[prefix_node+field] = st_name
+ self.fields_invert[st_name] = prefix_node+field
+ if fields[field].get('type','') == 'one2many' and level>0:
+ fields2 = rpc.session.rpc_exec_auth('/object', 'execute', fields[field]['relation'], 'fields_get', False, rpc.session.context)
+ model_populate(fields2, prefix_node+field+'/', node, st_name+'/', level-1)
+ if fields[field].get('relation',False) and level>0:
+ #self.fields[field+':id'] = fields[field]['string']
+ #self.fields_invert[fields[field]['string']] = field+':id'
+ model_populate({'/id':{'string':'ID'},'.id':{'string':_('Database ID')}}, \
+ prefix_node+field, node, st_name+'/', level-1)
+ fields.update({'id':{'string':'ID'},'.id':{'string':_('Database ID')}})
+ model_populate(fields)
+
+ #for f in fields:
+ # self.model1.set(self.model1.append(), 1, f, 0, fields[f].get('string', 'unknown'))
+
+ self.view1.set_model(self.model1)
+ self.view2.set_model(self.model2)
+ self.view1.show_all()
+ self.view2.show_all()
+
+ self.glade.signal_connect('on_but_unselect_all_clicked', self.sig_unsel_all)
+ self.glade.signal_connect('on_but_select_all_clicked', self.sig_sel_all)
+ self.glade.signal_connect('on_but_select_clicked', self.sig_sel)
+ self.glade.signal_connect('on_but_unselect_clicked', self.sig_unsel)
+ self.glade.signal_connect('on_but_autodetect_clicked', self.sig_autodetect)
+
+ def file_changed(self, widget=None):
+ fname= self.filechooser.get_filename()
+ if not fname:
+ self.autodetect_btn.set_sensitive(False)
+ else:
+ self.autodetect_btn.set_sensitive(True)
+
+ def sig_autodetect(self, widget=None):
+ fname= self.filechooser.get_filename()
+ if not fname:
+ common.message('You must select an import file first !')
+ return True
+ csvsep= self.glade.get_widget('import_csv_sep').get_text()
+ csvdel= self.glade.get_widget('import_csv_del').get_text()
+ csvcode= self.glade.get_widget('import_csv_combo').get_active_text() or 'UTF-8'
+
+ self.glade.get_widget('import_csv_skip').set_value(1)
+ try:
+ data = csv.reader(file(fname), quotechar=csvdel or '"', delimiter=csvsep)
+ except:
+ common.warning(_('Error opening .CSV file'), _('Input Error.'), parent=self.win)
+ return True
+ self.sig_unsel_all()
+ word=''
+ try:
+ for line in data:
+ for word in line:
+ word = word.decode(csvcode)
+ if not csvcode.lower() == 'utf-8':
+ word = word.encode('utf-8')
+ if (word in self.fields):
+ num = self.model2.append()
+ self.model2.set(num, 0, self.fields[word], 1, word)
+ elif word in self.fields_invert:
+ self.invert = True
+ num = self.model2.append()
+ self.model2.set(num, 0, word, 1, word)
+ else:
+ raise Exception(_("You cannot import this field %s, because we cannot auto-detect it"))
+ break
+ except:
+ common.warning(_('Error processing your first line of the file.\nField %s is unknown !') % (word,), _('Import Error.'), parent=self.win)
+ return True
+
+ def sig_sel_all(self, widget=None):
+ self.model2.clear()
+ for field in self.fields.keys():
+ self.model2.set(self.model2.append(), 0, self.fields[field], 1, field)
+
+ def sig_sel(self, widget=None):
+ sel = self.view1.get_selection()
+ sel.selected_foreach(self._sig_sel_add)
+
+ def _sig_sel_add(self, store, path, iter):
+ num = self.model2.append()
+ self.model2.set(num, 0, store.get_value(iter,0), 1, store.get_value(iter,1))
+
+ def sig_unsel(self, widget=None):
+ def _sig_sel_del(store, path, iter):
+ store.remove(iter)
+ (store,paths) = self.view2.get_selection().get_selected_rows()
+ for p in paths:
+ store.remove(store.get_iter(p))
+
+ def sig_unsel_all(self, widget=None):
+ self.model2.clear()
+
+ def go(self):
+ while True:
+ button = self.win.run()
+ if button == gtk.RESPONSE_OK:
+ if not len(self.model2):
+ common.warning(_("You have not selected any fields to import"), parent=self.win)
+ continue
+
+ fields = []
+ fields2 = []
+ iter = self.model2.get_iter_root()
+ while iter:
+ fields.append(self.model2.get_value(iter, 1))
+ fields2.append(self.model2.get_value(iter, 0))
+ iter = self.model2.iter_next(iter)
+
+ csv = {
+ 'fname': self.glade.get_widget('import_csv_file').get_filename(),
+ 'sep': self.glade.get_widget('import_csv_sep').get_text(),
+ 'del': self.glade.get_widget('import_csv_del').get_text(),
+ 'skip': self.glade.get_widget('import_csv_skip').get_value(),
+ 'combo': self.glade.get_widget('import_csv_combo').get_active_text() or 'UTF-8'
+ }
+ self.parent.present()
+ self.win.destroy()
+ if csv['fname']:
+ if self.invert:
+ inverted = []
+ for f in fields:
+ for key, value in self.fields_invert.items():
+ if key.encode('utf8') == f:
+ inverted.append(value)
+ return import_csv(csv, inverted, self.model, self.fields_invert, context=self.context, parent=self.win)
+ else:
+ return import_csv(csv, fields, self.model, self.fields, context=self.context, parent=self.win)
+ return False
+ else:
+ self.parent.present()
+ self.win.destroy()
+ return False
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/win_list.py'
--- bin/modules/gui/window/win_list.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/win_list.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+from gtk import glade
+import gobject
+import gettext
+
+
+#from view_tree import parse
+import rpc
+
+
+fields_list_type = {
+ 'checkbox': gobject.TYPE_BOOLEAN,
+ 'integer': gobject.TYPE_INT,
+ 'float': gobject.TYPE_FLOAT
+}
+
+class win_list(object):
+ def __init__(self, model, sel_multi=True, context={}, search=False):
+ self.sel_multi = sel_multi
+ self.context = context
+ self.context.update(rpc.session.context)
+
+ self.model_name = model
+ view = rpc.session.rpc_exec_auth('/object', 'execute', model, 'fields_view_get', False, 'tree', context)
+ self.view_data = view
+ self.tree = widget.tree(view['arch'], view['fields'], model, sel_multi=sel_multi, search=search)
+ self.tree.context = context
+ self.fields = view['fields']
+ self.widget = self.tree.widget
+ self.view = self.tree.widget
+ self.fields_order = self.tree.fields_order
+
+ def destroy(self):
+ self.tree.destroy()
+ del self.fields_order
+ del self.widget
+ del self.view
+
+ def reload(self, ids):
+ res = rpc.session.rpc_exec_auth('/object', 'execute', self.model_name, 'read', ids, self.fields_order, self.context)
+ self.tree.value = res
+
+ def sel_pos_set(self, num):
+ sel = self.view.get_selection()
+ sel.unselect_all()
+ sel.select_path((num,))
+ self.view.scroll_to_cell((num,))
+
+ def sel_ids_get(self):
+ return self.tree.sel_ids_get()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/win_preference.py'
--- bin/modules/gui/window/win_preference.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/win_preference.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gettext
+
+import gtk
+from gtk import glade
+
+import rpc
+
+import common
+import win_search
+import copy
+import service
+from widget.screen import Screen
+
+import form
+
+import options
+import translate
+
+
+class win_preference(object):
+ def __init__(self, parent=None):
+ self.glade = glade.XML(common.terp_path("openerp.glade"),'win_preference', gettext.textdomain())
+ self.win = self.glade.get_widget('win_preference')
+ self.win.set_icon(common.OPENERP_ICON)
+ if not parent:
+ parent = service.LocalService('gui.main').window
+ self.win.set_transient_for(parent)
+ self.parent = parent
+
+ action_id = rpc.session.rpc_exec_auth('/object', 'execute', 'res.users', 'action_get', {})
+ action = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.actions.act_window', 'read', [action_id], False, rpc.session.context)[0]
+
+ view_ids = []
+ if action.get('views', []):
+ view_ids = [x[0] for x in action['views']]
+ elif action.get('view_id', False):
+ view_ids = [action['view_id'][0]]
+
+ self.screen = Screen('res.users', view_type=[], window=parent)
+ self.screen.add_view_id(view_ids[0], 'form', display=True)
+ self.screen.load([rpc.session.uid])
+ self.screen.display(rpc.session.uid)
+
+ vbox = self.glade.get_widget('preference_vbox')
+ vbox.pack_start(self.screen.widget)
+
+ self.win.set_title(_('Preferences'))
+ self.win.show_all()
+
+ def run(self, datas={}):
+ lang = rpc.session.context.get('lang', 'en_US')
+ end = False
+ while not end:
+ res = self.win.run()
+ end = (res != gtk.RESPONSE_OK) or self.screen.current_model.validate()
+ if not end:
+ self.screen.display()
+ self.screen.current_view.set_cursor()
+ if res == gtk.RESPONSE_OK:
+ values = self.screen.get()
+ rpc.session.rpc_exec_auth('/object', 'execute', 'res.users', 'write', [rpc.session.uid], values)
+ rpc.session.context_reload()
+ new_lang = rpc.session.context.get('lang', 'en_US')
+ if lang != new_lang:
+ common.message(_("The default language of the interface has been modified, do not forget to restart " \
+ "the client to have the interface in your language"),
+ _("Default language modified !"), parent=self.win)
+ self.parent.present()
+ self.win.destroy()
+ return True
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/modules/gui/window/win_search.py'
--- bin/modules/gui/window/win_search.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/win_search.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+from gtk import glade
+from copy import deepcopy
+import gobject
+import gettext
+import common
+import service
+
+import rpc
+
+from widget.screen import Screen
+import widget_search
+
+fields_list_type = {
+ 'checkbox': gobject.TYPE_BOOLEAN
+}
+
+class dialog(object):
+ def __init__(self, model, domain=None, context=None, window=None, target=False):
+ if domain is None:
+ domain = []
+ if context is None:
+ context = {}
+
+ if not window:
+ window = service.LocalService('gui.main').window
+
+ self.dia = gtk.Dialog(_('OpenERP - Link'), window,
+ gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT)
+ self.window = window
+ if not target:
+ self.dia.set_property('default-width', 760)
+ self.dia.set_property('default-height', 500)
+ self.dia.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+ self.dia.set_icon(common.OPENERP_ICON)
+
+ self.accel_group = gtk.AccelGroup()
+ self.dia.add_accel_group(self.accel_group)
+
+ self.but_cancel = self.dia.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+ self.but_cancel.add_accelerator('clicked', self.accel_group, gtk.keysyms.Escape, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
+
+ self.but_ok = self.dia.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
+ self.but_ok.add_accelerator('clicked', self.accel_group, gtk.keysyms.Return, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
+
+ scroll = gtk.ScrolledWindow()
+ scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scroll.set_placement(gtk.CORNER_TOP_LEFT)
+ scroll.set_shadow_type(gtk.SHADOW_NONE)
+ self.dia.vbox.pack_start(scroll, expand=True, fill=True)
+
+ vp = gtk.Viewport()
+ vp.set_shadow_type(gtk.SHADOW_NONE)
+ scroll.add(vp)
+ self.screen = Screen(model, view_ids=None, domain=domain, context=context, window=self.dia, view_type=['form'])
+ self.screen.new()
+ vp.add(self.screen.widget)
+
+ x,y = self.screen.screen_container.size_get()
+ width, height = window.get_size()
+ vp.set_size_request(min(width - 20, x + 20),min(height - 60, y + 25))
+ self.dia.show_all()
+ self.screen.display()
+
+ def run(self, datas={}):
+ while True:
+ try:
+ res = self.dia.run()
+ if res == gtk.RESPONSE_OK:
+ if self.screen.current_model.validate() and self.screen.save_current():
+ return self.screen.current_model.id
+ else:
+ self.screen.display()
+ self.screen.current_view.set_cursor()
+ else:
+ break
+ except Exception:
+ # Passing all exceptions, most preferably the one of sql_constraint
+ pass
+ return False
+
+ def destroy(self):
+ self.window.present()
+ self.dia.destroy()
+ self.screen.destroy()
+
+
+class win_search(object):
+ def __init__(self, model, sel_multi=True, ids=[], context={}, domain = [], parent=None):
+ self.model = model
+ self.sel_multi = sel_multi
+ self.ids = ids
+ self.glade = glade.XML(common.terp_path("openerp.glade"),'win_search',gettext.textdomain())
+ self.win = self.glade.get_widget('win_search')
+ self.win.set_icon(common.OPENERP_ICON)
+ if not parent:
+ parent = service.LocalService('gui.main').window
+ self.parent = parent
+ self.win.set_transient_for(parent)
+ self.screen = Screen(model, view_type=['tree'], show_search=True, domain=domain,
+ context=context, parent=self.win, win_search=True)
+ self.view = self.screen.current_view
+ if self.screen.filter_widget.focusable:
+ self.screen.filter_widget.focusable.grab_focus()
+ self.title = _('OpenERP Search: %s') % self.screen.name
+ self.title_results = _('OpenERP Search: %s (%%d result(s))') % (self.screen.name,)
+ self.win.set_title(self.title)
+ self.view.unset_editable()
+ sel = self.view.widget_tree.get_selection()
+ if not sel_multi:
+ sel.set_mode('single')
+ else:
+ sel.set_mode(gtk.SELECTION_MULTIPLE)
+ self.view.widget_tree.connect('row_activated', self.sig_activate)
+ self.view.widget_tree.connect('button_press_event', self.sig_button)
+ self.screen.win_search_callback = self.update_title
+ vp = gtk.Viewport()
+ vp.set_shadow_type(gtk.SHADOW_NONE)
+ vp.add(self.screen.widget)
+ vp.show()
+ self.sw = gtk.ScrolledWindow()
+ self.sw.set_shadow_type(gtk.SHADOW_NONE)
+ self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.sw.add(vp)
+ self.sw.show()
+ self.wid = self.glade.get_widget('win_search_vbox')
+ self.wid.pack_start(self.sw)
+ self.wid.show_all()
+
+ def sig_activate(self, treeview, path, column, *args):
+ self.view.widget_tree.emit_stop_by_name('row_activated')
+ if not self.sel_multi:
+ self.win.response(gtk.RESPONSE_OK)
+ return False
+
+ def sig_button(self, view, event):
+ if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
+ self.win.response(gtk.RESPONSE_OK)
+ return False
+
+ def find(self, widget=None, *args):
+ self.screen.search_filter()
+ self.update_title()
+
+ def update_title(self):
+ self.ids = self.screen.win_search_ids
+ self.reload()
+ self.win.set_title(self.title_results % len(self.ids))
+
+ def reload(self):
+ sel = self.view.widget_tree.get_selection()
+ if sel.get_mode() == gtk.SELECTION_MULTIPLE:
+ sel.select_all()
+
+
+ def sel_ids_get(self):
+ return self.screen.sel_ids_get()
+
+ def destroy(self):
+ self.parent.present()
+ self.win.destroy()
+
+ def go(self):
+ ## This is if the user has set some filters by default with search_default_XXX
+ if self.ids:
+ self.screen.win_search_domain += [('id','in', self.ids)]
+ self.find()
+ else:
+ self.screen.update_scroll()
+ end = False
+ while not end:
+ button = self.win.run()
+ if button == gtk.RESPONSE_OK:
+ res = self.sel_ids_get() or self.ids
+ end = True
+ elif button== gtk.RESPONSE_APPLY:
+ self.find()
+ else:
+ res = None
+ end = True
+ self.destroy()
+ if button == gtk.RESPONSE_ACCEPT:
+ dia = dialog(self.model, window=self.parent, domain=self.screen.domain_init ,context=self.screen.context)
+ id = dia.run()
+ res = id and [id] or None
+ dia.destroy()
+ return res
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added file 'bin/modules/gui/window/win_selection.py'
--- bin/modules/gui/window/win_selection.py 1970-01-01 00:00:00 +0000
+++ bin/modules/gui/window/win_selection.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import gtk
+from gtk import glade
+import gettext
+import common
+
+from view_tree import parse
+import gobject
+import rpc
+
+fields_list_type = {
+ 'checkbox': gobject.TYPE_BOOLEAN
+}
+
+#
+# Should be replaced by win_browse
+#
+
+class win_selection_class(object):
+ def __init__(self, ids, model, view=None):
+ self.glade = glade.XML(common.terp_path("openerp.glade"), "win_selection",gettext.textdomain())
+ self.win = self.glade.get_widget('win_selection')
+ self.win.set_icon(common.OPENERP_ICON)
+ self.parent = service.LocalService('gui.main').window
+ self.win.set_transient_for(parent)
+
+ self.ids = ids
+ self.view = self.glade.get_widget('win_sel_tree')
+ self.view.get_selection().set_mode('single')
+ if view==None:
+ fields = { 'name': {'type': 'char', 'string':_('Name')} }
+ xml = '''<?xml version="1.0"?>
+<tree string="%s">
+ <field name="name" string="%s"></field>
+</tree>''' % (_('Ressource Name'), _('Names'))
+ else:
+ fields = None
+ xml = None
+
+ p = parse.parse(fields)
+ p.parse(xml, self.view)
+ self.view.set_expander_column(self.view.get_column(1))
+ self.fields_order = p.fields_order
+
+ types=[ gobject.TYPE_STRING ]
+ for x in self.fields_order:
+ types.append( fields_list_type.get(fields[x]['type'], gobject.TYPE_STRING))
+ self.model = gtk.ListStore(*types)
+
+ if view==None:
+ res_ids = rpc.session.rpc_exec_auth('/object', 'execute', model, 'name_get', self.ids, rpc.session.context)
+ for res in res_ids:
+ num = self.model.append()
+ self.model.set(num, 0, res[0], 1, res[1])
+ else:
+ pass # Todo
+
+ self.view.set_model(self.model)
+ self.view.show_all()
+
+ def id_name_get(self):
+ id = self.value_get(0)
+ if id:
+ return (id, self.value_get(1))
+ return None
+
+ def value_get(self, col):
+ sel = self.view.get_selection().get_selected()
+ if sel==None:
+ return None
+ (model, iter) = sel
+ return model.get_value(iter, col)
+
+ def go(self):
+ button = self.win.run()
+ if button==gtk.RESPONSE_OK:
+ res = self.id_name_get()
+ else:
+ res=None
+ self.parent.present()
+ self.win.destroy()
+ return res
+
+def win_selection_h(from_resource, ids, model, view=None):
+ return win_selection(ids, model, view)
+
+def win_selection(ids, model, view=None):
+ if len(ids)==1:
+ return rpc.session.rpc_exec_auth('/object', 'execute', model, 'name_get', ids, rpc.session.context)[0]
+ win = win_selection_class(ids, model, view)
+ res = win.go()
+ return res
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added directory 'bin/modules/spool'
=== added file 'bin/modules/spool/__init__.py'
--- bin/modules/spool/__init__.py 1970-01-01 00:00:00 +0000
+++ bin/modules/spool/__init__.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import service
+
+class spool(service.Service):
+ def __init__(self, name='spool'):
+ service.Service.__init__(self, name, '*')
+ self.obj_sub = {}
+ self.report = {}
+
+ def publish(self, name, obj, datas, trigger=True):
+ if name not in self.report:
+ self.report[name]=[]
+ self.report[name].append((obj, datas))
+ if trigger:
+ return self.trigger(name)
+ return 0
+
+ def subscribe(self, name, method, datas={}):
+ if name not in self.obj_sub:
+ self.obj_sub[name]=[]
+ self.obj_sub[name].append( (method, datas) )
+
+ def trigger(self, name):
+ nbr = 0
+ while len(self.report[name]):
+ (obj, datas) = self.report[name].pop()
+ if name in self.obj_sub:
+ for i in self.obj_sub[name]:
+ new_datas = datas.copy()
+ new_datas.update(i[1])
+ i[0](obj, new_datas)
+ nbr +=1
+ return nbr
+spool()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/observator.py'
--- bin/observator.py 1970-01-01 00:00:00 +0000
+++ bin/observator.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+
+class ObservatorRegistry(object):
+ def __new__(cls):
+ if not hasattr(cls, '_inst'):
+ cls._inst = object.__new__(cls)
+ return cls._inst
+
+ def __init__(self):
+ self._observables = {}
+ self._receivers = {}
+
+ def add_observable(self, oid, obj):
+ self._observables[oid] = obj
+
+ def add_receiver(self, signal, callable):
+ if signal not in self._receivers:
+ self._receivers[signal] = []
+ self._receivers[signal].append(callable)
+
+ def remove_receiver(self, signal, callable):
+ self._receivers[signal].remove(callable)
+
+ def warn(self, *args):
+ for receiver in self._receivers.get(args[0], []):
+ receiver(*args[1:])
+
+oregistry = ObservatorRegistry()
+
+
+class Observable(object):
+ def __init__(self):
+ oregistry.add_observable(id(self), self)
+
+ def warn(self, *args):
+ oregistry.warn(args[0], self, *args[1:])
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/openerp-client.py'
--- bin/openerp-client.py 1970-01-01 00:00:00 +0000
+++ bin/openerp-client.py 2011-04-25 08:38:27 +0000
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+"""
+OpenERP - Client
+OpenERP is an ERP+CRM program for small and medium businesses.
+
+The whole source code is distributed under the terms of the
+GNU Public Licence.
+
+(c) 2003-TODAY, Fabien Pinckaers - Tiny sprl
+"""
+
+
+import sys
+import os
+import release
+__author__ = release.author
+__version__ = release.version
+
+import __builtin__
+__builtin__.__dict__['openerp_version'] = __version__
+
+import logging
+arguments = {}
+if sys.platform == 'win32':
+ arguments['filename'] = os.path.join(os.environ['USERPROFILE'], 'openerp-client.log')
+
+logging.basicConfig(**arguments)
+
+from distutils.sysconfig import get_python_lib
+terp_path = os.path.join(get_python_lib(), 'openerp-client')
+sys.path.append(terp_path)
+
+if os.name == 'nt':
+ sys.path.insert(0, os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'GTK\\bin'))
+ sys.path.insert(0, os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'GTK\\lib'))
+ os.environ['PATH'] = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'GTK\\lib') + ";" + os.environ['PATH']
+ os.environ['PATH'] = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'GTK\\bin') + ";" + os.environ['PATH']
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gtk.glade
+
+#gtk.gdk.threads_init() # causes the GTK client to block everything.
+
+import locale
+import gettext
+
+import atk
+import gtk._gtk
+import pango
+
+if os.name == 'nt':
+ sys.path.insert(0, os.path.join(os.getcwd(), os.path.dirname(sys.argv[0])))
+ os.environ['PATH'] = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0])) + ";" + os.environ['PATH']
+
+import translate
+translate.setlang()
+
+import options
+
+# On first run, client won't have a language option,
+# so try with the LANG environ, or fallback to english
+client_lang = options.options['client.lang']
+if not client_lang:
+ client_lang = os.environ.get('LANG', '').split('.')[0]
+
+translate.setlang(client_lang)
+
+
+# add new log levels below DEBUG
+logging.DEBUG_RPC = logging.DEBUG - 1
+logging.addLevelName(logging.DEBUG_RPC, 'DEBUG_RPC')
+logging.Logger.debug_rpc = lambda self, msg, *args, **kwargs: self.log(logging.DEBUG_RPC, msg, *args, **kwargs)
+
+logging.DEBUG_RPC_ANSWER = logging.DEBUG - 2
+logging.addLevelName(logging.DEBUG_RPC_ANSWER, 'DEBUG_RPC_ANSWER')
+logging.Logger.debug_rpc_answer = lambda self, msg, *args, **kwargs: self.log(logging.DEBUG_RPC_ANSWER, msg, *args, **kwargs)
+
+logging.getLogger().setLevel(getattr(logging, options.options['logging.level'].upper()))
+
+
+
+import modules
+import common
+
+items = [('terp-flag', '_Translation', gtk.gdk.CONTROL_MASK, ord('t'), '')]
+gtk.stock_add (items)
+
+factory = gtk.IconFactory ()
+factory.add_default ()
+
+pix_file = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'icons')
+if not os.path.isdir(pix_file):
+ pix_file = os.path.join(options.options['path.pixmaps'],'icons')
+
+for fname in os.listdir(pix_file):
+ ffname = os.path.join(pix_file,fname)
+ if not os.path.isfile(ffname):
+ continue
+ iname = os.path.splitext(fname)[0]
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(ffname)
+ except:
+ pixbuf = None
+ continue
+ if pixbuf:
+ icon_set = gtk.IconSet (pixbuf)
+ factory.add('terp-'+iname, icon_set)
+
+try:
+ win = modules.gui.main.terp_main()
+ if options.options.rcexist:
+ win.sig_login()
+ if os.name == 'nt':
+ from tools.win32 import get_systemfont_style
+ gtk.rc_parse_string(get_systemfont_style())
+ gtk.main()
+except KeyboardInterrupt, e:
+ log = logging.getLogger('common')
+ log.info(_('Closing OpenERP, KeyboardInterrupt'))
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
=== added file 'bin/openerp.glade'
--- bin/openerp.glade 1970-01-01 00:00:00 +0000
+++ bin/openerp.glade 2011-04-25 08:38:27 +0000
@@ -0,0 +1,8424 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+ <widget class="GtkDialog" id="win_login">
+ <property name="title" translatable="yes">OpenERP - Login</property>
+ <property name="modal">True</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkVBox" id="vbox30">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImage" id="image_tinyerp">
+ <property name="width_request">500</property>
+ <property name="height_request">71</property>
+ <property name="visible">True</property>
+ <property name="stock">gtk-missing-image</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="border_width">10</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">3</property>
+ <property name="row_spacing">3</property>
+ <child>
+ <widget class="GtkEntry" id="ent_passwd">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="activates_default">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="ent_login">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="activates_default">True</property>
+ <property name="text">admin</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="width_request">117</property>
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xpad">3</property>
+ <property name="ypad">3</property>
+ <property name="label" translatable="yes">Server:</property>
+ </widget>
+ <packing>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="ent_server">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="editable">False</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">16</property>
+ <property name="text">localhost</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="but_server">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Change</property>
+ <property name="use_underline">True</property>
+ <property name="response_id">0</property>
+ <signal name="activate" handler="on_but_server_activate"/>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label266">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xpad">3</property>
+ <property name="ypad">3</property>
+ <property name="label" translatable="yes">Database:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xpad">3</property>
+ <property name="ypad">3</property>
+ <property name="label" translatable="yes">User:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xpad">3</property>
+ <property name="ypad">3</property>
+ <property name="label" translatable="yes">Password:</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox57">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkComboBox" id="combo_db">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="combo_label">
+ <property name="visible">True</property>
+ <property name="xalign">0.0099999997764825821</property>
+ <property name="yalign">1</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="ent_db">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="okbutton1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_connect">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="win_main">
+ <property name="width_request">600</property>
+ <property name="height_request">400</property>
+ <property name="visible">True</property>
+ <property name="title">OpenERP</property>
+ <property name="window_position">GTK_WIN_POS_CENTER</property>
+ <property name="default_width">920</property>
+ <property name="default_height">780</property>
+ <signal name="destroy_event" handler="on_win_main_destroy_event"/>
+ <child>
+ <widget class="GtkVBox" id="vbox_main">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment52">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="menuitem1_menu">
+ <child>
+ <widget class="GtkImageMenuItem" id="login">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Connect...</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_login_activate"/>
+ <accelerator key="O" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4209">
+ <property name="visible">True</property>
+ <property name="stock">gtk-connect</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="logout">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Disconnect</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_logout_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4210">
+ <property name="visible">True</property>
+ <property name="stock">gtk-disconnect</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator9">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="Databases">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Databases</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="Databases_menu">
+ <child>
+ <widget class="GtkImageMenuItem" id="db_new">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_New database</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_db_new_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4212">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="db_restore">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Restore database</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_db_restore_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4213">
+ <property name="visible">True</property>
+ <property name="stock">gtk-open</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="db_backup">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Backup database</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_db_backup_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4214">
+ <property name="visible">True</property>
+ <property name="stock">gtk-save-as</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="db_drop">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Dro_p database</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_db_drop_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4215">
+ <property name="visible">True</property>
+ <property name="stock">gtk-delete</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator77">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="db_migrate_retrieve_script">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Download Migrations Code</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_db_migrate_retrieve_script_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image_retrieve_script">
+ <property name="visible">True</property>
+ <property name="stock">gtk-sort-descending</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="db_migrate">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Migrate Database(s)</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_db_migrate_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image_migrate">
+ <property name="visible">True</property>
+ <property name="stock">gtk-refresh</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator7">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="admin_password">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Administrator Password</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_admin_password_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4216">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-question</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4211">
+ <property name="visible">True</property>
+ <property name="stock">gtk-network</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator2">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="quitter1">
+ <property name="visible">True</property>
+ <property name="label">gtk-quit</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_quit_activate"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="user">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">_User</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_requests_activate"/>
+ <child>
+ <widget class="GtkMenu" id="user_menu">
+ <child>
+ <widget class="GtkImageMenuItem" id="preference">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Preferences</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_preference_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4217">
+ <property name="visible">True</property>
+ <property name="stock">gtk-preferences</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="change_passwd">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Change password</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_change_passwd_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="item2">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="send_request">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Send a request</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_send_request_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4218">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="read_requests">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Read my requests</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_read_requests_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4219">
+ <property name="visible">True</property>
+ <property name="stock">gtk-find</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="séparateur12">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="request_wait">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Waiting Requests</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_request_wait_activate"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="form">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">For_m</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_activate"/>
+ <child>
+ <widget class="GtkMenu" id="form_menu">
+ <child>
+ <widget class="GtkImageMenuItem" id="form_new">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_New</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_new_activate"/>
+ <accelerator key="N" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4220">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_save">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Save</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_save_activate"/>
+ <accelerator key="S" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4221">
+ <property name="visible">True</property>
+ <property name="stock">gtk-save</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="copy">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Copy this resource</property>
+ <property name="label" translatable="yes">_Duplicate</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_copy_activate"/>
+ <accelerator key="D" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4222">
+ <property name="visible">True</property>
+ <property name="stock">gtk-copy</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_delete">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Delete</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_del_activate"/>
+ <accelerator key="D" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4223">
+ <property name="visible">True</property>
+ <property name="stock">gtk-delete</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="séparateur1">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_search">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Find</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_search_activate"/>
+ <accelerator key="F" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4224">
+ <property name="visible">True</property>
+ <property name="stock">gtk-find</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_next">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Ne_xt</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_next_activate"/>
+ <accelerator key="Page_Down" modifiers="" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4225">
+ <property name="visible">True</property>
+ <property name="stock">gtk-go-forward</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_previous">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Pre_vious</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_previous_activate"/>
+ <accelerator key="Page_Up" modifiers="" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4226">
+ <property name="visible">True</property>
+ <property name="stock">gtk-go-back</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="switch_lf">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Switch to list/form</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_but_switch_clicked"/>
+ <accelerator key="L" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4227">
+ <property name="visible">True</property>
+ <property name="stock">gtk-justify-fill</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="win_new">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Menu</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_win_new_activate"/>
+ <accelerator key="T" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4228">
+ <property name="visible">True</property>
+ <property name="stock">gtk-index</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator8">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="win_home">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_New Home Tab</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_win_home_activate"/>
+ <accelerator key="H" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4229">
+ <property name="visible">True</property>
+ <property name="stock">gtk-home</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="win_close">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Close Tab</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_win_close_activate"/>
+ <accelerator key="W" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4230">
+ <property name="visible">True</property>
+ <property name="stock">gtk-close</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="win_prev">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Previous Tab</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_win_prev_activate"/>
+ <accelerator key="Page_Up" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="win_next">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Next Tab</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_win_next_activate"/>
+ <accelerator key="Page_Down" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="séparateur9">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="form_log">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">View _logs</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_log_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="goto_id">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Go to resource ID...</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_goto_id_activate"/>
+ <accelerator key="G" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator6">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_open">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Open</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_open_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4231">
+ <property name="visible">True</property>
+ <property name="stock">gtk-open</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_reload">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Reloa_d / Undo</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_reload_activate"/>
+ <accelerator key="R" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4232">
+ <property name="visible">True</property>
+ <property name="stock">gtk-redo</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="séparateur5">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="form_repeat">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Repeat latest _action</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_repeat_activate"/>
+ <accelerator key="Y" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK" signal="activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="séparateur7">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_print">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Preview in PDF</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_print_activate"/>
+ <accelerator key="P" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4233">
+ <property name="visible">True</property>
+ <property name="stock">gtk-print-preview</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_print_html">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Previe_w in editor</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_print_html_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4234">
+ <property name="visible">True</property>
+ <property name="stock">gtk-print-preview</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="séparateur8">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="form_save_as">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Expor_t data...</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_save_as_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4235">
+ <property name="visible">True</property>
+ <property name="stock">gtk-save-as</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="form_import">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">I_mport data...</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_form_import_activate"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="options">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Options</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="options_menu">
+ <child>
+ <widget class="GtkMenuItem" id="extension_manager">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Extension Manager</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_extension_manager_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="menubar">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Menubar</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_menubar_activate"/>
+ <child>
+ <widget class="GtkMenu" id="menubar_menu">
+ <child>
+ <widget class="GtkRadioMenuItem" id="menubar_both">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Text _and Icons</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_menubar_both_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="menubar_icons">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Icons only</property>
+ <property name="use_underline">True</property>
+ <property name="group">menubar_both</property>
+ <signal name="activate" handler="on_menubar_icons_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="menubar_text">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Text only</property>
+ <property name="use_underline">True</property>
+ <property name="group">menubar_both</property>
+ <signal name="activate" handler="on_menubar_text_activate"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="forms1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Forms</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_forms1_activate"/>
+ <child>
+ <widget class="GtkMenu" id="forms1_menu">
+ <child>
+ <widget class="GtkCheckMenuItem" id="opt_form_toolbar">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Right Toolbar</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_opt_form_toolbar_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="forms_tab">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Tabs default position</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_forms_tabs_activate"/>
+ <child>
+ <widget class="GtkMenu" id="forms_tab_menu">
+ <child>
+ <widget class="GtkRadioMenuItem" id="opt_form_tab_top">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Top</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_opt_form_tab_top_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="opt_form_tab_left">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Left</property>
+ <property name="use_underline">True</property>
+ <property name="group">opt_form_tab_top</property>
+ <signal name="activate" handler="on_opt_form_tab_left_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="opt_form_tab_right">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Right</property>
+ <property name="use_underline">True</property>
+ <property name="group">opt_form_tab_top</property>
+ <signal name="activate" handler="on_opt_form_tab_right_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="opt_form_tab_bottom">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Bottom</property>
+ <property name="use_underline">True</property>
+ <property name="group">opt_form_tab_top</property>
+ <signal name="activate" handler="on_opt_form_tab_bottom_activate"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="forms_tab_orientation">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Tabs default orientation</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_forms_tabs_orientation_activate"/>
+ <child>
+ <widget class="GtkMenu" id="forms_tab_orientation_menu">
+ <child>
+ <widget class="GtkRadioMenuItem" id="opt_form_tab_orientation_0">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Horizontal</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_opt_form_tab_orientation_horizontal_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="opt_form_tab_orientation_90">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Vertical</property>
+ <property name="use_underline">True</property>
+ <property name="group">opt_form_tab_orientation_0</property>
+ <signal name="activate" handler="on_opt_form_tab_orientation_vertical_activate"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="printer">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Print</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="printer_menu">
+ <child>
+ <widget class="GtkCheckMenuItem" id="opt_print_preview">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Previe_w before print</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_opt_print_preview_activate"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4238">
+ <property name="visible">True</property>
+ <property name="stock">gtk-print</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator5">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="opt_save">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Save options</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_opt_save_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4239">
+ <property name="visible">True</property>
+ <property name="stock">gtk-save</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="plugins">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">_Plugins</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_plugins_activate"/>
+ <child>
+ <widget class="GtkMenu" id="plugins_menu">
+ <child>
+ <widget class="GtkImageMenuItem" id="plugin_execute">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Execute a plugin</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_plugin_execute_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4240">
+ <property name="visible">True</property>
+ <property name="stock">gtk-network</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="shortcut">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">_Shortcuts</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_shortcut_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="help">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="help_menu">
+ <child>
+ <widget class="GtkCheckMenuItem" id="opt_debug_mode_tooltip">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Enable Debug Mode Tooltips</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_opt_debug_mode_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="support">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Support Request</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_support_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4241">
+ <property name="visible">True</property>
+ <property name="stock">gtk-help</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="séparateur13">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="help_index">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">User _Manual</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_help_index_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4242">
+ <property name="visible">True</property>
+ <property name="stock">gtk-zoom-fit</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="help_contextual">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Contextual Help</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_help_contextual_activate"/>
+ <accelerator key="H" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4243">
+ <property name="visible">True</property>
+ <property name="stock">gtk-jump-to</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="shortcuts">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Keyboard Shortcuts</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_shortcuts_activate"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="séparateur11">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="help_licence">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_License</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_help_licence_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4245">
+ <property name="visible">True</property>
+ <property name="stock">gtk-justify-center</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="about1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_About...</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_about_activate"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4246">
+ <property name="visible">True</property>
+ <property name="stock">gtk-about</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolbar" id="main_toolbar">
+ <property name="visible">True</property>
+ <property name="toolbar_style">GTK_TOOLBAR_BOTH</property>
+ <child>
+ <widget class="GtkToolButton" id="but_new">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Create a new resource</property>
+ <property name="stock_id">gtk-new</property>
+ <signal name="clicked" handler="on_but_new_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_save">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Edit / Save this resource</property>
+ <property name="stock_id">gtk-save</property>
+ <signal name="clicked" handler="on_but_save_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkSeparatorToolItem" id="separatortoolitem5">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_remove">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Delete this resource</property>
+ <property name="stock_id">gtk-delete</property>
+ <signal name="clicked" handler="on_but_remove_clicked"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorToolItem" id="separatortoolitem1">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_previous">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Go to previous matched search</property>
+ <property name="label" translatable="yes">Previous</property>
+ <property name="stock_id">gtk-go-back</property>
+ <signal name="clicked" handler="on_but_previous_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_next">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Go to next matched resource</property>
+ <property name="label" translatable="yes">Next</property>
+ <property name="stock_id">gtk-go-forward</property>
+ <signal name="clicked" handler="on_but_next_clicked"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorToolItem" id="separatortoolitem888">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radio_tree">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">List</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-find</property>
+ <signal name="toggled" handler="on_radio_tree_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radio_form">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Form</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-new</property>
+ <property name="group">radio_tree</property>
+ <signal name="clicked" handler="on_radio_form_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radio_calendar">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Calendar</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-select-color</property>
+ <property name="group">radio_tree</property>
+ <signal name="clicked" handler="on_radio_calendar_clicked"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radio_diagram">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Diagram</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-select-color</property>
+ <property name="group">radio_tree</property>
+ <signal name="clicked" handler="on_radio_diagram_clicked"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radio_graph">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Graph</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-spell-check</property>
+ <property name="group">radio_tree</property>
+ <signal name="clicked" handler="on_radio_graph_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radio_gantt">
+ <property name="visible">False</property>
+ <property name="label" translatable="yes">Gantt</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-justify-right</property>
+ <property name="group">radio_tree</property>
+ <signal name="clicked" handler="on_radio_gantt_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkSeparatorToolItem" id="separatortoolitem9">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_print">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Print documents</property>
+ <property name="stock_id">gtk-print</property>
+ <signal name="clicked" handler="on_but_print_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_action">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Launch actions about this resource</property>
+ <property name="label" translatable="yes">Action</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-execute</property>
+ <signal name="clicked" handler="on_but_action_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_attach">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Add an attachment to this resource</property>
+ <property name="label" translatable="yes">Attachment</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-paste</property>
+ <signal name="clicked" handler="on_but_attach_clicked"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorToolItem" id="separatortoolitem10">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_menu">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="tooltip" translatable="yes">Menu</property>
+ <property name="label" translatable="yes">Menu</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-index</property>
+ <signal name="clicked" handler="on_but_menu_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkSeparatorToolItem" id="separatortoolitem8">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_reload">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Reload</property>
+ <property name="label" translatable="yes">Reload</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-redo</property>
+ <signal name="clicked" handler="on_form_reload_activate"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkSeparatorToolItem" id="separatortoolitem19">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="but_close">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Close this window</property>
+ <property name="stock_id">gtk-close</property>
+ <signal name="clicked" handler="on_but_close_clicked"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox_status_main">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkStatusbar" id="sb_user_server">
+ <property name="width_request">150</property>
+ <property name="visible">True</property>
+ <property name="border_width">2</property>
+ <property name="has_resize_grip">False</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkStatusbar" id="sb_user_name">
+ <property name="width_request">130</property>
+ <property name="visible">True</property>
+ <property name="border_width">2</property>
+ <property name="has_resize_grip">False</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label54">
+ <property name="visible">True</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">Company:</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkStatusbar" id="sb_company">
+ <property name="width_request">125</property>
+ <property name="visible">True</property>
+ <property name="border_width">2</property>
+ <property name="has_resize_grip">False</property>
+ </widget>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label56">
+ <property name="visible">True</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">Requests:</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkStatusbar" id="sb_requests">
+ <property name="width_request">120</property>
+ <property name="visible">True</property>
+ <property name="border_width">2</property>
+ <property name="has_resize_grip">False</property>
+ </widget>
+ <packing>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="req_search_but">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip" translatable="yes">Read my Requests</property>
+ <property name="relief">GTK_RELIEF_NONE</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_read_requests_activate"/>
+ <child>
+ <widget class="GtkImage" id="image376">
+ <property name="visible">True</property>
+ <property name="stock">gtk-find</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="request_new_but">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip" translatable="yes">Send a new request</property>
+ <property name="relief">GTK_RELIEF_NONE</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_send_request_activate"/>
+ <child>
+ <widget class="GtkImage" id="image377">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkImage" id="secure_img">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-authentication</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="win_tree">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">OpenERP - Tree Resources</property>
+ <child>
+ <widget class="GtkVBox" id="win_tree_container">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkHPaned" id="hp_tree">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">250</property>
+ <child>
+ <widget class="GtkVBox" id="widget_vbox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkVPaned" id="tree_vpaned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">400</property>
+ <child>
+ <widget class="GtkToolbar" id="tree_toolbar">
+ <property name="visible">True</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+ <property name="toolbar_style">GTK_TOOLBAR_BOTH_HORIZ</property>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <child>
+ <widget class="GtkVBox" id="vbox13">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkToolbar" id="toolbar10">
+ <property name="visible">True</property>
+ <property name="toolbar_style">GTK_TOOLBAR_ICONS</property>
+ <property name="show_arrow">False</property>
+ <child>
+ <widget class="GtkToolButton" id="tbsc">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Shortcuts</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_tbsc_clicked"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolbar" id="toolbar1">
+ <property name="visible">True</property>
+ <property name="toolbar_style">GTK_TOOLBAR_ICONS</property>
+ <child>
+ <widget class="GtkToolButton" id="button4">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-add</property>
+ <signal name="clicked" handler="on_but_sc_add_clicked"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="button6">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-remove</property>
+ <signal name="clicked" handler="on_but_sc_del_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkSeparatorToolItem" id="separatortoolitem7">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="button7">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-goto-bottom</property>
+ <signal name="clicked" handler="on_but_expand_collapse_clicked"/>
+ </widget>
+ <packing>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkTreeView" id="win_tree_sc">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="reorderable">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="main_tree_sw">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="win_form">
+ <property name="visible">True</property>
+ <property name="extension_events">GDK_EXTENSION_EVENTS_ALL</property>
+ <property name="title" translatable="yes">OpenERP - Forms</property>
+ <child>
+ <widget class="GtkVBox" id="win_form_container">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="border_width">3</property>
+ <child>
+ <widget class="GtkStatusbar" id="stat_form">
+ <property name="width_request">103</property>
+ <property name="visible">True</property>
+ <property name="border_width">1</property>
+ <property name="has_resize_grip">False</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label37">
+ <property name="visible">True</property>
+ <property name="xpad">10</property>
+ <property name="label" translatable="yes">State:</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="stat_state">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="win_about">
+ <property name="width_request">430</property>
+ <property name="height_request">360</property>
+ <property name="title" translatable="yes">OpenERP - About</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkLabel" id="label36">
+ <property name="visible">True</property>
+ <property name="xpad">5</property>
+ <property name="ypad">8</property>
+ <property name="label" translatable="yes"><b>About OpenERP</b>
+<i>The most advanced Open Source ERP &amp; CRM !</i></property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHSeparator" id="hseparator4">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">2</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkNotebook" id="notebook2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow14">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="window_placement">GTK_CORNER_BOTTOM_LEFT</property>
+ <child>
+ <widget class="GtkTextView" id="textview2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="justification">GTK_JUSTIFY_CENTER</property>
+ <property name="text" translatable="yes">
+OpenERP - GTK Client - v%s
+
+OpenERP is an Open Source ERP+CRM
+for small to medium businesses.
+
+The whole source code is distributed under
+the terms of the GNU Public Licence.
+
+(c) 2003-TODAY, Tiny sprl
+
+More Info on www.openerp.com !</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox30">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImage" id="image165">
+ <property name="visible">True</property>
+ <property name="stock">gtk-network</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label98">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_OpenERP</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow15">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkTextView" id="textview3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="justification">GTK_JUSTIFY_CENTER</property>
+ <property name="accepts_tab">False</property>
+ <property name="text" translatable="yes">
+(c) 2003-TODAY - Tiny sprl
+OpenERP is a product of Tiny sprl:
+
+Tiny sprl
+40 Chaussée de Namur
+1367 Gérompont
+Belgium
+
+Tel : (+32)81.81.37.00
+Mail: sales@xxxxxxx
+Web: http://tiny.be</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox31">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImage" id="image166">
+ <property name="visible">True</property>
+ <property name="stock">gtk-home</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label99">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Contact</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="okbutton2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-5</property>
+ <signal name="pressed" handler="on_but_ok_pressed"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="win_form_widget">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Open ERP - Forms widget</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow44">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <child>
+ <widget class="GtkViewport" id="viewport5">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkVBox" id="vbox10">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="widget_textbox_tag">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkHBox" id="toolbar11">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkToggleToolButton" id="toggle_underline">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-underline</property>
+ <signal name="toggled" handler="on_toggle_underline_toggled"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToggleToolButton" id="toggle_bold">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-bold</property>
+ <signal name="toggled" handler="on_toggle_bold_toggled"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToggleToolButton" id="toggle_italic">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-italic</property>
+ <signal name="toggled" handler="on_toggle_italic_toggled"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToggleToolButton" id="toggle_strikethrough">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-strikethrough</property>
+ <signal name="toggled" handler="on_toggle_strike_toggled"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radioleft">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-justify-left</property>
+ <signal name="toggled" handler="on_radioleft_toggled"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radiocenter">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-justify-center</property>
+ <property name="group">radioleft</property>
+ <signal name="toggled" handler="on_radiocenter_toggled"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radioright">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-justify-right</property>
+ <property name="group">radioleft</property>
+ <signal name="toggled" handler="on_radioright_toggled"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioToolButton" id="radiofill">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-justify-fill</property>
+ <property name="group">radioleft</property>
+ <signal name="toggled" handler="on_radiofill_toggled"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolbar" id="toolbar111">
+ <property name="visible">True</property>
+ <property name="toolbar_style">GTK_TOOLBAR_ICONS</property>
+ <child>
+ <widget class="GtkFontButton" id="font_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <signal name="font-set" handler="on_font_button_clicked"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkColorButton" id="color_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <signal name="color-set" handler="on_color_button_clicked"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow31">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="window_placement">GTK_CORNER_BOTTOM_LEFT</property>
+ <child>
+ <widget class="GtkTextView" id="widget_textbox_tag_tv">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">12</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="win_sur">
+ <property name="width_request">450</property>
+ <property name="height_request">150</property>
+ <property name="title" translatable="yes">OpenERP - Confirmation</property>
+ <property name="modal">True</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox4">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkHBox" id="hbox28">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImage" id="image144">
+ <property name="visible">True</property>
+ <property name="xpad">15</property>
+ <property name="ypad">15</property>
+ <property name="stock">gtk-dialog-question</property>
+ <property name="icon_size">6</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="lab_question">
+ <property name="visible">True</property>
+ <property name="label">Are you sure?</property>
+ <property name="wrap">True</property>
+ <property name="single_line_mode">False</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">4</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area4">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="cancelbutton2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="okbutton4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="win_selection">
+ <property name="title" translatable="yes">OpenERP - Selection</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="default_width">400</property>
+ <property name="default_height">400</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox6">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkVBox" id="win_selection_vbox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkLabel" id="win_sel_title">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Your selection:</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHSeparator" id="hseparator8">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">3</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkTreeView" id="win_sel_tree">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area8">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="cancelbutton5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ <signal name="clicked" handler="on_cancelbutton5_clicked"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="okbutton6">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-5</property>
+ <signal name="clicked" handler="on_okbutton6_clicked"/>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="win_dialog">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">OpenERP - Dialog</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dia_vbox_main">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dia_hbox_button">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="win_field_pref">
+ <property name="width_request">440</property>
+ <property name="height_request">268</property>
+ <property name="title" translatable="yes">OpenERP, Field Preference target</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox8">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkTable" id="table3">
+ <property name="visible">True</property>
+ <property name="border_width">4</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">5</property>
+ <property name="row_spacing">4</property>
+ <child>
+ <widget class="GtkFrame" id="frame6">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment13">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkHBox" id="hbox40">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <widget class="GtkRadioButton" id="radio_user_pref">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_only for you</property>
+ <property name="use_underline">True</property>
+ <property name="response_id">0</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="radiobutton2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">for _all users</property>
+ <property name="use_underline">True</property>
+ <property name="response_id">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radio_user_pref</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label116">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Value applicable for:</b></property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkFrame" id="frame5">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment12">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkVBox" id="pref_vbox">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label115">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Value applicable if:</b></property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label63">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Field _Name:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ <property name="mnemonic_widget">ent_field</property>
+ </widget>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label66">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Domain:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ <property name="mnemonic_widget">ent_domain</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="ent_field">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="ent_domain">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="ent_value">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label64">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Default _value:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">ent_value</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area