1 """pygtk support for qubtree: clipboard and visual editing.
2
3 Copyright 2007-2012 Research Foundation State University of New York
4 This file is part of QUB Express.
5
6 QUB Express is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 QUB Express is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License,
17 named LICENSE.txt, in the QUB Express program directory. If not, see
18 <http://www.gnu.org/licenses/>.
19
20 """
21
22 import qubx.tree
23 from qubx.util_types import *
24 from qubx.GTK import *
25 import qubx.pyenv
26 import qubx.pyenvGTK
27 import sys
28 import os
29 import re
30 from pprint import pprint
31
32
33
34 -def Copy(node, clipboard=None):
35 """Copies node to the specified (def main) clipboard."""
36 targets = [ ("STRING", 0, 0),
37 ("TEXT", 0, 1),
38 ("COMPOUND_TEXT", 0, 2),
39 ("UTF8_STRING", 0, 3),
40 ("qubtree", 0, 4) ]
41 bin = [ node.getBytes() ]
42 def get(clipboard, selectiondata, info, data):
43 if info == 4:
44 selectiondata.set("qubtree", 8, bin[0])
45 else:
46 selectiondata.set_text(str(qubx.tree.ReadBytes(bin[0])))
47 def clear(clipboard, data):
48 bin[0] = ''
49
50 clip = clipboard or gtk.clipboard_get("CLIPBOARD")
51 clip.set_with_data(targets, get, clear, None)
52
54 """Returns True if there is a qubx.tree.Node in the specified (def main) clipboard."""
55 clip = clipboard or gtk.clipboard_get("CLIPBOARD")
56 targets = clip.wait_for_targets()
57 return bool(targets and ("qubtree" in targets))
58
59 -def Paste(clipboard=None):
60 """
61 Returns a copy of the qubx.tree.Node from the specified (def main) clipboard.
62 If there is none, or any exception occurs, node.isNull == True."""
63 try:
64 clip = clipboard or gtk.clipboard_get("CLIPBOARD")
65 seldata = clip.wait_for_contents("qubtree")
66 return qubx.tree.ReadBytes(seldata.data)
67 except:
68 traceback.print_exc()
69 return qubx.tree.NullNode()
70
71
72
74 l = eval(s, qubx.pyenv.env.globals)
75 try:
76 l = [0+l]
77 except:
78 try:
79 sum(l)
80 except:
81 sum(sum(l, []))
82 return l
83
85 """
86 acceptNodeData(node) -> accept;
87 accept(string) -> action_fn;
88 action_fn() -> modifies node.data;
89 e.g.
90
91 >>> try:
92 >>> acceptNodeData(node)(string)()
93 >>> except:
94 >>> invalid string
95 """
96 def assign(x):
97 def doit():
98 node.data = x
99 doit.val = x
100 return doit
101 def slice_ass(l):
102 def doit():
103 typ = node.data and node.data.type or qubx.tree.QTR_TYPE_DOUBLE
104 try:
105 sum(l)
106 if (node.data.rows > 1) and (node.data.cols > 1):
107 node.data.setup(typ, len(l), 1)
108 elif (not node.data) and (len(l) >= 1):
109 node.data = l[0]
110 node.data.resize(len(l))
111 else:
112 node.data.resize(len(l))
113 node.data[0:len(l)] = l
114 except:
115 nc = l and len(l[0]) or 0
116 nr = len(l)
117 node.data.setup(typ, nr, nc)
118 for i,r in enumerate(l):
119 node.data[i*nc:(i+1)*nc] = r[0:nc]
120 doit.val = l
121 return doit
122 def accept(s):
123 if not s:
124 return assign(None)
125 elif node.data and (node.data.loadedRows != (0, node.data.rows-1)):
126 raise Exception("can't handle unloaded data")
127 elif node.data.type == qubx.tree.QTR_TYPE_STRING:
128 return assign(s)
129 else:
130 try:
131 return slice_ass( acceptNumList(s) )
132 except:
133 if node.data:
134 raise Exception('expected number(s)')
135 return assign(s)
136 return accept
137
149
150 ex_numeric = """Examples:
151 (numeric)
152 1
153 1.0
154 pi
155 [2, 3]
156 [2.0, 3.0]
157 [[2.0, 3.0], [4.0, 5.0]]
158 [[2.0], [3.0]]
159 [float(x) for x in xrange(100)]
160 """
161
163 if node.data.type == qubx.tree.QTR_TYPE_STRING:
164 return 'To change to numeric data, first clear the data and close this window. Then enter an integer or floating point number.'
165 elif node.data.type in [qubx.tree.QTR_TYPE_UNKNOWN, qubx.tree.QTR_TYPE_POINTER]:
166 return 'details: %s\n%d x %d' % qubx.tree.TYPENAMES[node.data.type], node.data.rows, node.data.cols
167 elif not node.data:
168 return ex_numeric+"""
169 (string)
170 whatever
171 """
172 elif node.data.loadedRows != (0, node.data.rows-1):
173 "To edit this data, double-click the node in tree-view, or right-click it -> Properties."
174 else:
175 return ex_numeric+"""
176 data type (%s) is preserved. If you'd like to change it, e.g. from float to int or even string, first clear the data and close this window. Then enter a number, with or without decimal point as desired.
177 """ % qubx.tree.TYPENAMES[node.data.type]
178
207
209 return not bool(re.search(r"\.\.\.", data))
210
211
212
214 """
215 gtk.Widget for browsing/editing a qubx.tree.Node and its children.
216
217 @ivar root: a qubx.tree.Node. no-one but this TreeView should edit root while it's assigned here
218 @ivar changed: whether this TreeView has made any changes since root was assigned
219 @ivar OnChange: L{WeakEvent}(TreeView)
220 """
221
222 __explore_featured = ['col_name', 'cell_name', 'col_data', 'cell_data', 'OnChange', 'root', 'changed', 'add_children']
223
236
238 self.set_size_request(200, 70)
239 self.popup = gtk.Menu()
240 self.itemCut = build_menuitem('Cut', self.__ref(self._onItemCut), menu=self.popup)
241 self.itemCopy = build_menuitem('Copy', self.__ref(self._onItemCopy), menu=self.popup)
242 self.itemPasteAfter = build_menuitem('Paste After', self.__ref(self._onItemPasteAfter), menu=self.popup)
243 self.itemPasteChild = build_menuitem('Paste Child', self.__ref(self._onItemPasteChild), menu=self.popup)
244 self.itemInsertAfter = build_menuitem('Insert After', self.__ref(self._onItemInsertAfter), menu=self.popup)
245 self.itemInsertChild = build_menuitem('Insert Child', self.__ref(self._onItemInsertChild), menu=self.popup)
246 self.itemDelete = build_menuitem('Delete', self.__ref(self._onItemDelete), menu=self.popup)
247 self.itemProperties = build_menuitem('Properties', self.__ref(self._onItemProperties), menu=self.popup)
248
249 self.store = gtk.TreeStore(str, str, gobject.TYPE_PYOBJECT, bool)
250 self.set_model(self.store)
251 self.col_name = gtk.TreeViewColumn('Name')
252 self.append_column(self.col_name)
253 self.cell_name = gtk.CellRendererText()
254 self.cell_name.set_property('editable', True)
255 self.col_name.pack_start(self.cell_name, True)
256 self.col_name.add_attribute(self.cell_name, 'text', 0)
257 self.col_data = gtk.TreeViewColumn('Data')
258 self.append_column(self.col_data)
259 self.cell_data = gtk.CellRendererText()
260
261 self.col_data.pack_start(self.cell_data, True)
262 self.col_data.add_attribute(self.cell_data, 'text', 1)
263 self.col_data.add_attribute(self.cell_data, 'editable', 3)
264
276 root = property(lambda s: s._root, set_root)
277
279 if x:
280 self.OnChange(self)
281 self._changed = x
282 changed = property(lambda s: s._changed, set_changed)
283
292
294 path = tuple(re.split(':',path_string))
295 if len(path) == 1:
296 return
297 node = self.store.get(self.store.get_iter(path_string), 2)[0]
298 node.name = new_text
299 self.store.set(self.store.get_iter(path_string), 0, new_text)
300 self.changed = True
302 node = self.store.get(self.store.get_iter(path_string), 2)[0]
303 accept = acceptNodeData(node)
304 def use(txt):
305 accept(txt)()
306 data = formatNodeData(node)
307 self.store.set(self.store.get_iter(path_string), 1, data)
308 self.store.set(self.store.get_iter(path_string), 3, nodeDataIsFullyShown(data))
309 self.changed = True
310 try:
311 use(new_text)
312 except:
313 dlg = qubx.pyenvGTK.HelpTestDialog(accept, captionNodeData(node), "data = ", helpNodeData(node),
314 lambda x: qubx.pyenv.env.globals.__setitem__('data', x.val), lambda x:'data')
315 if gtk.RESPONSE_ACCEPT == dlg.run(new_text):
316 try:
317 use(dlg.expr)
318 except:
319 pass
320 dlg.destroy()
358 hereI = self.store.get_iter(self.itemPath)
359 node = self.store.get(hereI, 2)[0]
360 Copy(node)
362 hereI = self.store.get_iter(self.itemPath)
363 node = self.store.get(hereI, 2)[0]
364 if node == self.root:
365 return
366 parentI = self.store.get_iter(self.itemPath[:-1])
367 try:
368 sib = Paste()
369 if sib.isNull:
370 return
371 node.parent.insert(sib, node)
372 data = formatNodeData(sib)
373 chI = self.store.insert_after(parentI, hereI, [sib.name, data, sib, nodeDataIsFullyShown(data)])
374 self.add_children(sib, chI)
375 self.set_cursor(self.store.get_path(chI), self.col_name, False)
376 self.changed = True
377 except:
378 traceback.print_exc()
380 hereI = self.store.get_iter(self.itemPath)
381 node = self.store.get(hereI, 2)[0]
382 try:
383 ch = Paste()
384 if ch.isNull:
385 return
386 node.insert(ch)
387 data = formatNodeData(ch)
388 chI = self.store.insert_after(hereI, None, [ch.name, data, ch, nodeDataIsFullyShown(data)])
389 self.add_children(ch, chI)
390 self.expand_row(self.itemPath, False)
391 self.set_cursor(self.store.get_path(chI), self.col_name, 0)
392 self.changed = True
393 except:
394 traceback.print_exc()
396 hereI = self.store.get_iter(self.itemPath)
397 node = self.store.get(hereI, 2)[0]
398 if node == self.root:
399 return
400 parentI = self.store.get_iter(self.itemPath[:-1])
401 sib = node.parent.insert('untitled', node)
402 data = formatNodeData(sib)
403 chI = self.store.insert_after(parentI, hereI, [sib.name, data, sib, nodeDataIsFullyShown(data)])
404 self.set_cursor(self.store.get_path(chI), self.col_name, True)
405 self.changed = True
407 hereI = self.store.get_iter(self.itemPath)
408 node = self.store.get(hereI, 2)[0]
409 ch = node.insert('untitled')
410 data = formatNodeData(ch)
411 chI = self.store.insert_after(hereI, None, [ch.name, data, ch, nodeDataIsFullyShown(data)])
412 self.expand_row(self.itemPath, False)
413 self.set_cursor(self.store.get_path(chI), self.col_name, True)
414 self.changed = True
434
435
436
437
439 """
440 Properties panel for a single qubx.tree.Node.
441
442 @ivar node: a L{qubx.tree.Node}; do not edit node's name or data while it is assigned here
443 """
444
445 __explore_featured = ['node', 'refresh', 'reshape', 'reparse']
446
466
468 self.set_size_request(400, 200)
469 self.header = pack_item(gtk.HBox(True), self)
470 h = pack_item(gtk.HBox(), self.header)
471 pack_label('Name', h)
472 self.txtName = pack_item(NumEntry(""), h, expand=True)
473 h = pack_item(gtk.HBox(), self.header)
474 pack_label('Data type:', h)
475 self.mnuType = pack_item(DynamicComboBox(), h, expand=True)
476 self.header2 = pack_item(gtk.HBox(), self)
477 h = self.header2
478 self.chkPreload = pack_check('Preload', h)
479 self.panRC = pack_item(gtk.HBox(), h, expand=True)
480 self.txtRows = pack_item(NumEntry(1, acceptIntGreaterThan(-1), width_chars=4), self.panRC)
481 pack_label(' x ', self.panRC)
482 self.txtCols = pack_item(NumEntry(1, acceptIntGreaterThan(-1), width_chars=4), self.panRC)
483 self.btnExport = pack_button('Export...', self.panRC, on_click=self._onPressExport)
484 self.btnImport = pack_button('Import...', self.panRC, on_click=self._onPressImport)
485 self.panLoad = pack_item(gtk.HBox(), h)
486 pack_label('Load:', self.panLoad)
487 self.txtLoadFrom = pack_item(NumEntry(1, acceptIntGreaterThan(-2), width_chars=4), self.panLoad)
488 pack_label('-', self.panLoad, expand=True)
489 self.txtLoadTo = pack_item(NumEntry(1, acceptIntGreaterThan(-2), width_chars=4), self.panLoad)
490 self.txtData = gtk.TextView()
491 self.txtDataOut = TextViewAppender(self.txtData)
492 pack_scrolled(self.txtData, self, expand=True)
493
494 self.buttons = pack_item(gtk.HBox(), self)
495 self.btnApply = pack_button('Apply', self.buttons, at_end=True, sensitive=False)
496
500 node = property(lambda s: s._node, set_node)
553 - def reshape(self, type, rows, cols):
554 node = self.node
555 if type == qubx.tree.QTR_TYPE_EMPTY:
556 node.data = None
557 return True
558 elif type in [qubx.tree.QTR_TYPE_UNKNOWN, qubx.tree.QTR_TYPE_POINTER]:
559 gobject.idle_add(self.mnuType.choose, qubx.tree.TYPENAMES[node.data.type])
560 return False
561 elif type == qubx.tree.QTR_TYPE_STRING:
562 buf = self.txtData.get_buffer()
563 node.data = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) or "edit_me"
564 return True
565 else:
566 if node.data.type in [qubx.tree.QTR_TYPE_EMPTY, qubx.tree.QTR_TYPE_UNKNOWN, qubx.tree.QTR_TYPE_POINTER]:
567 node.data.setup(type, 1, 1)
568 node.data[0] = 0
569 elif node.data.type == qubx.tree.QTR_TYPE_STRING:
570 node.data.setup(type, 1, 1)
571 self.reparse()
572 elif (node.data.type == type) and (node.data.cols == cols):
573 node.data.resize(rows)
574 else:
575 clone = node.clone(False)
576 pre = node.data.preload
577 ldb = node.data.loadedRows
578 node.data.preload = True
579 node.data.setup(type, rows, cols)
580 for i in xrange(min(rows, node.data.rows)):
581 node_row = node.data.row(i)
582 clone_row = clone.data.row(i)
583 for j in xrange(min(len(node_row), len(clone_row))):
584 node_row[j] = clone_row[j]
585 node.data.preload = pre
586 if pre:
587 node.data.loadRows()
588 else:
589 node.data.loadRows(*ldb)
590 return True
592 node = self.node
593 buf = self.txtData.get_buffer()
594 txt = buf.get_text(buf.get_start_iter(), buf.get_end_iter())
595 if node.data.type == qubx.tree.QTR_TYPE_STRING:
596 node.data = txt
597 self.btnApply.set_sensitive(False)
598 self.txtData.modify_text(gtk.STATE_NORMAL, gdk.color_parse("#000000"))
599 else:
600 try:
601 lst = acceptNumList(txt or '[]')
602 try:
603 sum(lst)
604 lst = [[x] for x in lst]
605 except:
606 pass
607 la, lb = node.data.loadedRows
608 if la == -1:
609 return
610 if node.data.preload:
611 self.reshape(node.data.type, len(lst), len(lst) and len(lst[0]))
612 la, lb = node.data.loadedRows
613 for i in xrange(min(len(lst), lb-la+1)):
614 lst_row = lst[i]
615 node_row = node.data.row(la+i)
616 for j in xrange(min(node.data.cols, len(lst_row))):
617 node_row[j] = lst_row[j]
618 self.txtData.modify_text(gtk.STATE_NORMAL, gdk.color_parse("#000000"))
619 self.btnApply.set_sensitive(False)
620 except:
621 traceback.print_exc()
640 dlg = gtk.FileChooserDialog('Export data as...', None, gtk.FILE_CHOOSER_ACTION_SAVE,
641 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK))
642 dlg.set_default_response(gtk.RESPONSE_OK)
643 filter = gtk.FileFilter()
644 filter.set_name("Comma-separated files")
645 filter.add_pattern("*.csv")
646 dlg.add_filter(filter)
647 response = dlg.run()
648 if response == gtk.RESPONSE_OK:
649 try:
650 fi = open(dlg.get_filename(), "w")
651 self._export(fi)
652 except Exception, e:
653 print "Saving %s: %s" % (dlg.get_filename(), e)
654 dlg.destroy()
656 dlg = gtk.FileChooserDialog('Import data...', None, gtk.FILE_CHOOSER_ACTION_OPEN,
657 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
658 dlg.set_default_response(gtk.RESPONSE_OK)
659 filter = gtk.FileFilter()
660 filter.set_name("Comma-separated files")
661 filter.add_pattern("*.csv")
662 dlg.add_filter(filter)
663 filter = gtk.FileFilter()
664 filter.set_name("All files")
665 filter.add_pattern("*")
666 dlg.add_filter(filter)
667 response = dlg.run()
668 if response == gtk.RESPONSE_OK:
669 try:
670 self._import(open(dlg.get_filename(), "r"))
671 except:
672 traceback.print_exc()
673 dlg.destroy()
675 node = self.node
676 data = node.data
677 la, lb = node.data.loadedRows
678 if la == 0 and lb == data.rows-1:
679 for i in xrange(data.rows):
680 fi.write(','.join(map(str, data.row(i)[0:data.cols])))
681 fi.write('\n')
682 else:
683 atatime = 1000000 / node.data.size
684 for ii in xrange(0, data.rows, atatime):
685 node.data.loadRows(ii,ii+atatime-1)
686 for i in xrange(ii, min(data.rows,ii+atatime)):
687 fi.write(','.join(map(str, data.row(i)[0:data.cols])))
688 fi.write('\n')
689 if la >= 0:
690 data.loadRows(la, lb)
691 else:
692 data.unloadRows()
694 self.txtData.get_buffer().set_text("")
695 out = self.txtDataOut
696 out.write('[')
697 first = True
698 for line in fi:
699 if first:
700 first = False
701 else:
702 out.write(',\n')
703 if line:
704 out.write('[%s]' % line.strip())
705 out.write(']')
706 self.reparse()
707 self.refresh()
729 self.txtData.modify_text(gtk.STATE_NORMAL, gdk.color_parse("#FF0000"))
730 self.btnApply.set_sensitive(True)
737
739 """A dialog box to hold the NodeView for editing a single qubx.tree.Node."""
740 - def __init__(self, parent=None, appName=""):
741 """
742 @param parent: a gtk.Window to center the dialog upon
743 @param appName: host application name, for the title bar
744 """
745 gtk.Dialog.__init__(self, "", parent or qubx.GTK.get_active_window(), gtk.DIALOG_MODAL)
746 appPrefix = appName and (appName+" - ") or ""
747 self.title_prefix = appPrefix+"Edit data"
748 self.set_title(self.title_prefix)
749
750 self.view = pack_item(NodeView(), self.vbox, expand=True)
751 self.add_buttons(gtk.STOCK_CLOSE, gtk.RESPONSE_REJECT)
752 - def run(self, node):
753 """Shows node's properties, modally"""
754 self.view.node = node
755 self.set_title(self.title_prefix + " for "+node.name)
756 return gtk.Dialog.run(self)
757
758
759
760
762 """A top-level gtk.Window with a TreeView for editing a qubx.tree.Node and its children.
763
764 @ivar view: the TreeView
765 @ivar root: view.root
766 """
767 - def __init__(self, stand_alone=False, appName=""):
768 """
769 @param stand_alone: has menus, prompts to save, quits on close
770 @param appName: host application name, for the title bar
771 """
772 gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
773 self.__ref = Reffer()
774 self.stand_alone = stand_alone
775 appPrefix = appName and (appName+" - ") or ""
776 self.title_prefix = appPrefix+"Tree Editor"
777 self.set_title(self.title_prefix)
778 self._build_ui()
779
780 self.connect('delete_event', self._onDelete)
781 self.connect('destroy', self._onDestroy)
782
783 self.root = qubx.tree.Node('Untitled')
784
794
798 root = property(lambda s: s._root, set_root)
799 - def open(self, path):
808 if self.stand_alone and self.view.changed:
809 filename = self.get_filename()
810 dlg = gtk.MessageDialog(self, buttons=gtk.BUTTONS_NONE,
811 flags=gtk.DIALOG_MODAL,
812 message_format='Save changes to %s (%s)?' % (filename, self.root.name))
813 dlg.add_buttons('Yes',gtk.RESPONSE_YES,
814 'No',gtk.RESPONSE_NO,
815 'Cancel',gtk.RESPONSE_CANCEL)
816 response = dlg.run()
817 dlg.destroy()
818 if response == gtk.RESPONSE_YES:
819 if not self.save(): return self.promptSave()
820 elif response == gtk.RESPONSE_CANCEL:
821 return False
822 elif response == gtk.RESPONSE_NO:
823 pass
824 return True
825 - def save(self, ipath=None):
845 - def saveAs(self, ipath=None):
846 path = ipath or self.root.path or ""
847 dlg = gtk.FileChooserDialog('Save as...', self, gtk.FILE_CHOOSER_ACTION_SAVE,
848 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK))
849 dlg.set_default_response(gtk.RESPONSE_OK)
850 dlg.set_filename(path)
851 filter = gtk.FileFilter()
852 filter.set_name("all files")
853 filter.add_pattern("*.*")
854 dlg.add_filter(filter)
855 dlg.set_current_folder(self.get_path())
856 response = dlg.run()
857 if response == gtk.RESPONSE_OK:
858 try:
859 self.root.saveAs(dlg.get_filename())
860 self.view.changed = False
861 self.set_title(self.title_prefix+" - "+self.get_filename())
862 except Exception, e:
863 mdlg = gtk.MessageDialog(self, buttons=gtk.BUTTONS_OK, flags=gtk.DIALOG_MODAL,
864 message_format=str(e))
865 mdlg.run()
866 mdlg.destroy()
867 response = gtk.RESPONSE_CANCEL
868 dlg.destroy()
869 return response == gtk.RESPONSE_OK
870 - def saveAsText(self, ipath=None):
871 path = ipath or self.root.path or ""
872 print path, os.path.splitext(path)
873 path = os.path.splitext(path)[0] + '.txt'
874 print path
875 dlg = gtk.FileChooserDialog('Save as...', self, gtk.FILE_CHOOSER_ACTION_SAVE,
876 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK))
877 dlg.set_default_response(gtk.RESPONSE_OK)
878 dlg.set_filename(path)
879 filter = gtk.FileFilter()
880 filter.set_name("text files")
881 filter.add_pattern("*.txt")
882 dlg.add_filter(filter)
883 dlg.set_current_folder(self.get_path())
884 response = dlg.run()
885 if response == gtk.RESPONSE_OK:
886 try:
887 path = os.path.splitext(dlg.get_filename())[0] + '.txt'
888 self.root.saveAsText(path)
889 except Exception, e:
890 mdlg = gtk.MessageDialog(self, buttons=gtk.BUTTONS_OK, flags=gtk.DIALOG_MODAL,
891 message_format=str(e))
892 mdlg.run()
893 mdlg.destroy()
894 response = gtk.RESPONSE_CANCEL
895 dlg.destroy()
896 return response == gtk.RESPONSE_OK
908 if self.promptSave():
909 if self.stand_alone:
910 return False
911 else:
912 self.hide()
913 return True
915 if self.stand_alone:
916 gtk.main_quit()
918 self.mnuFile = gtk.Menu()
919 self.itemNew = build_menuitem("New", self.__ref(self._onItemNew), menu=self.mnuFile)
920 self.itemPasteNew = build_menuitem("Paste as new document", self.__ref(self._onItemPasteNew), menu=self.mnuFile)
921 self.itemOpen = build_menuitem("Open...", self.__ref(self._onItemOpen), menu=self.mnuFile)
922 self.itemSave = build_menuitem('Save', self.__ref(self._onItemSave), menu=self.mnuFile)
923 self.itemSaveAs = build_menuitem('Save As...', self.__ref(self._onItemSaveAs), menu=self.mnuFile)
924 self.itemSaveAsText = build_menuitem('Save a copy as text...', self.__ref(self._onItemSaveAsText), menu=self.mnuFile)
925 self.itemQuit = build_menuitem(self.stand_alone and 'Quit' or 'Close', self.__ref(self._onItemQuit), menu=self.mnuFile)
926
927 self.menubar = gtk.MenuBar()
928 self.mnuItemFile = build_menuitem('File', self.__ref(self._onShowMnuFile), submenu=self.mnuFile, menu=self.menubar)
930 self.itemPasteNew.set_sensitive(CanPaste())
931 self.itemSave.set_sensitive((not self.root.path) or bool(self.root.modified))
933 self.root = qubx.tree.Node('Untitled')
934 self.set_title(self.title_prefix)
941 dlg = gtk.FileChooserDialog('Open...', self, gtk.FILE_CHOOSER_ACTION_OPEN,
942 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
943 dlg.set_default_response(gtk.RESPONSE_OK)
944 filter = gtk.FileFilter()
945 filter.set_name("All files")
946 filter.add_pattern("*")
947 dlg.add_filter(filter)
948 dlg.set_current_folder(self.get_path())
949 response = dlg.run()
950 if response == gtk.RESPONSE_OK:
951 self.open(dlg.get_filename())
952 dlg.destroy()
957 - def _onItemSaveAsText(self, item):
960 if self.promptSave():
961 if self.stand_alone:
962 self.destroy()
963 else:
964 self.hide()
965
966
967
969 """
970 Dialog for modal editing of a qubx.tree.Node and its children.
971
972 @ivar view: the TreeView
973 @ivar root: view.root
974 """
975 - def __init__(self, parent=None, appName="", caption=""):
976 """
977 @param parent: a gtk.Window to center the dialog upon
978 @param appName: host application name, for the title bar
979 @param caption: optional text for the top of the dialog
980 """
981 gtk.Dialog.__init__(self, "", parent, gtk.DIALOG_MODAL)
982 self.resize(500, 600)
983 appPrefix = appName and (appName+" - ") or ""
984 self.title_prefix = appPrefix+"Tree Editor"
985 self.set_title(self.title_prefix)
986
987 if caption:
988 pack_label(caption, self.vbox)
989 self.view = TreeView()
990 pack_scrolled(self.view, self.vbox, expand=True)
991
992 self.root = qubx.tree.Node('Untitled')
996 root = property(lambda s: s._root, set_root)
997 - def run(self, node):
998 """Shows the dialog modally, editing node"""
999 self.root = node
1000 return gtk.Dialog.run(self)
1001
1002 -def UserEdit(node, parent=None, appName="", caption=""):
1008
1009
1010
1011 if __name__ == '__main__':
1012 qubx.tree.CHOOSE_FLAVOR('numpy')
1013 path = os.path.join(os.path.expanduser('~'), '.qubtree')
1014 qubx.pyenv.Init(path)
1015
1016 w = TreeWindow(stand_alone=True)
1017 w.open(sys.argv[1])
1018 w.show()
1019
1020 gtk.main()
1021
1022
1023