Package qubx :: Module faces
[hide private]
[frames] | no frames]

Source Code for Module qubx.faces

   1  """ 
   2  Container widgets.  Each has a name and knows if it's showing on screen.  L{ToolsFace} contains other L{Face}s. 
   3  Uses super (http://www.phyast.pitt.edu/~micheles/python/super.pdf). 
   4   
   5  Copyright 2008-2013 Research Foundation State University of New York  
   6  This file is part of QUB Express.                                           
   7   
   8  QUB Express is free software; you can redistribute it and/or modify           
   9  it under the terms of the GNU General Public License as published by  
  10  the Free Software Foundation, either version 3 of the License, or     
  11  (at your option) any later version.                                   
  12   
  13  QUB Express is distributed in the hope that it will be useful,                
  14  but WITHOUT ANY WARRANTY; without even the implied warranty of        
  15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
  16  GNU General Public License for more details.                          
  17   
  18  You should have received a copy of the GNU General Public License,    
  19  named LICENSE.txt, in the QUB Express program directory.  If not, see         
  20  <http://www.gnu.org/licenses/>.                                       
  21   
  22  """ 
  23   
  24  import gtk 
  25  import gobject 
  26  import traceback 
  27  import sys 
  28  import os 
  29   
  30  import qubx.pyenv 
  31  import qubx.tree 
  32  from gtk import gdk 
  33  from gtk import keysyms 
  34  from itertools import izip, count 
  35  from math import * 
  36  from qubx.util_types import * 
  37  from qubx.GTK import TextViewAppender, gtk_literally 
  38  from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled, build_menuitem 
  39  from qubx.toolspace import ToolSpace, Layer, SubLayer_Label, SubLayer_DropDown, SubLayer_Popup, SubLayer_Popup_PNG, ColorInfo, COLOR_CLEAR 
  40   
  41  FACE_POP_INSIDE, FACE_POP_WINDOW, FACE_POP_MINIMIZED, FACE_POP_MAXIMIZED = (0, 1, 2, 3) 
  42  COLOR_POPPED_TEXT = ('faces.popped.empty.text', (.1,0,0,1)) 
  43  ColorInfo[COLOR_POPPED_TEXT[0]].label = 'Window popped-out placeholder text' 
  44  COLOR_POPPED_BTN = ('faces.popped.btn', (.7,0,.1,1)) 
  45  ColorInfo[COLOR_POPPED_BTN[0]].label = 'Window popped-out placeholder button' 
  46  FILEMENU_WIDTH = 32 
  47  RECENT_FILES_COUNT = 20 
  48   
  49  POS_DROPDOWN = 'dropdown' 
  50   
  51  REQUEST_SHOW_AS_WINDOW = False 
  52   
  53  RESPONSE_NO_TO_ALL = 12345 
  54   
  55   
