| 1 | #!/usr/bin/env python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | # Copyright (C) 2008 Adriano Monteiro Marques |
|---|
| 4 | # |
|---|
| 5 | # Author: Francesco Piccinno <stack.box@gmail.com> |
|---|
| 6 | # |
|---|
| 7 | # This program is free software; you can redistribute it and/or modify |
|---|
| 8 | # it under the terms of the GNU General Public License as published by |
|---|
| 9 | # the Free Software Foundation; either version 2 of the License, or |
|---|
| 10 | # (at your option) any later version. |
|---|
| 11 | # |
|---|
| 12 | # This program is distributed in the hope that it will be useful, |
|---|
| 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 15 | # GNU General Public License for more details. |
|---|
| 16 | # |
|---|
| 17 | # You should have received a copy of the GNU General Public License |
|---|
| 18 | # along with this program; if not, write to the Free Software |
|---|
| 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 20 | |
|---|
| 21 | import gtk |
|---|
| 22 | import pango |
|---|
| 23 | import gobject |
|---|
| 24 | |
|---|
| 25 | class Editor(gtk.HBox): |
|---|
| 26 | painter = None |
|---|
| 27 | |
|---|
| 28 | def __init__(self, field): |
|---|
| 29 | gtk.HBox.__init__(self, 0, False) |
|---|
| 30 | |
|---|
| 31 | self.field = field |
|---|
| 32 | self.create_widgets() |
|---|
| 33 | self.pack_widgets() |
|---|
| 34 | self.connect_signals() |
|---|
| 35 | |
|---|
| 36 | self.show() |
|---|
| 37 | |
|---|
| 38 | def create_widgets(self): pass |
|---|
| 39 | def pack_widgets(self): pass |
|---|
| 40 | def connect_signals(self): pass |
|---|
| 41 | |
|---|
| 42 | def get_value(self): |
|---|
| 43 | return self.field |
|---|
| 44 | |
|---|
| 45 | def set_value(self, value): |
|---|
| 46 | self.field = value |
|---|
| 47 | |
|---|
| 48 | value = property(get_value, set_value) |
|---|
| 49 | |
|---|
| 50 | class IntEditor(Editor): |
|---|
| 51 | # Check the tipe in __init__ |
|---|
| 52 | |
|---|
| 53 | def create_widgets(self): |
|---|
| 54 | self.adj = gtk.Adjustment() |
|---|
| 55 | self.spin = gtk.SpinButton(self.adj) |
|---|
| 56 | |
|---|
| 57 | def pack_widgets(self): |
|---|
| 58 | self.pack_start(self.spin) |
|---|
| 59 | self.spin.set_has_frame(False) |
|---|
| 60 | self.spin.show() |
|---|
| 61 | |
|---|
| 62 | class BitEditor(Editor): |
|---|
| 63 | def create_widgets(self): |
|---|
| 64 | self.btn = gtk.CheckButton() |
|---|
| 65 | |
|---|
| 66 | def pack_widgets(self): |
|---|
| 67 | self.pack_start(self.btn) |
|---|
| 68 | self.btn.show() |
|---|
| 69 | |
|---|
| 70 | def render(self, window, bounds, state): |
|---|
| 71 | if self.value: |
|---|
| 72 | sh = gtk.SHADOW_IN |
|---|
| 73 | else: |
|---|
| 74 | sh = gtk.SHADOW_OUT |
|---|
| 75 | |
|---|
| 76 | size = 20 |
|---|
| 77 | |
|---|
| 78 | if size > bounds.height: |
|---|
| 79 | size = bounds.height |
|---|
| 80 | if size > bounds.width: |
|---|
| 81 | size = bounds.width |
|---|
| 82 | |
|---|
| 83 | print window |
|---|
| 84 | |
|---|
| 85 | self.style.paint_check(window, state, sh, bounds, self.btn, "checkbutton", \ |
|---|
| 86 | bounds.x, bounds.y, bounds.width, bounds.height) |
|---|
| 87 | |
|---|
| 88 | cr = window.cairo_create() |
|---|
| 89 | cr.set_source_rgb(1, 0, 0) |
|---|
| 90 | cr.rectangle(bounds.x, bounds.y, bounds.width, bounds.height) |
|---|
| 91 | |
|---|
| 92 | class StrEditor(Editor): |
|---|
| 93 | def create_widgets(self): |
|---|
| 94 | self.entry = gtk.Entry() |
|---|
| 95 | self.btn = gtk.Button("...") |
|---|
| 96 | |
|---|
| 97 | def pack_widgets(self): |
|---|
| 98 | self.entry.set_has_frame(False) |
|---|
| 99 | self.pack_start(self.entry) |
|---|
| 100 | self.pack_start(self.btn, False, False, 0) |
|---|
| 101 | |
|---|
| 102 | def connect_signals(self): |
|---|
| 103 | self.btn.connect('clicked', self.__on_edit) |
|---|
| 104 | |
|---|
| 105 | def __on_edit(self, widget): |
|---|
| 106 | print "Yeah launch a dialog to edit the field" |
|---|
| 107 | |
|---|
| 108 | class HackEntry(gtk.Entry): |
|---|
| 109 | __gtype_name__ = "HackEntry" |
|---|
| 110 | |
|---|
| 111 | def __init__(self): |
|---|
| 112 | gtk.Entry.__init__(self) |
|---|
| 113 | |
|---|
| 114 | self.box = gtk.EventBox() |
|---|
| 115 | self.box.modify_bg(gtk.STATE_NORMAL, self.style.white) |
|---|
| 116 | self.box.connect('button-press-event', lambda *w: True) |
|---|
| 117 | |
|---|
| 118 | self.connect('parent-set', self.__on_parent_set) |
|---|
| 119 | |
|---|
| 120 | def do_show(self): |
|---|
| 121 | return |
|---|
| 122 | |
|---|
| 123 | def __on_parent_set(self, widget, parent): |
|---|
| 124 | if self.get_parent(): |
|---|
| 125 | if self.get_parent_window(): |
|---|
| 126 | self.box.set_parent_window(self.get_parent_window()) |
|---|
| 127 | self.box.set_parent(self.get_parent()) |
|---|
| 128 | self.box.show() |
|---|
| 129 | else: |
|---|
| 130 | self.box.unparent() |
|---|
| 131 | |
|---|
| 132 | def do_size_allocate(self, alloc): |
|---|
| 133 | # I wanna be extra large! mc donalds rules |
|---|
| 134 | if self.allocation.width >= alloc.width and \ |
|---|
| 135 | self.allocation.height >= alloc.height: |
|---|
| 136 | return |
|---|
| 137 | |
|---|
| 138 | gtk.Entry.do_size_allocate(self, alloc) |
|---|
| 139 | |
|---|
| 140 | # Reserving space for borders |
|---|
| 141 | alloc.height -= 1 |
|---|
| 142 | alloc.width -= 1 |
|---|
| 143 | |
|---|
| 144 | self.box.size_request() |
|---|
| 145 | self.box.size_allocate(alloc) |
|---|
| 146 | |
|---|
| 147 | class CellRendererProperty(gtk.CellRendererText): |
|---|
| 148 | __gtype_name__ = "CellRendererProperty" |
|---|
| 149 | |
|---|
| 150 | def __init__(self, tree): |
|---|
| 151 | super(CellRendererProperty, self).__init__() |
|---|
| 152 | |
|---|
| 153 | self.tree = tree |
|---|
| 154 | self.set_property('xalign', 0) |
|---|
| 155 | self.set_property('xpad', 3) |
|---|
| 156 | self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE) |
|---|
| 157 | |
|---|
| 158 | self.painter = None |
|---|
| 159 | |
|---|
| 160 | dummy_entry = gtk.Entry() |
|---|
| 161 | dummy_entry.set_has_frame(False) |
|---|
| 162 | self.row_height = dummy_entry.size_request()[1] |
|---|
| 163 | |
|---|
| 164 | def do_get_size(self, widget, area): |
|---|
| 165 | w, h = 0, 0 |
|---|
| 166 | |
|---|
| 167 | if h < self.row_height: |
|---|
| 168 | h = self.row_height |
|---|
| 169 | |
|---|
| 170 | w += self.get_property('xpad') * 2 |
|---|
| 171 | h += self.get_property('ypad') * 2 |
|---|
| 172 | |
|---|
| 173 | return (0, 0, w, h) |
|---|
| 174 | |
|---|
| 175 | def do_render(self, window, widget, background_area, \ |
|---|
| 176 | cell_area, expose_area, flags): |
|---|
| 177 | |
|---|
| 178 | # We draw two lines for emulating a box _| |
|---|
| 179 | widget.style.paint_hline( |
|---|
| 180 | window, gtk.STATE_NORMAL, |
|---|
| 181 | background_area, widget, "cell-line", |
|---|
| 182 | background_area.x, background_area.x + background_area.width, |
|---|
| 183 | background_area.y + background_area.height - 1) |
|---|
| 184 | |
|---|
| 185 | widget.style.paint_vline( \ |
|---|
| 186 | window, gtk.STATE_NORMAL, |
|---|
| 187 | background_area, widget, "cell-line", |
|---|
| 188 | background_area.y, |
|---|
| 189 | background_area.y + background_area.height - 1, |
|---|
| 190 | background_area.x + background_area.width - 1 |
|---|
| 191 | ) |
|---|
| 192 | |
|---|
| 193 | if self.painter != None: |
|---|
| 194 | if self.flags() & gtk.CELL_RENDERER_SELECTED: |
|---|
| 195 | state = gtk.STATE_SELECTED |
|---|
| 196 | else: |
|---|
| 197 | state = gtk.STATE_NORMAL |
|---|
| 198 | |
|---|
| 199 | self.painter.render(window, cell_area, state) |
|---|
| 200 | else: |
|---|
| 201 | return gtk.CellRendererText.do_render( \ |
|---|
| 202 | self, window, widget, background_area, cell_area, expose_area, flags |
|---|
| 203 | ) |
|---|
| 204 | |
|---|
| 205 | def do_start_editing(self, event, widget, path, \ |
|---|
| 206 | background_area, cell_area, flags): |
|---|
| 207 | |
|---|
| 208 | entry = HackEntry() |
|---|
| 209 | |
|---|
| 210 | entry.box.add(StrEditor(None)) |
|---|
| 211 | entry.box.show_all() |
|---|
| 212 | |
|---|
| 213 | entry.size_allocate(background_area) |
|---|
| 214 | |
|---|
| 215 | # Yes type error - PyGTK bug #542583 |
|---|
| 216 | return entry |
|---|
| 217 | |
|---|
| 218 | gobject.type_register(CellRendererProperty) |
|---|
| 219 | |
|---|
| 220 | class PropertyGridTree(gtk.ScrolledWindow): |
|---|
| 221 | def __init__(self): |
|---|
| 222 | gtk.ScrolledWindow.__init__(self) |
|---|
| 223 | |
|---|
| 224 | self.store = gtk.TreeStore(str, object, bool, object) |
|---|
| 225 | self.tree = gtk.TreeView(self.store) |
|---|
| 226 | |
|---|
| 227 | self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) |
|---|
| 228 | self.set_shadow_type(gtk.SHADOW_ETCHED_IN) |
|---|
| 229 | |
|---|
| 230 | col = gtk.TreeViewColumn('Property') |
|---|
| 231 | |
|---|
| 232 | crt = CellRendererProperty(self.tree) |
|---|
| 233 | crt.xpad = 0 |
|---|
| 234 | |
|---|
| 235 | col.pack_start(crt, True) |
|---|
| 236 | col.set_resizable(True) |
|---|
| 237 | col.set_expand(True) |
|---|
| 238 | col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) |
|---|
| 239 | col.set_fixed_width(180) |
|---|
| 240 | col.set_attributes(crt, text=0) |
|---|
| 241 | |
|---|
| 242 | col.set_cell_data_func(crt, self.__group_cell_func) |
|---|
| 243 | self.tree.append_column(col) |
|---|
| 244 | |
|---|
| 245 | col = gtk.TreeViewColumn('Value') |
|---|
| 246 | crt = CellRendererProperty(self.tree) |
|---|
| 247 | |
|---|
| 248 | col.pack_start(crt, True) |
|---|
| 249 | col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) |
|---|
| 250 | col.set_resizable(False) |
|---|
| 251 | col.set_expand(True) |
|---|
| 252 | col.set_attributes(crt, text=2, editable=2) |
|---|
| 253 | |
|---|
| 254 | col.set_cell_data_func(crt, self.__property_cell_func) |
|---|
| 255 | self.tree.append_column(col) |
|---|
| 256 | #self.tree.set_headers_visible(False) |
|---|
| 257 | |
|---|
| 258 | self.add(self.tree) |
|---|
| 259 | |
|---|
| 260 | it = self.store.append(None, ["test", None, True, True]) |
|---|
| 261 | self.store.append(it, ["test", None, True, None]) |
|---|
| 262 | self.store.append(it, ["test", None, True, None]) |
|---|
| 263 | self.store.append(None, ["test", None, True, None]) |
|---|
| 264 | |
|---|
| 265 | def __property_cell_func(self, col, cell, model, iter): |
|---|
| 266 | if isinstance(model.get_value(iter, 3), bool): |
|---|
| 267 | cell.painter = BitEditor(None) |
|---|
| 268 | |
|---|
| 269 | def __group_cell_func(self, col, cell, model, iter): |
|---|
| 270 | pass |
|---|
| 271 | |
|---|
| 272 | |
|---|
| 273 | class PropertyGrid(gtk.VBox): |
|---|
| 274 | def __init__(self): |
|---|
| 275 | gtk.VBox.__init__(self) |
|---|
| 276 | |
|---|
| 277 | self.__create_toolbar() |
|---|
| 278 | self.__create_widgets() |
|---|
| 279 | |
|---|
| 280 | def __create_widgets(self): |
|---|
| 281 | self.tree = PropertyGridTree() |
|---|
| 282 | self.pack_end(self.tree) |
|---|
| 283 | |
|---|
| 284 | def __create_toolbar(self): |
|---|
| 285 | pass |
|---|
| 286 | |
|---|
| 287 | if __name__ == "__main__": |
|---|
| 288 | w = gtk.Window() |
|---|
| 289 | w.add(PropertyGrid()) |
|---|
| 290 | w.show_all() |
|---|
| 291 | w.connect('delete-event', lambda *w: gtk.main_quit()) |
|---|
| 292 | gtk.main() |
|---|