56 -class Face(gtk.VBox):
57 """Base container class. 58 59 @ivar showing: (bool) whether this Face is on screen 60 @ivar icon: widget next to name in ToolsFace tabs 61 @ivar tab_sublayers: extra controls for ToolToggle sidebar 62 @ivar menubar: optional gtk.MenuBar for windowed state only 63 """ 64 __explore_featured = ['face_name', 'global_name', 'showing', 'icon', 'parent_window', 'pop_info', 'pop_prefs', 'tab_sublayers', 'menubar', 'pack_start', 'pack_end', 'remove', 'show', 'onShow', 'request_show', 'set_size_request'] 65
66 - def __init__(self, name, global_name=""):
67 super(Face, self).__init__() 68 self.face_name = name 69 self.global_name = global_name 70 self._showing = False 71 self.icon = None 72 self.__parent_window = None 73 self.__pop_prefs = None 74 self.pop_info = None 75 self.tab_sublayers = [] 76 self.menubar = None
77 showing = property(lambda s: s._showing, lambda s,x: s.set_showing(x))
78 - def set_showing(self, x):
79 if x == self._showing: return 80 self._showing = x 81 self.onShow(x)
82 - def onShow(self, showing):
83 """Override this method to do something when showing changes.""" 84 pass
85 - def request_show(self):
86 if self.pop_info: 87 if self.pop_info.state: 88 self.pop_info.window.present() 89 elif REQUEST_SHOW_AS_WINDOW: 90 self.pop_info.state = FACE_POP_WINDOW 91 self.pop_info.window.present() 92 else: 93 self.pop_info.container.show_face(self.face_name) 94 self.pop_info.container.request_show()
95 parent_window = property(lambda s: s.__parent_window, lambda s, x: s.set_parent_window(x))
96 - def set_parent_window(self, parent_window):
97 self.__parent_window = parent_window
98 pop_prefs = property(lambda s: s.__pop_prefs, lambda s, x: s.set_pop_prefs(x))
99 - def set_pop_prefs(self, pop_prefs):
100 self.__pop_prefs = pop_prefs
101 102
103 -class PopInfo(object):
104 """Represents the embedded or windowed state of a Face inside ToolsFace or ToolsToggleFace.""" 105
106 - def __init__(self, container, face):
107 self.container = container 108 self.face = face 109 self.window = None 110 self.OnWindow = WeakEvent() # (Face) 111 self.__pop_prefs = None 112 self.__state = FACE_POP_INSIDE 113 self.__position = (0, 0, 0, 0)
114 - def set_pop_prefs(self, prefs):
115 self.__pop_prefs = prefs 116 if prefs is None: return 117 if self.face.global_name: 118 self.__position = prefs.get_position(self.face.global_name) 119 gobject.idle_add(self.set_state, prefs.get_state(self.face.global_name))
120 pop_prefs = property(lambda self: self.__pop_prefs, lambda self, x: self.set_pop_prefs(x))
121 - def set_state(self, state):
122 if self.__state == state: return 123 self.face.pop_prefs.update_state(self.face.global_name, state) 124 if self.__state == FACE_POP_INSIDE: 125 self.__state = state 126 if self.__position == (0, 0, 0, 0): 127 x, y, w, h = self.face.get_allocation() 128 try: 129 x, y = self.face.translate_coordinates(self.face.get_toplevel(), 0, 0) 130 except: 131 pass 132 if self.face.parent_window: 133 px, py = self.face.parent_window.get_position() 134 x += px 135 y += py 136 self.__position = (x, y, w, h) 137 self.face.pop_prefs.update_position(self.face.global_name, x, y, w, h) 138 else: 139 x, y, w, h = self.__position 140 self.container.pop_out(self.face) 141 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 142 self.window.set_title(self.face.global_name) 143 self.window.connect('destroy', self.__onWinDestroy) 144 self.window.connect('configure_event', self.__onWinConfigure) 145 self.window.connect('window_state_event', self.__onWinState) 146 self.window.resize(w, h) 147 gobject.idle_add(self.window.move, x, y) 148 self.face.parent_window = self.window 149 self.toplevel = gtk.VBox() 150 self.toplevel.show() 151 self.window.add(self.toplevel) 152 if self.face.menubar: 153 self.toplevel.pack_start(self.face.menubar, False, True) 154 self.face.menubar.show() 155 self.toplevel.pack_start(self.face, True, True) 156 if state == FACE_POP_MINIMIZED: 157 self.window.iconify() 158 elif state == FACE_POP_MAXIMIZED: 159 self.window.maximize() 160 self.window.show() 161 self.face.showing = state != FACE_POP_MINIMIZED 162 self.OnWindow(self.face) 163 return 164 elif state == FACE_POP_INSIDE: 165 self.face.parent_window = self.container.parent_window 166 self.toplevel.remove(self.face) 167 if self.face.menubar: 168 self.toplevel.remove(self.face.menubar) 169 self.window.destroy() 170 self.window = self.toplevel = None 171 self.__state = state 172 self.container.pop_in(self.face, show=True) 173 self.OnWindow(self.face) 174 return 175 if self.__state == FACE_POP_MINIMIZED: 176 self.window.deiconify() 177 elif self.__state == FACE_POP_MAXIMIZED: 178 self.window.unmaximize() 179 self.__state = state 180 if state == FACE_POP_MINIMIZED: 181 self.window.iconify() 182 elif state == FACE_POP_MAXIMIZED: 183 self.window.maximize() 184 self.face.showing = state != FACE_POP_MINIMIZED
185 state = property(lambda self: self.__state, lambda self, x: self.set_state(x))
186 - def set_position(self, x, y, w, h):
187 if self.__position == (x, y, w, h): return 188 self.__position = x, y, w, h 189 self.face.pop_prefs.update_position(self.face.global_name, x, y, w, h) 190 if self.state == FACE_POP_WINDOW: 191 gobject.idle_add(self.window.move, x, y) 192 self.window.resize(w, h)
193 position = property(lambda self: self.__position)
194 - def __onWinDestroy(self, win):
195 self.toplevel.remove(self.face) 196 if self.face.menubar: 197 self.toplevel.remove(self.face.menubar) 198 self.__state = FACE_POP_INSIDE 199 self.face.pop_prefs.update_state(self.face.global_name, FACE_POP_INSIDE) 200 self.window = None 201 self.container.pop_in(self.face)
202 - def __onWinConfigure(self, win, alloc):
203 x, y = win.get_position() 204 self.__position = (x, y, alloc.width, alloc.height) 205 self.face.pop_prefs.update_position(self.face.global_name, x, y, alloc.width, alloc.height)
206 - def __onWinState(self, win, event):
207 state = self.state 208 if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: 209 if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: 210 state = FACE_POP_MINIMIZED 211 else: 212 state = FACE_POP_WINDOW 213 if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED: 214 if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED: 215 state = FACE_POP_MAXIMIZED 216 else: 217 state = FACE_POP_WINDOW 218 self.__state = state 219 self.face.showing = state != FACE_POP_MINIMIZED 220 self.face.pop_prefs.update_state(self.face.global_name, state)
221 222
223 -class PopPrefs(object):
224 """Manages global, persistent state for all containers' pop-out states."""
225 - def __init__(self, profile):
226 self.profile = profile 227 self.nodes = dict((node.name, node) for node in qubx.tree.children(profile)) 228 self.freeze = False
229 - def get_node(self, global_name):
230 if global_name in self.nodes: 231 node = self.nodes[global_name] 232 else: 233 node = self.profile[global_name] 234 self.nodes[global_name] = node 235 if not node['state'].data: 236 node['state'].data = FACE_POP_INSIDE 237 if len(node['position'].data) != 4: 238 node['position'].data = (0, 0, 0, 0) 239 return node
240 - def get_state(self, global_name):
241 return self.get_node(global_name)['state'].data[0]
242 - def get_position(self, global_name):
243 return self.get_node(global_name)['position'].data[:]
244 - def update_state(self, global_name, state):
245 if not self.freeze: 246 self.get_node(global_name)['state'].data = state
247 - def update_position(self, global_name, x, y, w, h):
248 if not self.freeze: 249 self.get_node(global_name)['position'].data = (x, y, w, h)
250 - def updateProfile(self, updates):
251 if self.freeze: return False 252 multi_window = False 253 for node in qubx.tree.children(updates): 254 if node.name in self.nodes: 255 try: 256 face = qubx.pyenv.env.eval_str(node.name, eat_exceptions=False) 257 if node['state'].data: 258 state = node['state'].data[0] 259 self.update_state(node.name, state) 260 face.pop_info.set_state(state) 261 multi_window = True 262 if len(node['position'].data) == 4: 263 x, y, w, h = node['position'].data[:] 264 self.update_position(node.name, x, y, w, h) 265 face.pop_info.set_position(x, y, w, h) 266 except AttributeError: 267 pass 268 except: 269 traceback.print_exc() 270 return multi_window
271 272
273 -class SubLayer_PopIcon(qubx.toolspace.SubLayer_Icon):
274 """Toggle graphic for pop-out/-in of sub-Face; shown in tabs of ToolsFace, ToolsToggleFace."""
275 - def __init__(self, *args, **kw):
276 self.__ref = Reffer() 277 if 'rgba' in kw: 278 self.rgba = kw['rgba'] 279 del kw['rgba'] 280 else: 281 self.rgba = (.7, .35, 0, .8) 282 kw['enter'] = self.__ref(self.__enter) 283 kw['exit'] = self.__ref(self.__exit) 284 qubx.toolspace.SubLayer_Icon.__init__(self, *args, **kw) 285 self.popped = False 286 self.__highlight = False
287 - def set_popped(self, x):
288 self.__popped = x 289 if self.layer and self.layer.space and (len(self.layer.subs) == 1): 290 self.layer.space.set_tooltip_text(x and 'Return panel to this layout' or 'Pop out panel as a separate window') 291 self.__exit() 292 self.invalidate()
293 popped = property(lambda self: self.__popped, lambda self, x: self.set_popped(x))
294 - def __enter(self, x, y, e):
295 self.__highlight = True 296 self.invalidate()
297 - def __exit(self):
298 self.__highlight = False 299 self.invalidate()
300 - def draw(self, context, w, h, appearance):
301 qubx.toolspace.SubLayer_Icon.draw(self, context, w, h, appearance) 302 context.save() 303 #context.set_source_rgba(.5, .5, .5, 1) 304 #context.rectangle(.05*self.w, .05*self.h, .9*self.w, .9*self.h) 305 #context.fill() 306 307 r,g,b,a = self.rgba 308 if self.__popped: 309 context.translate(self.w, self.h) 310 context.rotate(pi) 311 if self.__highlight: 312 h,s,v = RGBtoHSV(r, g, b) 313 r, g, b = HSVtoRGB(h, s, (v+1)/2) 314 context.set_line_width((0.25 + 0.1*self.__highlight) * appearance.emsize) 315 context.set_source_rgba(r, g, b, a) 316 for y0 in (.15, .35): 317 context.move_to(.25*self.w, (y0+.15)*self.h) 318 context.line_to(.5*self.w, y0*self.h) 319 context.line_to(.75*self.w, (y0+.15)*self.h) 320 context.stroke() 321 context.restore()
322 323
324 -class ToolsFace(Face):
325 """Shows one of a collection of L{Face}s, chosen by tab. Child faces are accessible 326 by natural naming; e.g. to get the child face with name "Whatever", type `myToolsFace.Whatever` 327 """ 328 329 __explore_featured = ['OnSwitchFace', 'book', 'mnu_line', 'mnu', 'faces', 'data', 330 'face', 'index', 'insert_face', 'append_face', 'remove_face', 'show_face', 331 'pop_out', 'pop_in']
332 - def __init__(self, name='Tools', pos=gtk.POS_LEFT, dropdown_label="", global_name=""):
333 """@param pos: e.g. gtk.POS_LEFT, for gtk.Notebook; or POS_DROPDOWN for a dropdown at top""" 334 super(ToolsFace, self).__init__(name, global_name) 335 self.__ref = Reffer() 336 self.__explore_featured = self.__explore_featured[:] # modify local copy with child face names 337 self.set_size_request(100, 100) 338 self.OnSwitchFace = WeakEvent() # (ToolsFace, face_name) 339 self.book = pack_item(gtk.Notebook(), self, expand=True, at_end=True) 340 self.book.connect('switch_page', self._onSwitchPage) 341 self.book.set_show_border(True) 342 if pos == POS_DROPDOWN: 343 self.book.set_show_tabs(False) 344 self.mnu_line = pack_item(gtk.HBox(), self) 345 pack_label(dropdown_label, self.mnu_line) 346 self.mnu = pack_item(gtk.combo_box_new_text(), self.mnu_line, expand=True) 347 self.mnu.connect('changed', self._onMnuChanged) 348 else: 349 self.mnu = None 350 self.mnu_line = None 351 self.book.set_tab_pos(pos) 352 self._face = None 353 self.faces = [] 354 self.data = {} 355 self.__draw_placeholder = self.__ref(lambda ctx, w, h: ctx.set_source_rgba(1,1,1,1) or ctx.paint())
356 - def set_parent_window(self, parent_window):
357 super(ToolsFace, self).set_parent_window(parent_window) 358 for face in self.faces: 359 if not face.pop_info.state: 360 face.parent_window = parent_window
361 - def set_pop_prefs(self, pop_prefs):
362 super(ToolsFace, self).set_pop_prefs(pop_prefs) 363 for face in self.faces: 364 face.pop_prefs = pop_prefs 365 face.pop_info.pop_prefs = pop_prefs
366 - def face(self, name):
367 """Returns a child L{Face} by name.""" 368 return self.data[name].face
369 - def index(self, name):
370 """Returns the index of a face name.""" 371 for i, f in enumerate(self.faces): 372 if f.face_name == name: 373 return i 374 raise KeyError(name)
375 - def build_tab_label(self, face):
376 tab = gtk.HBox() 377 if face.icon: 378 tab.pack_start(face.icon, False, True) 379 tab.pack_start(gtk.Label(face.face_name)) 380 sub = None 381 if face.pop_prefs: 382 pop = ToolSpace() 383 pop.set_size_request(14, 14) 384 pop.OnDraw += self.__ref(self.__onDrawPopBG) 385 name = face.face_name 386 sub = pop.add_layer(Layer(x=.2,y=.1, w=-.2, h=-.1, cBG=COLOR_CLEAR)).add_sublayer(SubLayer_PopIcon(w=-.01, h=-.01, rgba=(.66, .25, 0, 1), action=self.__ref(lambda x,y,e: self.__onTogglePopped(name)))) 387 sub.popped = False 388 tab.pack_start(pop, False, False) 389 tab.show_all() 390 return tab, sub
391 - def __onDrawPopBG(self, context, w, h):
392 context.set_source_rgba(.85, .85, .85, .85) 393 context.paint()
394 - def __onTogglePopped(self, face_name):
395 face = self.faces[self.index(face_name)] 396 if face.pop_info.state: 397 face.pop_info.state = 0 398 self.show_face(face.face_name) 399 else: 400 face.pop_info.state = 1
401 - def insert_face(self, i, face):
402 """Inserts a child face at index i.""" 403 self.faces.insert(i, face) 404 safename = SafeName(face.face_name) 405 self.data[face.face_name] = Anon(face=face) 406 try: 407 if isinstance(self.__dict__[safename], Face): 408 raise Exception('replaceable') 409 except: 410 self.__dict__[safename] = face 411 if not face.global_name: 412 face.global_name = "%s.%s" % (self.global_name, face.face_name) 413 face.parent_window = self.parent_window 414 face.pop_prefs = self.pop_prefs 415 face.pop_info = PopInfo(self, face) 416 face.pop_info.pop_prefs = self.pop_prefs 417 tab, pop = self.build_tab_label(face) 418 self.data[face.face_name].pop = pop 419 self.__explore_featured.append(safename) 420 face.show() 421 self.book.insert_page(face, tab, position=i) 422 if self.mnu: 423 self.mnu.insert_text(i, face.face_name) 424 if i == 0: 425 self.mnu.set_active(0)
426 - def append_face(self, face):
427 """Inserts a child face at the end.""" 428 if face.face_name in self.data: 429 self.remove_face(self.index(face.face_name)) 430 self.insert_face(len(self.faces), face)
431 - def remove_face(self, ix):
432 """Removes the child face at index ix.""" 433 face = self.faces[ix] 434 data = self.data[face.face_name] 435 del self.faces[ix] 436 del self.data[face.face_name] 437 self.__explore_featured.remove(SafeName(face.face_name)) 438 self.book.remove_page(ix) 439 if self.mnu: 440 self.mnu.remove_text(ix) 441 if face == self.__dict__[SafeName(face.face_name)]: 442 del self.__dict__[SafeName(face.face_name)] 443 if not self.faces: 444 self._face = None 445 self.OnSwitchFace(self, None) 446 face.parent_window = None 447 face.pop_prefs = None 448 face.pop_info = None
449 - def show_face(self, name):
450 """Brings the named face to the front.""" 451 self.book.set_current_page(self.faces.index(self.data[name].face))
452 - def onShow(self, showing):
453 if self._face and not self._face.pop_info.state: 454 self._face.showing = showing
455 - def _onSwitchPage(self, book, page, page_num):
456 if self._face and self._face.pop_info and (not self._face.pop_info.state) and self.showing: 457 self._face.showing = False 458 self._face = self.faces[page_num] 459 if self._face.pop_info and not self._face.pop_info.state: 460 self._face.showing = self.showing 461 if self.mnu: 462 self.mnu.set_active(page_num) 463 self.OnSwitchFace(self, self._face.face_name)
464 - def _onMnuChanged(self, mnu):
465 i = mnu.get_active() 466 if not (0 <= i < len(self.faces)): return 467 self.book.set_current_page(i)
468 - def pop_out(self, face):
469 ix = self.faces.index(face) 470 placeholder = ToolSpace() 471 placeholder.show() 472 placeholder.OnDraw += self.__draw_placeholder 473 layer = qubx.toolspace.Layer(x=0, y=0, w=50, h=9, cBG=COLOR_CLEAR) 474 placeholder.add_layer(layer) 475 layer.add_sublayer(qubx.toolspace.SubLayer_Label("This panel has been popped out as a separate window.", 476 -1, 1, x=6, y=2, w=50, h=1.5, 477 color=COLOR_POPPED_TEXT, hover_color=COLOR_POPPED_TEXT)) 478 layer.add_sublayer(qubx.toolspace.SubLayer_Label("Return to Layout", 0, 1, x=12, y=5, w=15, h=1.5, color=COLOR_POPPED_BTN, 479 border=1, action=self.__ref(bind(self.__return_to_layout, face.face_name)))) 480 layer.add_sublayer(qubx.toolspace.SubLayer_Label("Show Window", 0, 1, x=30, y=5, w=15, h=1.5, color=COLOR_POPPED_BTN, 481 border=1, action=self.__ref(bind(self.__show_window, face.face_name)))) 482 self.book.remove_page(ix) 483 tab, pop = self.build_tab_label(face) 484 self.data[face.face_name].pop = pop 485 pop.popped = True 486 self.book.insert_page(placeholder, tab, position=ix)
487 ### maybe with a message / buttons: bring to front, put back in this frame
488 - def __show_window(self, face_name):
489 face = self.faces[self.index(face_name)] 490 face.pop_info.window.present()
491 - def __return_to_layout(self, face_name):
492 face = self.faces[self.index(face_name)] 493 face.pop_info.set_state(0) or face.pop_info.container.show_face(face_name)
494 - def pop_in(self, face, show=False):
495 ix = self.faces.index(face) 496 self.book.remove_page(ix) 497 tab, pop = self.build_tab_label(face) 498 self.data[face.face_name].pop = pop 499 self.book.insert_page(face, tab, position=ix) 500 face.showing = self.showing and (self.book.get_current_page() == ix)
501 502 503 TOOLTOGGLE_WIDTH_EM = 2.0 504 TOOLTOGGLE_MENUHEIGHT_EM = 3.0 505 TOOLTOGGLE_TABHEIGHT_EM = 10.0 506 TOOLTOGGLE_NAME_EM = 16.0 507 TOOLTOGGLE_TAB_BORDER = 1.5 508 #TOOLTOGGLE_MIN_REPOS_PIX = 16 509 510 COLOR_TOOLTAB_BG = ('faces.tooltab.bg', (0,0,0,1)) 511 ColorInfo[COLOR_TOOLTAB_BG[0]].label = 'Window side button background' 512 COLOR_TOOLTAB = lambda showing: showing and ('faces.tooltab.showing', (.2, 1, .2, 1)) or ('faces.tooltab.hidden', (.2, 1, .2, .5)) 513 COLOR_TOOLTAB_CLOSE = ('faces.tooltoggle.close', (.9, 0, 0, 1)) 514 ColorInfo[COLOR_TOOLTAB_CLOSE].label = 'Window sidebar tab close button' 515 COLOR_TOOLTAB_CLOSE_HOVER = ('faces.tooltoggle.close.hover', (1, .2, .2, 1)) 516 ColorInfo[COLOR_TOOLTAB_CLOSE_HOVER].label = 'Window sidebar tab close button mouseover' 517 ColorInfo['faces.tooltab.showing'].label = 'Window side button foreground' 518 ColorInfo['faces.tooltab.hidden'].label = 'Window side button foreground hidden' 519 COLOR_TOOLTOGGLE_MENU = ('faces.tooltoggle.menu', (.2, 1, .2, 1)) 520 ColorInfo[COLOR_TOOLTOGGLE_MENU[0]].label = 'Window main menu' 521 COLOR_TOOLTOGGLE_INFO = ('faces.tooltoggle.info', (1, 1, 1, .76)) 522 ColorInfo[COLOR_TOOLTOGGLE_INFO[0]].label = 'Window instructions' 523
524 -class ToolsToggleFace(Face):
525 """Shows one or more of a collection of L{Face}s, toggled visible by left-hand tabs. Child faces are accessible 526 by natural naming; e.g. to get the child face with name "Whatever", type `myToolsFace.Whatever` 527 """ 528 __explore_featured = ['OnToggleFace', 'tabspace', 'tabLayer', 'toolBox', 'toolRoot', 'faces', 'data', 529 'tabs', 'pops', 'widgets', 'splits', 'splits_pos', 'data_showing', 530 'face', 'index', 'insert_face', 'append_face', 'remove_face', 'show_face', 531 'updateProfile', 'update_from_fractions', 'pop_out', 'pop_in', 'unpop_all'] 532
533 - def __init__(self, name='Tools', profile=None, global_name="", pop_prefs=None):
534 super(ToolsToggleFace, self).__init__(name, global_name) 535 self.connect('size_allocate', self.__on_size_allocate) 536 if not (profile is None): 537 self.profile = profile 538 else: 539 self.profile = qubx.tree.Node('') 540 self.__explore_featured = self.__explore_featured[:] # modify local copy with child face names 541 self.ref = Reffer() 542 self.set_size_request(100, 100) 543 self.OnToggleFace = WeakEvent() # (ToolsToggleFace, face_name, showing) 544 self.hbox = pack_item(gtk.HBox(), self, expand=True) 545 self.tabspace = pack_item(ToolSpace(), self.hbox) 546 self.tabspace.OnDraw += self.ref(self.__onDrawTabsBG) 547 self.tabspace.set_size_request(int(2*self.tabspace.appearance.emsize*TOOLTOGGLE_WIDTH_EM), -1) 548 self.tabspace.appearance.OnSetFontSize += self.ref(self.__onSetFontSize) 549 self.tabLayer = Layer(y=0.2*TOOLTOGGLE_WIDTH_EM, w=-.01, h=-TOOLTOGGLE_NAME_EM) 550 self.tabspace.add_layer(self.tabLayer) 551 self.tabLayer.add_sublayer(SubLayer_Label('panels:', x=0, y=-TOOLTOGGLE_TABHEIGHT_EM-2, 552 w=TOOLTOGGLE_WIDTH_EM, h=TOOLTOGGLE_TABHEIGHT_EM, 553 color=COLOR_TOOLTOGGLE_INFO, rotate=pi/2)) 554 self.nameLayer = Layer(y=-TOOLTOGGLE_NAME_EM-1, w=-.01, h=TOOLTOGGLE_NAME_EM) 555 self.tabspace.add_layer(self.nameLayer) 556 557 self.toolBox = pack_item(gtk.VBox(), self.hbox, expand=True) 558 self.toolRoot = None 559 self.faces = [] 560 self.data = {} 561 self.tabs = [] 562 self.pops = [] 563 self.widgets = [] 564 self.splits = [] 565 self.splits_pos = [] 566 self.data_showing = [] 567 self.__repositioning = False 568 self.__wh = (0,0) 569 if pop_prefs is None: 570 self.pop_prefs = PopPrefs(self.profile['PopPrefs']) 571 else: 572 self.pop_prefs = pop_prefs
573 # this is maybe obnoxious, preventing background work:
574 - def request_show(self):
575 super(ToolsToggleFace, self).request_show() 576 if self.parent_window and qubx.GTK.is_front_app(): 577 self.parent_window.present()
578 - def set_parent_window(self, parent_window):
582 - def set_pop_prefs(self, pop_prefs):
583 super(ToolsToggleFace, self).set_pop_prefs(pop_prefs) 584 for face in self.faces: 585 face.pop_prefs = pop_prefs 586 face.pop_info.pop_prefs = pop_prefs
587 - def updateProfile(self, settings, updates, second_time=False):
588 for node in qubx.tree.children(updates['Showing']): 589 if node.data and (node.name in self.data): 590 self.show_face(node.name, bool(node.data[0])) 591 if self.showing: 592 changed_frac = False 593 for node in qubx.tree.children(updates['Fraction']): 594 if node.data and (node.name in self.data): 595 self.data[node.name].fraction = node.data[0] 596 self.profile['Fraction'][node.name].data = node.data[0] 597 changed_frac = True 598 if changed_frac: 599 self.__relayout(renest=False) 600 multi_window = self.pop_prefs.updateProfile(updates.find('PopPrefs')) 601 # needs twice? 602 if not second_time: 603 gobject.idle_add(self.updateProfile2, settings, updates) 604 return multi_window
605 - def updateProfile2(self, settings, updates):
606 self.updateProfile(settings, updates, True)
607 - def face(self, name):
608 """Returns a child L{Face} by name.""" 609 return self.data[name].face
610 - def index(self, name):
611 """Returns the index of a face name.""" 612 for i, f in enumerate(self.faces): 613 if f.face_name == name: 614 return i 615 raise KeyError(name)
616 - def insert_face(self, i, face, showing=None):
617 """Inserts a child face at index i.""" 618 if showing is None: 619 if self.profile['Showing'][face.face_name].data: 620 show = bool(self.profile['Showing'][face.face_name].data[0]) 621 else: 622 show = True 623 else: 624 show = showing 625 frac = self.profile['Fraction'][face.face_name].data and self.profile['Fraction'][face.face_name].data[0] or 0 626 self.faces.insert(i, face) 627 self.data[face.face_name] = Anon(face=face, showing=show, fraction=frac) 628 self.__dict__[SafeName(face.face_name)] = face 629 self.__explore_featured.append(SafeName(face.face_name)) 630 face.parent_window = self.parent_window 631 face.pop_prefs = self.pop_prefs 632 face.pop_info = PopInfo(self, face) 633 face.pop_info.pop_prefs = self.pop_prefs 634 if show: 635 face.showing = self.showing 636 self.__relayout(retab=True, add_name=face.face_name) 637 self.OnToggleFace(self, face, show)
638 - def append_face(self, face, showing=None):
639 """Inserts a child face at the end.""" 640 if face.face_name in self.data: 641 self.remove_face(self.index(face.face_name)) 642 self.insert_face(len(self.faces), face, showing=showing)
643 - def remove_face(self, ix):
644 """Removes the child face at index ix.""" 645 face = self.faces[ix] 646 data = self.data[face.face_name] 647 self.__explore_featured.remove(SafeName(face.face_name)) 648 if face.pop_info.state: 649 face.pop_info.state = FACE_POP_INSIDE 650 if face.showing: 651 self.OnToggleFace(self, face, False) 652 face.showing = False 653 del self.faces[ix] 654 del self.data[face.face_name] 655 self.__relayout(retab=True) 656 face.parent_window = None 657 face.pop_prefs = None 658 face.pop_info = None
659 - def show_face(self, name, showing=True):
660 """Brings the named face onscreen (or hides).""" 661 data = self.data[name] 662 if data.showing != showing: 663 data.showing = showing 664 add_name = rem_name = None 665 if data.face.pop_info.state: 666 if showing: 667 data.face.pop_info.state = FACE_POP_WINDOW 668 data.face.pop_info.window.present() 669 else: 670 data.face.pop_info.state = FACE_POP_MINIMIZED 671 else: 672 if self.showing: 673 self.data[name].face.showing = showing 674 if showing: 675 add_name = name 676 else: 677 rem_name = name 678 self.__relayout(add_name=add_name, rem_name=rem_name) 679 self.tabs[self.index(name)].color = COLOR_TOOLTAB(showing) 680 self.profile['Showing'][name].data = int(showing) 681 self.OnToggleFace(self, self.data[name].face, showing)
682 - def onShow(self, showing):
683 for info in self.data.itervalues(): 684 if not (info.face.pop_info.state) and info.showing: 685 info.face.showing = showing
686 - def __onDrawTabsBG(self, context, w, h):
687 context.set_source_rgba(*self.tabspace.appearance.color(COLOR_TOOLTAB_BG)) 688 context.paint()
689 - def update_from_fractions(self):
690 for data in self.data_showing: 691 self.profile['Fraction'][data.face.face_name].data = data.fraction 692 self.__relayout(renest=False)
693 - def __relayout(self, retab=False, renest=True, add_name=None, rem_name=None):
694 self.__repositioning = True 695 faces_showing = [face for face in self.faces if ((not face.pop_info.state) and self.data[face.face_name].showing)] 696 # redo the sidebar of tabs 697 if retab: 698 for sub in self.tabs: 699 self.tabLayer.remove_sublayer(sub) 700 for sub in self.pops: 701 self.tabLayer.remove_sublayer(sub) 702 for sub in self.widgets: 703 self.tabLayer.remove_sublayer(sub) 704 y = 0 705 self.tabs = [] 706 self.pops = [] 707 self.widgets = [] 708 for face in self.faces: 709 sub = SubLayer_Label(face.face_name, x=0, y=y, w=TOOLTOGGLE_WIDTH_EM, h=TOOLTOGGLE_TABHEIGHT_EM, 710 color=COLOR_TOOLTAB(face in faces_showing), rotate=pi/2, border=TOOLTOGGLE_TAB_BORDER, 711 action=self.ref(bind(self.__onClickTab, face.face_name))) 712 self.tabLayer.add_sublayer(sub) 713 self.tabs.append(sub) 714 pop = SubLayer_PopIcon(x=TOOLTOGGLE_WIDTH_EM, y=y, w=TOOLTOGGLE_WIDTH_EM, h=TOOLTOGGLE_WIDTH_EM, 715 border=TOOLTOGGLE_TAB_BORDER, 716 tooltip="Show %s panel in its own window" % face.face_name, 717 action=self.ref(bind(self.__onTogglePopped, face))) 718 self.tabLayer.add_sublayer(pop) 719 self.pops.append(pop) 720 closer = SubLayer_Label('-', 0, 1, x=TOOLTOGGLE_WIDTH_EM, y=y+TOOLTOGGLE_WIDTH_EM, w=TOOLTOGGLE_WIDTH_EM, h=TOOLTOGGLE_WIDTH_EM, 721 color=COLOR_TOOLTAB_CLOSE, hover_color=COLOR_TOOLTAB_CLOSE_HOVER, 722 border=TOOLTOGGLE_TAB_BORDER, 723 tooltip="Hide %s panel" % face.face_name, 724 action=self.ref(bind(self.__onClickClose, face.face_name))) 725 self.tabLayer.add_sublayer(closer) 726 self.widgets.append(closer) 727 widget_y = y + 2*TOOLTOGGLE_WIDTH_EM 728 for widget in face.tab_sublayers: 729 widget.x = TOOLTOGGLE_WIDTH_EM 730 widget.y = widget_y 731 self.tabLayer.add_sublayer(widget) 732 self.widgets.append(widget) 733 widget_y += TOOLTOGGLE_WIDTH_EM 734 y += TOOLTOGGLE_TABHEIGHT_EM 735 self.tabLayer.h_min = y + TOOLTOGGLE_TABHEIGHT_EM # extra tab for 'show/hide:' 736 if renest: 737 # add/rem face: distribute delta fraction among showing faces 738 if add_name: 739 if len(faces_showing) == 1: 740 self.data[add_name].fraction = add_frac = 1.0 741 else: 742 add_frac = self.data[add_name].fraction or (1.0 / max(1, len(faces_showing))) 743 self.data[add_name].fraction = add_frac 744 self.profile['Fraction'][add_name].data = add_frac 745 for data in self.data_showing: 746 data.fraction *= (1 - add_frac) 747 if rem_name and faces_showing: 748 rescale = 1.0 / (1 - self.data[rem_name].fraction) 749 for data in self.data_showing: 750 if data.face.face_name != rem_name: 751 data.fraction *= rescale 752 if add_name or (rem_name and faces_showing): 753 for data in self.data_showing: 754 self.profile['Fraction'][data.face.face_name].data = data.fraction 755 756 # remove prior nested layout 757 if self.toolRoot: 758 for split in reversed(self.splits): 759 split.remove(split.get_child1()) 760 split.remove(split.get_child2()) 761 self.toolBox.remove(self.toolRoot) 762 self.splits = [] 763 # and re-establish layout 764 self.toolRoot = make_nested_layout(faces_showing, self.splits, self.__on_notify_position) 765 self.splits_pos = [split.get_position() for split in self.splits] 766 if self.toolRoot: 767 self.toolBox.pack_start(self.toolRoot, True, True) 768 self.data_showing = [self.data[face.face_name] for face in faces_showing] 769 set_nested_proportions([data.fraction for data in self.data_showing], self.splits) 770 gobject.idle_add(self.__reenable_repos)
771 - def __reenable_repos(self):
772 self.__repositioning = False
773 - def __onClickTab(self, name):
774 self.show_face(name, True) # not self.data[name].showing)
775 - def __onClickClose(self, name):
776 self.show_face(name, False)
777 - def __onTogglePopped(self, face):
778 if face.pop_info.state: 779 face.pop_info.state = 0 780 self.show_face(face.face_name) 781 else: 782 face.pop_info.state = 1
783 - def __on_notify_position(self, paned, param_spec):
784 if self.__repositioning: return 785 frac = [0.0] * len(self.data_showing) 786 read_nested_proportions(frac, self.splits) 787 for i, f in enumerate(frac): 788 self.data_showing[i].fraction = f 789 self.profile['Fraction'][self.data_showing[i].face.face_name].data = f
790 #ix = self.splits.index(paned) + 1 791 #if abs(self.splits_pos[ix-1] - self.splits[ix-1].get_position()) < TOOLTOGGLE_MIN_REPOS_PIX: 792 # return 793 ## distribute delta evenly among all faces above/below 794 #frac_pre = [data.fraction for data in self.data_showing] 795 #frac_post = frac_pre[:] # will overwrite; just need same shape 796 #read_nested_proportions(frac_post, self.splits) 797 #self.splits_pos = [split.get_position() for split in self.splits] 798 #print frac_pre 799 #print frac_post 800 #print 801 #scale = sum(frac_post[:ix]) / sum(frac_pre[:ix]) 802 #for i in xrange(ix): 803 # self.data_showing[i].fraction = frac_pre[i] * scale 804 #scale = sum(frac_post[ix:]) / sum(frac_pre[ix:]) 805 #for i in xrange(ix,len(frac_post)): 806 # self.data_showing[i].fraction = frac_pre[i] * scale 807 #self.__repositioning = True 808 #gobject.idle_add(self.__relayout_frac_only) 809 # def __relayout_frac_only(self): 810 # self.__relayout(renest=False)
811 - def __on_size_allocate(self, *args):
812 if not self.window: 813 return 814 alloc = self.get_allocation() 815 wh = (alloc.width, alloc.height) 816 if wh != self.__wh: 817 self.__wh = wh 818 self.__relayout(renest=False)
819 - def __onSetFontSize(self, fontsize):
820 self.tabspace.set_size_request(int(2*self.tabspace.appearance.emsize*TOOLTOGGLE_WIDTH_EM), -1)
821 # resize?
822 - def pop_out(self, face):
823 self.pops[self.index(face.face_name)].popped = True 824 if self.data[face.face_name].showing: 825 self.__relayout(rem_name=face.face_name)
826 - def pop_in(self, face, show=False):
827 self.pops[self.index(face.face_name)].popped = False 828 if self.data[face.face_name].showing: 829 self.__relayout(add_name=face.face_name)
830 - def unpop_all(self, root=None):
831 r = self if (root is None) else root 832 for face in r.faces: 833 if hasattr(face, 'faces') and face.faces: 834 self.unpop_all(face) 835 face.pop_info.state = FACE_POP_INSIDE
836 837 838
839 -def make_nested_layout(faces, splits, notify_position, i=0):
840 if len(faces) == 0: 841 return None 842 elif i == (len(faces)-1): 843 return faces[i] 844 else: 845 split = gtk.VPaned() 846 split.connect("notify::position", notify_position) 847 splits.append(split) 848 split.pack1(faces[i], True, True) 849 split.pack2(make_nested_layout(faces, splits, notify_position, i+1), True, True) 850 split.show() 851 return split
852
853 -def read_nested_proportions(fractions, splits, i=0, k=1.0):
854 if i == len(splits): 855 fractions[i] = k 856 else: 857 frac_local = splits[i].get_position() * 1.0 / splits[i].get_allocation()[3] 858 fractions[i] = max(1e-5, frac_local * k) 859 read_nested_proportions(fractions, splits, i+1, k*(1-frac_local))
860
861 -def set_nested_proportions(fractions, splits, i=0, k=1.0):
862 if i == len(splits): 863 return 864 frac_local = k * fractions[i] 865 splits[i].set_position(int(frac_local * splits[i].get_allocation()[3])) 866 set_nested_proportions(fractions, splits, i+1, frac_local and k/((1 - frac_local) or 1) or k)
867 868 # hbox[ 869 # vs0[ 870 # panel0 871 # vs1[ 872 # panel1 873 # panel2 874 # ] 875 # ] 876 # ] 877
878 -class TextFace(Face):
879 """Shows a scrolling text area(s). 880 881 @ivar textView: gtk.TextView 882 @ivar textViews: list of gtk.TextView 883 @ivar textOut: L{qubx.GTK.TextViewAppender} 884 @ivar textOuts: list of textOut 885 """ 886 887 __explore_featured = ['textViews', 'textOuts', 'textView', 'textOut', 888 'clear', 'write', 'set_text', 'get_text'] 889
890 - def __init__(self, name, global_name="", add_panes=[]):
891 super(TextFace, self).__init__(name, global_name) 892 self.textViews = [] 893 self.textOuts = [] 894 for i in xrange(len(add_panes)+1): 895 self.textView = qubx.GTK.HyperTextView() 896 self.scroll = pack_scrolled(self.textView, self, size_request=(40, 40), expand=True, at_end=True) 897 self.textOut = TextViewAppender(self.textView) 898 self.textViews.insert(0, self.textView) # self.textView == self.textViews[0] 899 self.textOuts.insert(0, self.textOut) 900 if i < len(add_panes): 901 line = pack_item(gtk.HBox(), self, at_end=True) 902 pack_label(add_panes[i], line)
903 - def clear(self, pane=0):
904 """Erases all the text.""" 905 self.textViews[pane].get_buffer().set_text('')
906 - def write(self, x, pane=0):
907 """Appends str(x).""" 908 self.textOuts[pane].write(str(x))
909 - def set_text(self, x, scroll_top=True, pane=0):
910 """Replaces all the text with str(x). 911 @param scroll_top: if True, moves the insertion point before the first char 912 """ 913 self.clear(pane=pane) 914 self.write(str(x), pane=pane) 915 if scroll_top: 916 b = self.textViews[pane].get_buffer() 917 b.place_cursor(b.get_start_iter()) 918 self.textViews[pane].scroll_to_mark(b.get_insert(), 0)
919 - def get_text(self, pane=0):
920 b = self.textViews[pane].get_buffer() 921 return b.get_text(b.get_start_iter(), b.get_end_iter())
922 923 924 COLOR_MENU_TEXT = ('faces.menu.text', (0,1,0,.75)) 925 ColorInfo[COLOR_MENU_TEXT[0]].label = 'Default files menu text' 926 COLOR_MENU_FILE = ('faces.menu.file', (0,1,0,1)) 927 ColorInfo[COLOR_MENU_FILE[0]].label = 'Default file menu triangle' 928
929 -class FilesFace(Face):
930 """Base class for a widget that manages multiple open files; e.g Data, Model. 931 Provides a menu of open files, file switching, a file menu, open and save dialogs, 932 933 @ivar file: object with fields "path", "save(path)", "saveAs(path)", "revert_to_saved()" 934 @ivar index: index of currently showing file 935 @ivar view: L{ToolSpace} for editing file; must have .controls as primary LayerSet 936 @ivar table: L{qubx.ObjectTable} containing files 937 @ivar views: list of each file's view 938 @ivar names: list of each file's name 939 @ivar lbls: list of each file's tab's gtk.Label 940 @ivar page0: a blank page shown when no file is open, constructed by ViewClass() 941 @ivar OnSwitch: L{WeakEvent}(FilesFace, file) called when index changes 942 @ivar OnChange: L{WeakEvent}(FilesFace, file) called when index changes or file is edited 943 @ivar OnSave: L{WeakEvent}(FilesFace, file, path) called after successful save 944 @ivar OnClosing: L{WeakEvent}(index) before close 945 """ 946 947 __explore_featured = ['file', 'index', 'view', 'table', 'views', 'names', 'page_list', 'path', 'lbls', 'page0', 948 'OnSwitch', 'OnChange', 'OnSave', 'OnClosing', 'ViewClass', 949 'mnuFiles', 'layFiles', 'layMenu', 'make_view', 'make_page', 950 'saveAs', 'close', 'close_one', 'on_pre_close', 'save', 'on_pre_save', 951 'promptSave', 'close_down', 'show_file', 'add_recent', 'new', 'open', 952 'is_changed', 'can_save', 'can_revert', 'init_file', 'done_file', 953 'get_open_filters', 'get_save_filters', 'mnuFile', 'mnuRecent'] 954
955 - def __init__(self, name, table, ViewClass, menu_width=32, popup_color=COLOR_MENU_FILE, global_name=""):
956 """ 957 @param name: the name of this face; e.g. Data 958 @param table: a L{qubx.ObjectTable} of files 959 @param ViewClass: class or function(files_table) which instantiates a ToolSpace, subsequently assigned .file 960 @param menu_width: the file switcher is this many 'M's wide, at lower-left 961 @param popup_color: COLORREF of the inscribed-triangle File menu 962 """ 963 Face.__init__(self, name, global_name) 964 self.__ref = Reffer() 965 self.ViewClass = ViewClass 966 self.table = table 967 self.table.OnInsert += self.__ref(self.__onInsert) 968 self.table.OnRemoved += self.__ref(self.__onRemoved) 969 self.table.OnSelect += self.__ref(self.__onSelect) 970 self.__buildMenu() 971 self.__recent = qubx.settings.SettingsMgr["%s.Recent" % name].active 972 973 self.path = qubx.pyenv.env.globals['documents_path'] 974 self._file = None 975 self._index = -1 976 self._view = None 977 self.views = [] 978 self.names = [] 979 self.lbls = [] 980 self.page_list = [] 981 self._no_to_all = False 982 983 self.OnSwitch = WeakEvent() # (self, file) when switch 984 self.OnChange = WeakEvent() # (self, file) when switch or edit 985 self.OnSave = WeakEvent() 986 self.OnClosing = WeakEvent() 987 988 self.layFiles = Layer( 1, -4, menu_width, 3) 989 self.mnuFiles = SubLayer_DropDown('<no file>', 0, 1, COLOR_MENU_TEXT, 990 x=1, y=.5, w=menu_width-2, h=2) 991 self.layFiles.add_sublayer(self.mnuFiles) 992 993 self.layMenu = Layer( menu_width+2, -4, 2, 3, COLOR_CLEAR) 994 self.layMenu.add_sublayer(SubLayer_Popup_PNG(self.mnuFile, os.path.join(qubx.global_namespace.app_path, 'icons', 'folder.png'), y=.5, w=2, h=2, tooltip="File menu", 995 on_popup=self.__ref(self.on_popup_file))) 996 self.tab_sublayers.append(SubLayer_Popup_PNG(self.mnuFile, os.path.join(qubx.global_namespace.app_path, 'icons', 'folder.png'), 997 w=TOOLTOGGLE_WIDTH_EM, h=TOOLTOGGLE_WIDTH_EM, border=TOOLTOGGLE_TAB_BORDER, 998 on_popup=self.__ref(self.on_popup_file), 999 tooltip="%s: File menu" % name)) 1000 1001 self.pages = pack_item(gtk.Notebook(), self, expand=True) 1002 self.pages.connect('switch_page', self._onSwitchPage) 1003 self.pages.set_show_border(True) 1004 self.pages.set_show_tabs(False) 1005 1006 self.page0 = ViewClass(None) 1007 self.page0.tool = None 1008 self.page0.controls.add_layer(self.layFiles) 1009 self.page0.controls.add_layer(self.layMenu) 1010 self.pages.insert_page(self.page0, None, 0) 1011 self.page0.show() 1012 self.page0.set_size_request(70, 70) 1013 1014 self._sensitize()
1015 1016 file = property(lambda self: self._file, lambda self, x: self.set_file(x)) 1017 index = property(lambda self: self._index, lambda self, x: self.set_index(x)) 1018 view = property(lambda self: self._view) 1019
1020 - def __buildMenu(self):
1021 self.mnuFile = gtk.Menu() 1022 self.itemNew = build_menuitem("New", self.__ref(self._onItemNew), menu=self.mnuFile) 1023 self.itemOpen = build_menuitem("Open...", self.__ref(self._onItemOpen), menu=self.mnuFile) 1024 self.mnuRecent = gtk.Menu() 1025 self.itemOpenRecent = build_menuitem("Open recent", submenu=self.mnuRecent, menu=self.mnuFile) 1026 self.itemSave = build_menuitem("Save", self.__ref(self._onItemSave), menu=self.mnuFile) 1027 self.itemSaveAs = build_menuitem("Save as...", self.__ref(self._onItemSaveAs), menu=self.mnuFile) 1028 self.itemRevert = build_menuitem('Revert to saved', self.__ref(self._onItemRevert), menu=self.mnuFile) 1029 self.itemClose = build_menuitem("Close", self.__ref(self._onItemClose), menu=self.mnuFile) 1030 self.itemCloseAll = build_menuitem("Close all", self.__ref(self._onItemCloseAll), menu=self.mnuFile)
1031 # self.itemPrint = build_menuitem("Print...", self.__ref(self._onItemPrint), menu=self.mnuFile) 1032
1033 - def _sensitize(self):
1034 if self.index >= 0: 1035 self._view = self.views[self.index] 1036 else: 1037 self._view = self.page0 1038 self.itemSave.set_sensitive(self.can_save()) 1039 self.itemRevert.set_sensitive(self.can_revert()) 1040 self.itemSaveAs.set_sensitive(not (self._file is None)) 1041 self.itemClose.set_sensitive(not (self._file is None)) 1042 self.itemCloseAll.set_sensitive(not (self._file is None)) 1043 self.layMenu.space = self._view 1044 self.layFiles.space = self._view
1045 # self.itemPrint.set_sensitive(not (self._file is None))
1046 - def on_popup_file(self):
1047 self.mnuRecent.foreach(lambda item: self.mnuRecent.remove(item)) 1048 for node in qubx.tree.children(self.__recent): 1049 if os.path.exists(str(node.data)): 1050 build_menuitem(gtk_literally(str(node.data)), self.__ref(bind(self.open, str(node.data))), menu=self.mnuRecent) 1051 elif os.path.exists(node.name): 1052 build_menuitem(gtk_literally(node.name), self.__ref(bind(self.open, node.name)), menu=self.mnuRecent) 1053 return True
1054 - def set_file(self, x):
1055 self.index = self.table.entries.index(x)
1056 - def set_index(self, x, force=False):
1057 if (x == self._index) and not force: return 1058 if self._file: 1059 self.done_file(self._file, self._view) 1060 if x >= 0: 1061 self._file = self.table.entries[x] 1062 self._index = x 1063 self.pages.set_current_page(x+1) 1064 self.mnuFiles.label = self.names[x] 1065 self.init_file(self._file, self.views[x]) 1066 else: 1067 self._file = self.page0.file 1068 self._index = -1 1069 self.pages.set_current_page(0) 1070 self.mnuFiles.label = '<no file>' 1071 self.init_file(self.page0.file, self.page0) 1072 self._sensitize() 1073 self.layMenu.space = self.view or self.page0 1074 self.layFiles.space = self.view or self.page0 1075 self.OnSwitch(self, self.file) 1076 self.OnChange(self, self.file)
1077 - def __onInsert(self, i, undoing):
1078 file = self.table.entries[i] 1079 self.views.insert(i, self.make_view(self.table)) 1080 self.names.insert(i, os.path.split(self.table.entries[i].path or 'Untitled')[1]) 1081 self.lbls.insert(i, gtk.Label(self.names[i])) 1082 self.mnuFiles.menu.insert(i, (self.names[i], lambda: self.set_file(file))) 1083 self.views[i].file = file 1084 self.views[i].mnuFile = self.mnuFile 1085 self.views[i].mnuFiles = self.mnuFiles 1086 self.views[i].controls.add_layer(self.layMenu) 1087 self.views[i].controls.add_layer(self.layFiles) 1088 self.page_list.insert(i, self.make_page(self.views[i])) 1089 self.pages.insert_page(self.page_list[i], self.lbls[i], i+1) 1090 self._sensitize() 1091 if os.path.exists(file.path): 1092 self.add_recent(file.path)
1093 - def make_view(self, table):
1094 return self.ViewClass(table)
1095 - def make_page(self, view):
1096 view.show() 1097 return view
1098 - def __onRemoved(self, i, undoing):
1099 j = None 1100 if i == self.index: 1101 j = i - 1 1102 if j < 0 and self.table.size > 1: 1103 j = 0 1104 self.pages.remove_page(i+1) 1105 self.views[i].controls.remove_layer(self.layMenu) 1106 self.views[i].controls.remove_layer(self.layFiles) 1107 del self.mnuFiles.menu[i] 1108 del self.names[i] 1109 del self.lbls[i] 1110 del self.views[i] 1111 del self.page_list[i] 1112 self.set_index(j, force=True) # in case index stays same but refers to new file 1113 self._sensitize()
1114 - def __onSelect(self, i, field_name, sender):
1115 if (not (i is None)) and (sender != self): 1116 self.index = i 1117 self._sensitize()
1118 - def _onSwitchPage(self, pages, page, page_num):
1119 self.index = page_num-1
1120 # disabled because obnoxious: 1121 #if self.index >= 0: 1122 # self.table.select(page_num-1, None, self)
1123 - def _onItemNew(self, item):
1124 self.new()
1125 - def _onItemOpen(self, item):
1126 fname = qubx.GTK.Open('Open...', self.path, self.open, self.get_open_filters(), allow_all_files=True) 1127 if fname: 1128 self.path = os.path.split(fname)[0]
1129 - def _onItemSave(self, item):
1130 try: 1131 self.save() 1132 #self.file.saveAs(self.file.path) 1133 self.OnSave(self, file, self.file.path) 1134 except IOError, e: 1135 qubx.GTK.ShowMessage("The file couldn't be saved. Try a different name or location.\n\n%s" % str(e), title="Error", parent=self.parent_window)
1136 - def _onItemSaveAs(self, item):
1137 self.saveAs()
1138 - def saveAs(self, fname=None):
1139 if fname: 1140 self.save(fname) 1141 else: 1142 name = os.path.split(self.file.path)[1] 1143 name = name.replace('<', '').replace('>', '') 1144 fname = qubx.GTK.SaveAs('Save as...', self.path, name, self.save, self.get_save_filters())
1145 - def _onItemRevert(self, item):
1146 dlg = gtk.MessageDialog(None, buttons=gtk.BUTTONS_NONE, 1147 flags=gtk.DIALOG_MODAL, 1148 message_format='Revert: discard all changes to %s?' % os.path.split(self.file.path)[1]) 1149 dlg.add_buttons('Yes',gtk.RESPONSE_YES, 1150 'No',gtk.RESPONSE_NO) 1151 response = dlg.run() 1152 dlg.destroy() 1153 if response == gtk.RESPONSE_YES: 1154 if self.global_name: 1155 qubx.pyenv.env.OnScriptable('%s.file.revert_to_saved()' % self.global_name) 1156 self.file.revert_to_saved()
1157 - def _onItemClose(self, item):
1158 self.close()
1159 - def _onItemCloseAll(self, item):
1160 node = qubx.settings.SettingsMgr['%s.FilesFace'%self.face_name.strip()].active['ask_close_all'] 1161 if not node.data: 1162 node.data = 1 1163 if node.data[0]: 1164 dlg = qubx.GTK.AskOnceDialog('Close All %s?' % self.face_name.strip(), qubx.GTK.get_active_window(), 1165 """Really close all %s files?""" % self.face_name.strip(), 1166 (("No", gtk.RESPONSE_REJECT), 1167 ("Yes", gtk.RESPONSE_ACCEPT)), 1168 dont_show_again=False) 1169 response, dont_show = dlg.run() 1170 dlg.destroy() 1171 if response != gtk.RESPONSE_ACCEPT: 1172 return 1173 node.data = int(not dont_show) 1174 self.close_down()
1175 - def _onItemPrint(self, item):
1176 self.do_print()
1177 - def close(self):
1178 self._no_to_all = False 1179 return self.close_one()
1180 - def close_one(self):
1181 if self.promptSave(): 1182 file, view = self.file, self.view 1183 self.OnClosing(self.index) 1184 self.on_pre_close() 1185 file.pre_close() 1186 self.table.remove(self.index) 1187 file.dispose() 1188 view.dispose() 1189 qubx.pyenv.env.gc_collect_on_idle() 1190 return True 1191 return False
1192 - def on_pre_close(self):
1193 pass
1194 - def save(self, path=None):
1195 fname = self.file.path if (path is None) else path 1196 if (not fname) or (fname.lower() == 'untitled'): 1197 return self.saveAs(None) # prompt for destination 1198 for other_view in self.views: 1199 if other_view.file != self.file: 1200 if other_view.file.path == fname: 1201 raise Exception('The file at that location is in use. Please close it first.') 1202 self.on_pre_save(fname) 1203 self.file.saveAs(fname) 1204 self.path = os.path.split(fname)[0] or self.path 1205 self.OnSave(self, self.file, fname) 1206 self.add_recent(self.file.path)
1207 - def on_pre_save(self, fname):
1208 pass
1209 - def promptSave(self):
1210 """Returns True if no changes, or asks "save changes" then returns True unless they chose cancel.""" 1211 if self.is_changed() and not self._no_to_all: 1212 dlg = gtk.MessageDialog(qubx.GTK.get_active_window(), buttons=gtk.BUTTONS_NONE, 1213 flags=gtk.DIALOG_MODAL, 1214 message_format='Save changes to %s?' % (self.file.path and os.path.split(self.file.path)[1] or 'Untitled')) 1215 dlg.add_buttons('Yes',gtk.RESPONSE_YES, 1216 'No',gtk.RESPONSE_NO, 1217 'No to all',RESPONSE_NO_TO_ALL, 1218 'Cancel',gtk.RESPONSE_CANCEL) 1219 response = dlg.run() 1220 dlg.destroy() 1221 if response == gtk.RESPONSE_YES: 1222 try: 1223 if self.file.path: 1224 self.save() 1225 else: 1226 self.saveAs() 1227 except: 1228 traceback.print_exc() 1229 return self.promptSave() # ask again on failure 1230 elif response == RESPONSE_NO_TO_ALL: 1231 self._no_to_all = True 1232 elif response == gtk.RESPONSE_CANCEL: 1233 return False 1234 return True
1235 - def close_down(self):
1236 """Attempts to close all files; returns True if successful.""" 1237 self._no_to_all = False 1238 for i in reversed(xrange(self.table.size)): 1239 self.index = i 1240 if not self.close_one(): 1241 return False 1242 return True
1243 - def show_file(self, name):
1244 for i, view in enumerate(self.views): 1245 if (name == view.file.path) or (name == os.path.split(view.file.path)[1]): 1246 self.index = i 1247 return view.file 1248 return None
1249 - def add_recent(self, path):
1250 node = self.__recent.insert("") 1251 node.data = path 1252 node = node.sibling 1253 i = 1 1254 while not node.isNull: 1255 if (i >= RECENT_FILES_COUNT) or (str(node.data) == path) or (node.name == path): 1256 to_remove, node = node, node.sibling 1257 self.__recent.remove(to_remove) 1258 else: 1259 node = node.sibling 1260 i += 1
1261
1262 - def do_print(self):
1263 """unimplemented.""" 1264 pass
1265 #pr = gtk.PrintOperation() 1266 #pr.connect('draw_page', qubx.fitsGTK.PageDrawer(self.fitting.hires)) 1267 #pr.set_n_pages(1) 1268 #pr.set_unit(gtk.UNIT_POINTS) 1269 #res = pr.run(gtk.PRINT_OPERATION_ACTION_PRINT_DIALOG, self) 1270 #if res == gtk.PRINT_OPERATION_RESULT_APPLY: settings = pr.get_print_settings()
1271 - def new(self):
1272 """Override this method to create a new file and add it to table.""" 1273 pass
1274 - def open(self, path=None):
1275 """Override this method to open the file at path and add it to table.""" 1276 if path is None: 1277 self._onItemOpen(None)
1278 - def is_changed(self):
1279 """Override this method to report if the current file has been modified.""" 1280 return False
1281 - def can_save(self):
1282 """Override this method to report if the file can be saved.""" 1283 return False
1284 - def can_revert(self):
1285 """Override this method to report if the file can be reverted to saved.""" 1286 return False
1287 - def init_file(self, file):
1288 """Override this method to do things when the file comes onscreen (e.g. subscribe to its events).""" 1289 pass
1290 - def done_file(self, file, view):
1291 """Override this method to do things when the file goes offscreen.""" 1292 pass
1293 - def get_open_filters(self):
1294 """Override this method to return a list of (".extension", gtk.FileFilter) to add to the open file dialog.""" 1295 return []
1296 - def get_save_filters(self):
1297 """Override this method to return a list of (".extension", gtk.FileFilter) to add to the save file dialog; By default, uses the open_filters.""" 1298 return self.get_open_filters()
1299