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

Source Code for Module qubx.notebookGTK

   1  """GUI components to control notebook export of figures. 
   2   
   3  Copyright 2008-2014 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 cairo 
  23  import cgi 
  24  from cStringIO import StringIO 
  25  import datetime 
  26  import gc 
  27  import gobject 
  28  import gtk 
  29  from gtk import gdk 
  30  from gtk import keysyms 
  31  import os 
  32  import shutil 
  33  import sys 
  34  import traceback 
  35  import webbrowser 
  36  import qubx.faces 
  37  import qubx.GTK 
  38  import qubx.notebook 
  39  import qubx.pyenv 
  40  import qubx.toolspace 
  41  from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled, build_menuitem 
  42  from qubx.settings import Propertied, Property 
  43  from qubx.util_types import Anon, WeakEvent, Reffer, printer, bind_with_args_before 
  44  from math import * 
  45   
  46  # suppress bogus errors that cause trouble at program close under Windows (trying to write express.exe.log in program dir) 
  47  stdout, stderr = sys.stdout, sys.stderr 
  48  sys.stdout = sys.stderr = Anon(write=lambda s: None) 
  49  try: 
  50      import tifffile 
  51  except: # it complains, then it works? 
  52      traceback.print_exc() 
  53  sys.stdout, sys.stderr = stdout, stderr 
  54   
  55   
  56  import xml.etree.ElementTree # for tifffile+py2exe 
  57  import xml.etree.cElementTree # for tifffile+py2exe 
  58  import numpy 
  59   
  60  NB_SUBTABLE_FREEZE_MS = 5000 
  61   
  62   
  63  try: # for py2exe's sake, since it doesn't see the qubx_notebook_office plugin: 
  64      import pythoncom 
  65      import win32com.client 
  66       
  67      pythoncom.CoInitialize() 
  68  except: 
  69      pass 
  70   
  71   
  72  import itertools 
73 -def frange(start, end=None, inc=1.0):
74 "An xrange-like generator which yields float values" 75 # imitate range/xrange strange assignment of argument meanings 76 if end is None: 77 end = start + 0.0 # Ensure a float value for 'end' 78 start = 0.0 79 assert inc # sanity check 80 for i in itertools.count( ): 81 next = start + i * inc 82 if (inc>0.0 and next>=end) or (inc<0.0 and next<=end): 83 break 84 yield next
85
86 87 -def target_supports(target, item):
88 if isinstance(item, qubx.notebook.NbItems): 89 for sub in item: 90 if target_supports(target, sub): 91 return True 92 return False 93 for supp_typ in target.supports: 94 if isinstance(item, supp_typ): 95 return True 96 return False
97
98 -def draw_ellipse(ctx, x, y, width, height):
99 ctx.save() 100 ctx.translate(x + width / 2., y + height / 2.) 101 ctx.scale(width / 2., height / 2.) 102 ctx.arc(0., 0., 1., 0., 2 * pi) 103 ctx.stroke() 104 ctx.restore()
105
106 -def DrawNotebook(context, w, h, alpha):
107 ww = int(round(0.95 * min(w, h))) 108 hw = ww/2 109 context.translate((w - ww) / 2, (h - ww) / 2) 110 depth = hw/3 111 context.translate(0, depth) 112 sheets = max(2, int(round(depth / 1.2)) + 1) 113 sheet_depth = depth / (sheets - 1) 114 dx = ww*0.15 115 sheet_width = ww - dx - depth 116 perspective = 0.03 * sheet_width 117 context.set_line_width(0.9) 118 context.set_source_rgba(.8, .7, .4) 119 for x in frange(dx+0.05*sheet_width+perspective, dx+1.05*sheet_width-perspective, sheet_width/5.0): 120 draw_ellipse(context, x, -0.3*depth, 0.9*depth, 1.3*depth) 121 for index in reversed(xrange(sheets)): 122 context.save() 123 context.translate(index*sheet_depth, index*sheet_depth) 124 context.move_to(dx+sheet_width - perspective, 0) 125 context.line_to(sheet_width, ww - 2*depth - 2) 126 context.line_to(0, ww - 2*depth - 2) 127 context.line_to(dx+perspective, 0) 128 context.line_to(dx+sheet_width - perspective, 0) 129 context.set_source_rgba(1.0, .95, .9, alpha) 130 context.fill_preserve() 131 context.set_source_rgba(0, 0, 0, alpha) 132 context.set_line_width(0.8) 133 context.stroke() 134 context.restore() 135 context.set_line_width(1.15) 136 context.set_source_rgba(.4, .65, 1.0, alpha) 137 for y in frange(0.2*(ww-3*depth), ww-2*depth-3, 3.5): 138 shift = dx * (1.0 - y / (ww-2.*depth-2)) 139 context.move_to(shift + perspective, y) 140 context.line_to(shift + sheet_width - perspective, y) 141 context.stroke() 142 context.set_line_width(1.6) 143 context.set_source_rgba(1.0, .65, .5, alpha) 144 context.move_to(dx + perspective + sheet_width/5, 0) 145 context.line_to(sheet_width/5, ww-2*depth-2) 146 context.stroke()
147
148 -class SubLayer_Notebook(qubx.toolspace.SubLayer_Popup):
149 """A notebook icon, which shows a menu when clicked. 150 151 @ivar items: list of L{qubx.notebook.NbItem} 152 @ivar menuitems: list of gtk.MenuItem for the end of the menu 153 """
154 - def __init__(self, x, y, w, h, show_options_always=True, *args, **kw):
155 self.__ref = Reffer() 156 qubx.toolspace.SubLayer_Popup.__init__(self, x=x, y=y, w=w, h=h, 157 popup=gtk.Menu(), on_popup=self.__ref(self.__onPopup), tooltip='Send to Notebook...', 158 *args, **kw) 159 self.__show_options_always = show_options_always 160 self.items = [] 161 self.menuitems = []
162 - def set_items(self, x):
163 self.items = x
164 - def __onPopup(self):
165 if (len(self.items) == 0) and not self.__show_options_always: 166 return False 167 nb = qubx.notebook.Notebook 168 self.popup.foreach(lambda item: self.popup.remove(item)) 169 if self.items: 170 if nb.target.menu_caption: 171 build_menuitem(nb.target.menu_caption, menu=self.popup) 172 for nb_item in self.items: 173 if target_supports(nb.target.target, nb_item): 174 build_menuitem(' '+nb_item.mnu_caption, self.__ref(bind_with_args_before(self.__onSendDefault, nb_item)), menu=self.popup) 175 for ti, target in enumerate(nb.targets): 176 if (target == nb.target) or (not target.menu_caption): 177 continue 178 sub = gtk.Menu() 179 build_menuitem(target.menu_caption, submenu=sub, menu=self.popup) 180 for nb_item in self.items: 181 if target_supports(target.target, nb_item): 182 build_menuitem(nb_item.mnu_caption, self.__ref(bind_with_args_before(self.__onSend, ti, nb_item)), menu=sub) 183 for item in self.menuitems: 184 self.popup.append(item) 185 build_menuitem('Options...', self.__ref(self.__onOptions), menu=self.popup) 186 return True
187 - def __onSendDefault(self, item, nb_item):
188 self.layer.mouse_exit() 189 gobject.idle_add(qubx.notebook.Notebook.send, nb_item)
190 - def __onSend(self, item, target_ix, nb_item):
191 self.layer.mouse_exit() 192 gobject.idle_add(qubx.notebook.Notebook.send, nb_item, target_ix)
193 - def draw(self, context, w, h, appearance):
194 qubx.toolspace.SubLayer.draw(self, context, w, h, appearance) 195 if self.entered: 196 context.translate(-1, -1) 197 alpha = 1.0 198 else: 199 alpha = 0.7 200 DrawNotebook(context, self.w, self.h, alpha)
201 - def __onOptions(self, item):
202 QubX = qubx.pyenv.env.globals['QubX'] 203 QubX.Admin.Notebook.request_show()
204
205 -class Button_Notebook(qubx.toolspace.ToolSpace):
206 __explore_featured = ['scale', 'aspect', 'layer', 'sub', 'items', 'menuitems', 'do_popup']
207 - def __init__(self, scale=1.0, aspect=1.0, show_options_always=False):
208 qubx.toolspace.ToolSpace.__init__(self) 209 self.scale = scale 210 self.aspect = aspect 211 self.set_tooltip_text("Send to Notebook...") 212 self.__ref = Reffer() 213 qubx.toolspace.Appearance.OnSetFontSize += self.__ref(self.__onSetFontSize) 214 self.__onSetFontSize(qubx.toolspace.Appearance.font_size) 215 if aspect < 1: 216 w, h = 3*scale, 3*scale/aspect 217 else: 218 w, h = 3*scale*aspect, 3*scale 219 self.set_size_request(int(round(w*qubx.toolspace.Appearance.emsize)), int(round(h*qubx.toolspace.Appearance.emsize))) 220 self.layer = qubx.toolspace.Layer(x=0, y=0, w=w, h=h, cBG=qubx.toolspace.COLOR_CLEAR) 221 self.add_layer(self.layer) 222 self.sub = SubLayer_Notebook(x=0, y=0, w=w, h=h, show_options_always=show_options_always) 223 self.layer.add_sublayer(self.sub) 224 self.sub.tooltip = None
225 items = property(lambda self: self.sub.items, lambda self, x: self.sub.set_items(x)) 226 menuitems = property(lambda self: self.sub.menuitems, lambda self, x: self.sub.set_menuitems(x))
227 - def do_popup(self, *args, **kw):
228 self.sub.do_popup(*args, **kw)
229 - def __onSetFontSize(self, font_size):
230 w = int(round(3 * self.scale * qubx.toolspace.Appearance.emsize)) 231 if self.aspect < 1: 232 self.set_size_request(w, int(round(w/self.aspect))) 233 else: 234 self.set_size_request(int(round(w*self.aspect)), w)
235
236 @Propertied(Property('tiff_resolution', 1200, 'dots per inch')) 237 -class NotebookFace(qubx.faces.Face):
238 __explore_featured = ['icon', 'mnuTarget', 'txtTiffResolution', 'panControls', 'mnuPresets', 'btnNone', 'lstAuto', 'scroll']
239 - def __init__(self, name='Notebook', global_name='QubX.Admin.Notebook'):
240 qubx.faces.Face.__init__(self, name, global_name) 241 self.__ref = Reffer() 242 self.propertied_connect_settings('NotebookGTK') 243 self.icon = Button_Notebook(scale=0.7) 244 245 h = pack_item(gtk.HBox(), self) 246 pack_label('Send figures to:', h) 247 self.mnuTarget = pack_item(qubx.GTK.DynamicComboBox(), h, expand=True) 248 self.mnuTarget.OnPopulate += self.__ref(self.__onPopulateTarget) 249 self.mnuTarget.OnChanged += self.__ref(self.__onChangeTarget) 250 self.mnuTarget.choose(qubx.notebook.Notebook.target.caption) 251 252 h = pack_item(gtk.HBox(), self) 253 pack_label('TIFF resolution (dots per inch):', h) 254 self.txtTiffResolution = pack_item(qubx.GTK.NumEntry(self.tiff_resolution, qubx.accept.acceptIntGreaterThan(0), width_chars=5), h) 255 self.propertied_connect_NumEntry('tiff_resolution', self.txtTiffResolution) 256 257 self.panControls = pack_item(gtk.VBox(), self, expand=True) 258 if qubx.notebook.Notebook.target.controls: 259 self.panControls.pack_start(qubx.notebook.Notebook.target.controls, True, True) 260 qubx.notebook.Notebook.target.controls.show() 261 262 qubx.notebook.Notebook.register_auto('Scriptable', 'Record of your actions, as scripts', False) 263 qubx.pyenv.env.OnScriptable += self.__ref(self.__onScriptable) 264 265 h = pack_item(gtk.HBox(), self) 266 pack_label('Automatic items:', h) 267 self.mnuPresets = pack_item(qubx.settingsGTK.PresetsMenu('qubx.notebook.NbController.auto', 268 qubx.pyenv.env.globals['QubX'].appname, 269 qubx.pyenv.env.globals['QubX']), 270 h, at_end=True) 271 self.btnNone = pack_button('None', h, at_end=True, on_click=self.__onClickNone) 272 self.lstAuto = qubx.GTK.CheckList() 273 self.lstAuto.OnToggle += self.__ref(self.__onToggleAuto) 274 self.scroll = pack_scrolled(self.lstAuto, self, expand=True) 275 for i, auto in enumerate(sorted(qubx.notebook.Notebook.auto.keys())): 276 rec = qubx.notebook.Notebook.auto[auto] 277 self.__onAutoAdded(i, rec) 278 279 qubx.notebook.Notebook.OnChangeTargetIx += self.__ref(self.__onTargetIxChanged) 280 qubx.notebook.Notebook.OnAddAuto += self.__ref(self.__onAutoAdded) 281 qubx.notebook.Notebook.OnChangeAuto += self.__ref(self.__onAutoChanged)
282 - def __onScriptable(self, expr):
283 qubx.notebook.Notebook.send(qubx.notebook.NbText(">>> %s" % expr), auto_id='Scriptable')
284 - def __onTargetIxChanged(self, ix):
285 self.mnuTarget.choose(qubx.notebook.Notebook.target.caption) 286 self.panControls.foreach(lambda child: self.panControls.remove(child)) 287 if qubx.notebook.Notebook.target.controls: 288 self.panControls.pack_start(qubx.notebook.Notebook.target.controls, True, True) 289 qubx.notebook.Notebook.target.controls.show()
290 - def __onAutoAdded(self, ix, auto):
291 self.lstAuto.insert(ix, auto.active, auto.mnu_caption)
292 - def __onAutoChanged(self, ix, auto):
293 # ignore any caption change 294 self.lstAuto.set_active(ix, auto.active)
295 - def __onToggleAuto(self, index, active):
296 auto_id = sorted(qubx.notebook.Notebook.auto.keys())[index] 297 qubx.notebook.Notebook.set_auto_active(auto_id, active)
298 - def __onClickNone(self, btn):
299 for auto_id in qubx.notebook.Notebook.auto: 300 qubx.notebook.Notebook.set_auto_active(auto_id, False)
301 - def __onPopulateTarget(self, add):
302 for target in qubx.notebook.Notebook.targets: 303 add(target.caption)
304 - def __onChangeTarget(self, mnu, active_text):
305 qubx.notebook.Notebook.target_ix = self.mnuTarget.active_i 306 qubx.pyenv.env.OnScriptable('Notebook.target_ix = %i' % self.mnuTarget.active_i)
307
308 -class NbEntryFace(qubx.faces.Face):
309 __explore_featured = ['txtNote', 'scroll', 'write']
310 - def __init__(self, name='NotebookEntry', global_name=""):
311 qubx.faces.Face.__init__(self, name, global_name) 312 self.set_size_request(120, 80) 313 h = pack_item(gtk.HBox(), self) 314 pack_space(h, expand=True) 315 self.btnNotebook = pack_item(Button_Notebook(scale=0.7, show_options_always=True), h) 316 pack_space(h, expand=True) 317 pack_label('Write to Notebook:', h) 318 pack_space(h, expand=True) 319 320 self.txtNote = gtk.TextView() 321 self.txtNote.connect('key_press_event', self.__onKeyPress) 322 self.scroll = pack_scrolled(self.txtNote, self, expand=True) 323 324 h = pack_item(gtk.HBox(), self, at_end=True) 325 self.btnWrite = pack_button('Write', h, at_end=True, on_click=self.__onClickWrite)
326 - def __onClickWrite(self, btn):
327 self.write()
328 - def __onKeyPress(self, txt, event):
329 if event.keyval == keysyms.Return: 330 self.write() 331 return True
332 - def write(self, what=None):
333 if not (what is None): 334 msg = what 335 else: 336 buf = self.txtNote.get_buffer() 337 msg = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) 338 buf.set_text("") 339 if msg: 340 qubx.notebook.Notebook.send(qubx.notebook.NbText(msg))
341
342 343 -class NbPicture(qubx.notebook.NbItem):
344 - def __init__(self, mnu_caption, global_name, get_shape, draw, save_png=None, get_pixbuf=None):
345 the_save_png = save_png or self.__save_png 346 the_get_pixbuf = get_pixbuf or self.__get_pixbuf 347 qubx.notebook.NbItem.__init__(self, mnu_caption=mnu_caption, to_txt=self.__to_txt, global_name=global_name, 348 get_shape=get_shape, draw=draw, save_png=the_save_png, 349 get_pixbuf=the_get_pixbuf)
350 - def __to_txt(self):
351 w, h = self.get_shape() 352 return "[%s: Picture (%d x %d)]" % (self.mnu_caption, w, h)
353 - def __save_png(self, path):
354 w, h = self.get_shape() 355 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) 356 context = cairo.Context(surface) 357 self.draw(context, w, h) 358 surface.write_to_png(path)
359 - def __get_pixbuf(self):
360 w, h = self.get_shape() 361 screen = gdk.screen_get_default() 362 colormap = screen.get_rgba_colormap() 363 depth = 32 364 if colormap is None: 365 colormap = screen.get_rgb_colormap() 366 depth = 24 367 pixmap = gdk.Pixmap(None, w, h, depth) 368 pixmap.set_colormap(colormap) 369 ctx = pixmap.cairo_create() 370 self.draw(ctx, w, h) 371 del ctx 372 pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, w, h) 373 pixbuf.get_from_drawable(pixmap, colormap, 0, 0, 0, 0, -1, -1) 374 del pixmap 375 return pixbuf
376
377 378 -class NbTarget_SaveAs(object):
379 - def __init__(self):
380 self.supports = [qubx.notebook.NbText, 381 qubx.notebook.NbDynText, 382 qubx.notebook.NbTable, 383 qubx.notebook.NbChart, 384 qubx.notebook.NbTrace, 385 NbPicture] 386 self.path = qubx.pyenv.env.globals['documents_path']
387 - def nb_init(self):
388 pass
389 - def nb_send(self, item):
390 fname = None 391 if isinstance(item, qubx.notebook.NbItems): 392 for it in item: 393 self.nb_send(it) 394 elif isinstance(item, NbPicture): 395 fname = qubx.GTK.SaveAs('Save %s as...'%item.mnu_caption, self.path, item.mnu_caption, 396 qubx.util_types.bind_with_args(self.save_image, item), 397 parent=qubx.GTK.get_active_window(), 398 filters=[('TIFF files', '.tiff'), ('TIFF (black and white)', '.tif'), ('PNG files', '.png')]) 399 else: 400 fname = qubx.GTK.SaveAs('Save %s as...'%item.mnu_caption, self.path, item.mnu_caption, 401 qubx.util_types.bind_with_args(self.save_text, item), 402 parent=qubx.GTK.get_active_window(), 403 filters=[('Text (tab-separated)', '.txt'), ('Comma-Separated text', '.csv')]) 404 if fname: 405 self.path = os.path.split(fname)[0]
406 - def save_image(self, item, fname):
407 ext = os.path.splitext(fname)[1].lower() 408 if ext in ('.tif', '.tiff'): 409 dpi = float(qubx.global_namespace.QubX.Admin.Notebook.tiff_resolution) 410 pixbuf = item.get_pixbuf() 411 # data = pixbuf.get_pixels_array() # crashes on windows 412 pix = pixbuf.get_pixels() 413 stride = pixbuf.get_rowstride() # in either form, rows can be padded 414 raw = numpy.fromstring(pix, dtype=numpy.uint8).reshape((len(pix)/stride, stride)) 415 data = numpy.zeros(shape=(pixbuf.get_height(), pixbuf.get_width(), 3), dtype=numpy.uint8) 416 for i in xrange(raw.shape[0]): 417 data[i,:].flat = raw[i,:3*data.shape[1]] 418 if ext == '.tif': 419 bw = numpy.zeros(shape=(data.shape[0], data.shape[1]), dtype='uint8') 420 for i in xrange(data.shape[0]): 421 for j in xrange(data.shape[1]): 422 bw[i,j] = numpy.max(data[i,j]) 423 data = bw 424 try: 425 tifffile.imsave(fname, data, byteorder='<', resolution=(dpi, dpi), 426 description="", software="%s %s" % (qubx.global_namespace.QubX.appname, qubx.global_namespace.QUBX_VERSION)) 427 except: 428 traceback.print_exc() 429 del pixbuf 430 qubx.pyenv.env.gc_collect_on_idle() 431 elif ext == '.png': 432 item.save_png(fname)
433 - def save_text(self, item, fname):
434 txt = item.to_txt() 435 ext = os.path.splitext(fname)[1].lower() 436 if ext == '.csv': 437 txt = txt.replace('\t', ',') 438 open(fname, 'w').write(txt)
439
440 441 -class NbTarget_Clipboard(object):
442 - def __init__(self):
449 - def nb_init(self):
450 pass
451 - def nb_send(self, item):
452 clipboard = gtk.clipboard_get(gdk.SELECTION_CLIPBOARD) 453 if isinstance(item, qubx.notebook.NbItems): 454 self.nb_send(item.items[0]) 455 elif isinstance(item, NbPicture): 456 pixbuf = item.get_pixbuf() 457 clipboard.set_image(pixbuf) 458 del pixbuf 459 qubx.pyenv.env.gc_collect_on_idle() 460 else: 461 clipboard.set_text(item.to_txt())
462
463 464 -class NbTarget_HTML(object):
465 - def __init__(self):
466 self.__fname = "" 467 self.__files_path = "" 468 self.__last_stamp = "" 469 self.__saving = False 470 self.__pending = None 471 self.save_as = lambda: False 472 self.supports = [qubx.notebook.NbText, 473 qubx.notebook.NbDynText, 474 qubx.notebook.NbTable, 475 qubx.notebook.NbChart, 476 qubx.notebook.NbTrace, 477 NbPicture]
478 - def set_fname(self, x):
479 """Raises file exceptions.""" 480 files_path = os.path.splitext(x)[0] + '_files' 481 files_rel = os.path.split(files_path)[1] 482 appname = qubx.pyenv.env.globals['QubX'].appname 483 if os.path.exists(x): 484 open(x, 'a').write("\n") 485 else: 486 open(x, 'w').write("""<html> 487 <head> 488 <title>%(appname)s - Notebook</title> 489 <link rel="stylesheet" href="%(files_rel)s/qubx_notebook.css"> 490 <!--[if IE]><script language="javascript" type"text/javascript" src="%(files_rel)s/excanvas.min.js"></script><![endif]--> 491 <script language="JavaScript" src="%(files_rel)s/jquery-1.4.2.min.js"></script> 492 <script language="JavaScript" src="%(files_rel)s/jquery.flot.min.js"></script> 493 <script language="JavaScript" src="%(files_rel)s/jquery.flot.axislabels.js"></script> 494 <script language="JavaScript" src="%(files_rel)s/jquery.base64.min.js"></script> 495 </head> 496 497 <body> 498 <div id="controls"> 499 <form name="controls_form" onsubmit="update_charts();"> 500 Chart width: <input name="width" type="text" onchange="update_charts();" /> 501 height: <input name="height" type="text" onchange="update_charts();" /> 502 </form> 503 </div> 504 505 <div id="savebox"> 506 </div> 507 508 <script language="JavaScript"> 509 510 $('#savebox').html(''); // clear any message captured by saving 511 512 DOTS = 0; 513 LINES = 1; 514 HISTOGRAM = 2; 515 516 charts = []; 517 nchart = 0; 518 removals = 0 519 520 function remove_entry(entry_id) 521 { 522 $(entry_id).html(''); 523 removals += 1; 524 if ( removals == 1 ) { 525 $(window).bind('beforeunload', function(){ 526 return "You've removed items. To keep your changes, click 'save edited notebook'"; 527 }); 528 } 529 $('#savebox').html('<a class="savebtn" href="#" onclick="javascript:build_save_html();">[save edited notebook...]</a>'); 530 } 531 532 function build_save_html() 533 { 534 $('#savebox').html(''); 535 var html = $('html').clone(); 536 var htmlString = html.html(); 537 var datauri = "data:text/html;charset=utf-8;base64," + $.base64.encode(htmlString); 538 $('#savebox').html('<a class="savebtn" href="' + datauri + '">[ready: right-click and save as...]</a><br>(keep it together with the folder "%(files_rel)s")'); 539 } 540 541 function add_chart(chart) 542 { 543 if ( nchart == 0 ) { 544 document.controls_form.width.value = Math.round(Math.min(document.width, 545 Math.max(300, (document.width * 2) / 5))); 546 // $(chart.box).width(); 547 document.controls_form.height.value = $(chart.box).height(); 548 } 549 charts[nchart] = chart; 550 nchart += 1; 551 $(chart.box).css({width: parseInt(document.controls_form.width.value), 552 height: parseInt(document.controls_form.height.value)}); 553 chart.draw(); 554 } 555 556 function update_charts() 557 { 558 var w = parseInt(document.controls_form.width.value); 559 var h = parseInt(document.controls_form.height.value); 560 $('.chart_box').css({width: w, height: h}); 561 var i; 562 for (i=0; i<nchart; ++i) 563 charts[i].draw(); 564 } 565 566 </script> 567 """ % locals()) 568 if not os.path.exists(files_path): 569 os.mkdir(files_path) 570 for leaf in ['qubx_notebook.css', 571 'excanvas.min.js', 572 'jquery-1.4.2.min.js', 573 'jquery.flot.min.js', 574 'jquery.flot.axislabels.js', 575 'jquery.base64.min.js']: 576 shutil.copy(os.path.join(qubx.pyenv.env.globals['app_path'], 'notebook_files', leaf), 577 os.path.join(files_path, leaf)) 578 open(x, 'a').write("""<hr /><div>written by %s %s</div><hr /> 579 580 """ % (qubx.pyenv.env.globals['QubX'].appname, 581 qubx.pyenv.env.globals['QUBX_VERSION'])) 582 self.__fname = x 583 self.__files_path = files_path
584 fname = property(lambda self: self.__fname, lambda self, x: self.set_fname(x)) 585 files_path = property(lambda self: self.__files_path)
586 - def __new_figure_id(self):
587 stamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') 588 if stamp == self.__last_stamp: 589 ix = self.__last_ix = self.__last_ix + 1 590 else: 591 self.__last_stamp = stamp 592 ix = self.__last_ix = 1 593 return "%s_%s" % (stamp, ix)
594 - def convert(self, item):
595 if isinstance(item, qubx.notebook.NbItems): 596 buf = StringIO() 597 for it in item: 598 buf.write(self.convert(it)) 599 return buf.getvalue() 600 if isinstance(item, qubx.notebook.NbChart): 601 try: 602 return self.chart_to_flot(item) 603 except: 604 traceback.print_exc() 605 if isinstance(item, NbPicture): 606 picname = item.savename = self.__new_figure_id() + '.png' 607 return '<a href="$FILES_REL$/%s"><img src="$FILES_REL$/%s" /></a>' % (picname, picname) 608 if isinstance(item, qubx.notebook.NbTable): 609 try: 610 Nr, Nc = item.get_shape() 611 if not (Nr and Nc): 612 return "" 613 formats = [item.get_col_format(c) for c in xrange(Nc)] 614 return '\n'.join(['<div class="table_caption">%s:</div>' % item.get_caption(), 615 '<table>\n<tr>%s</tr>' % ''.join('<th>%s</th>' % hdr for hdr in item.get_headers())] + 616 ['<tr>%s</tr>' % ''.join('<td>%s</td>' % formats[c](x) for c,x in enumerate(item.get_row(r))) 617 for r in xrange(Nr)] + 618 ['</table>']) 619 except: 620 traceback.print_exc() 621 622 return """<pre> 623 %s 624 </pre>""" % cgi.escape(item.to_txt())
625 - def chart_to_flot(self, item):
626 if item.get_color_indices(): 627 return self.chart_to_flot_colors(item) 628 chart_id = self.__new_figure_id() 629 txtname = item.savename = chart_id + '.txt' 630 link_caption = """<div class="chart_link"><a href="$FILES_REL$/%s">[link to chart data]</a></div> 631 <div class="chart_caption">%s</div>""" % (txtname, cgi.escape(item.get_caption())) 632 series = item.get_series() 633 if len(series) == 0: 634 return link_caption 635 Nr, Nc = item.get_shape() 636 if (Nr < 2) or (Nc < 2): 637 return "" 638 col_format = [item.get_col_format(c) for c in xrange(Nc)] 639 colors = item.get_colors() or [] 640 buf = StringIO() 641 buf.write("""<div class="chart_box">%s 642 <div class="chart"></div> 643 </div> 644 <script language="JavaScript"> 645 function NbChart_%s(box, chart) 646 { 647 this.box = box 648 this.chart = chart; 649 this.caption = "%s"; 650 this.table = %s; 651 this.headers = %s; 652 this.xlabel = "%s"; 653 this.ylabel = "%s"; 654 this.series = [""" % (link_caption, 655 chart_id, 656 cgi.escape(item.get_caption()), 657 [ [x for x in item.get_row(r)] for r in xrange(Nr) ], 658 item.get_headers(), 659 item.get_xlabel(), 660 item.get_ylabel())) 661 for s, ser in enumerate(series): 662 if ser.nrow <= 0: 663 ser.nrow = Nr - ser.first_row 664 if s: buf.write(""", 665 """) 666 buf.write("""{xcol:%(xcol)s, ycol:%(ycol)s, first_row:%(first_row)s, nrow:%(nrow)s, ser_type:%(ser_type)s, color_hex:"%(color_hex)s", weight:%(weight)s}""" % ser.__dict__) 667 buf.write("""]; 668 this.colors = %s; 669 this.draw = function() 670 { 671 var options = { 672 series: {points: {show: true}}, 673 xaxis: { 674 axisLabel: this.xlabel, 675 axisLabelUseCanvas: true 676 }, 677 yaxis: { 678 axisLabel: this.ylabel, 679 axisLabelUseCanvas: true 680 }, 681 }; 682 683 var flot_series = []; 684 var i; 685 for (i=0; i<this.series.length; ++i) { 686 one_x = this.table[this.series[i].first_row+1][this.series[i].xcol] - 687 this.table[this.series[i].first_row][this.series[i].xcol]; 688 flot_series[i] = {color : this.series[i].color_hex, 689 lines : {show : (this.series[i].ser_type == LINES), 690 lineWidth : 2*this.series[i].weight}, 691 points : {show : (this.series[i].ser_type == DOTS)}, 692 bars : {show: (this.series[i].ser_type == HISTOGRAM), 693 fill: .3, barWidth: .9*one_x, align:'center'}, 694 // label : this.headers[this.series[i].ycol], 695 data : []} 696 for (j=0; j<this.series[i].nrow; ++j) 697 flot_series[i].data[j] = [this.table[this.series[i].first_row+j][this.series[i].xcol], 698 this.table[this.series[i].first_row+j][this.series[i].ycol]]; 699 } 700 $.plot(this.chart, flot_series, options); 701 } 702 } 703 704 chart = new NbChart_%s( $('.chart_box:last')[0], $('.chart:last')[0] ); 705 add_chart(chart); 706 707 </script> 708 """ % (colors, chart_id)) 709 return buf.getvalue()
710 - def chart_to_flot_colors(self, item):
711 chart_id = self.__new_figure_id() 712 txtname = item.savename = chart_id + '.txt' 713 link_caption = """<div class="chart_link"><a href="$FILES_REL$/%s">[link to chart data]</a></div> 714 <div class="chart_caption">%s</div>""" % (txtname, cgi.escape(item.get_caption())) 715 series = item.get_series() 716 if len(series) == 0: 717 return link_caption 718 Nr, Nc = item.get_shape() 719 if (Nr < 2) or (Nc < 2): 720 return "" 721 col_format = [item.get_col_format(c) for c in xrange(Nc)] 722 colors = [qubx.notebook.rgb_to_hex(rgb) for rgb in item.get_colors()] 723 color_indices = item.get_color_indices() 724 buf = StringIO() 725 buf.write("""<div class="chart_box">%s 726 <div class="chart"></div> 727 </div> 728 <script language="JavaScript"> 729 function NbChart_%s(box, chart) 730 { 731 this.box = box 732 this.chart = chart; 733 this.caption = "%s"; 734 this.table = %s; 735 this.headers = %s; 736 this.xlabel = "%s"; 737 this.ylabel = "%s"; 738 this.series = [""" % (link_caption, 739 chart_id, 740 cgi.escape(item.get_caption()), 741 [ [x for x in item.get_row(r)] for r in xrange(Nr) ], 742 item.get_headers(), 743 item.get_xlabel(), 744 item.get_ylabel())) 745 for s, ser in enumerate(series): 746 if ser.nrow <= 0: 747 ser.nrow = Nr - ser.first_row 748 if s: buf.write(""", 749 """) 750 buf.write("""{xcol:%(xcol)s, ycol:%(ycol)s, first_row:%(first_row)s, nrow:%(nrow)s, ser_type:%(ser_type)s, color_hex:"%(color_hex)s", weight:%(weight)s}""" % ser.__dict__) 751 buf.write("""]; 752 this.colors = %s; 753 this.color_indices = %s; 754 this.draw = function() 755 { 756 var options = { 757 series: {points: {show: true}}, 758 xaxis: { 759 axisLabel: this.xlabel, 760 axisLabelUseCanvas: true 761 }, 762 yaxis: { 763 axisLabel: this.ylabel, 764 axisLabelUseCanvas: true 765 }, 766 }; 767 768 var flot_series = []; 769 var i; 770 for (i=0; i<this.series.length; ++i) { 771 one_x = this.table[this.series[i].first_row+1][this.series[i].xcol] - 772 this.table[this.series[i].first_row][this.series[i].xcol]; 773 flot_series[i] = {color : this.series[i].color_hex, 774 lines : {show : (this.series[i].ser_type == LINES), 775 lineWidth : 2*this.series[i].weight}, 776 points : {show : (this.series[i].ser_type == DOTS)}, 777 bars : {show: (this.series[i].ser_type == HISTOGRAM), 778 fill: .3, barWidth: .9*one_x, align:'center'}, 779 // label : this.headers[this.series[i].ycol], 780 data : []} 781 for (j=0; j<this.series[i].nrow; ++j) 782 flot_series[i].data[j] = [this.table[this.series[i].first_row+j][this.series[i].xcol], 783 this.table[this.series[i].first_row+j][this.series[i].ycol]]; 784 } 785 var colors_used = [] 786 for (i=0; i<this.color_indices.length; ++i) 787 colors_used[this.color_indices[i]] = 1; 788 var s = this.series.length; 789 for (i in colors_used) { 790 flot_series[s] = {color : this.colors[i], 791 lines : {show : false}, 792 points : {show : true}, 793 bars : {show : false}, 794 data : []} 795 var p = 0; 796 for (j=0; j<this.series[0].nrow; ++j) { 797 if ( this.color_indices[j] == i ) { 798 flot_series[s].data[p] = flot_series[0].data[j]; 799 ++p; 800 } 801 } 802 ++s; 803 } 804 805 $.plot(this.chart, flot_series, options); 806 } 807 } 808 809 chart = new NbChart_%s( $('.chart_box:last')[0], $('.chart:last')[0] ); 810 add_chart(chart); 811 812 </script> 813 """ % (colors, color_indices, chart_id)) 814 return buf.getvalue()
815 - def nb_init(self):
816 if not self.__fname: 817 self.do_save_as()
818 - def nb_send(self, item):
819 html = StringIO() 820 now = datetime.datetime.now() 821 entry_id = now.strftime('entry_%Y%m%d_%H%M%S_%f') 822 html.write("""<div class="entry" id="%(entry_id)s"> 823 <div class="hrule">%(now)s<a href="#" onclick="javascript:remove_entry('#%(entry_id)s');">[remove this entry]</a></div> 824 """ % locals()) 825 826 html.write(self.convert(item)) 827 html.write("""</div> 828 829 """) 830 self.send_html(html.getvalue(), item)
831 - def save_files(self, item):
832 if isinstance(item, qubx.notebook.NbItems): 833 for it in item: 834 self.save_files(it) 835 elif isinstance(item, NbPicture): 836 item.save_png(os.path.join(self.__files_path, item.savename)) 837 elif isinstance(item, qubx.notebook.NbChart): 838 open(os.path.join(self.__files_path, item.savename), 'w').write(item.to_txt())
839 - def send_html(self, html, item):
840 try: 841 open(self.__fname, 'a').write(html.replace("$FILES_REL$", os.path.split(self.__files_path)[1])) 842 self.save_files(item) 843 except: 844 if self.__pending is None: 845 if self.__saving: # from nb_init 846 self.__pending = [(html, item)] 847 else: 848 self.__pending = [] 849 self.do_save_as(lambda: self.send_html(html, item)) 850 else: 851 self.__pending.append((html, item))
852 - def do_save_as(self, continuation = lambda: None):
853 if not self.__saving: 854 self.__saving = True 855 rtn = self.save_as() 856 if rtn: 857 continuation() 858 if self.__pending: 859 for pending_html, pending_item in self.__pending: 860 self.send_html(pending_html, pending_item) 861 else: 862 if qubx.GTK.PromptChoices('Disable the HTML lab notebook? You can re-enable it in the Tools:Admin:Notebook panel.', ['Ask later', 'Disable'], 863 '%s - HTML Notebook' % qubx.global_namespace.QubX.appname, qubx.global_namespace.QubX): 864 qubx.notebook.Notebook.target_id = 'stdout' 865 self.__pending = None 866 self.__saving = False 867 return rtn 868 else: 869 gobject.idle_add(self.do_save_as, continuation) 870 return False
871
872 -class NbControls_HTML(gtk.HBox):
873 __explore_featured = ['target', 'save_as']
874 - def __init__(self, target):
875 gtk.HBox.__init__(self) 876 self.target = target 877 pack_label('Append to:', self) 878 self.txtFname = pack_item(gtk.Entry(), self, expand=True) 879 self.txtFname.set_editable(False) 880 self.txtFname.set_text(target.fname) 881 self.btnChoose = pack_button('...', self, on_click=self.__onClickChoose) 882 self.btnChoose.set_tooltip_text('Choose a location to save the HTML file') 883 self.btnShow = pack_button('->', self, on_click=self.__onClickShow) 884 self.btnShow.set_tooltip_text('Open the HTML file in a web browser') 885 prop = qubx.settings.SettingsMgr['NbControls_HTML'].active['hide_intro'] 886 self.__introduced = prop.data and prop.data[0] 887 self.target.save_as = self.save_as
888 - def __onClickChoose(self, btn):
889 self.save_as()
890 - def __onClickShow(self, btn):
891 if os.path.exists(self.target.fname): 892 webbrowser.open('file://%s' % self.target.fname)
893 - def save_as(self):
894 if not self.__introduced: 895 QubX = qubx.pyenv.env.globals['QubX'] 896 dlg = qubx.GTK.AskOnceDialog('About the %s Notebook'%qubx.pyenv.env.globals['QubX'].appname, qubx.GTK.get_active_window(), 897 """%s writes figures and messages to a "notebook" -- a web-ready html document saved 898 on your hard drive. You can write a figure any time using the notebook icon. To configure 899 which figures are automatically written, go to Admin:Notebook. You'll be asked where to save 900 the notebook, after this message, and once each session. If you pick an existing file, we'll 901 append the new figures to the end of it."""%qubx.pyenv.env.globals['QubX'].appname, 902 (("Go to Admin:Notebook", 21), 903 ("Disable automatic", 22), 904 ("OK", gtk.RESPONSE_ACCEPT))) 905 response, dont_show = dlg.run() 906 dlg.destroy() 907 qubx.settings.SettingsMgr['NbControls_HTML'].active['hide_intro'].data = dont_show 908 self.__introduced = True 909 if response == 21: # Go to Admin:Notebook 910 QubX.Admin.Notebook.request_show() 911 elif response == 22: # Disable automatic 912 for auto_id in qubx.notebook.Notebook.auto: 913 qubx.notebook.Notebook.set_auto_active(auto_id, False) 914 return False 915 start_path = os.path.split(self.target.fname)[0] or qubx.pyenv.env.globals['documents_path'] 916 fname = qubx.GTK.SaveAs('Save Notebook as...', start_path, "%s notebook.html"%qubx.pyenv.env.globals['QubX'].appname, 917 self.target.set_fname, 918 caption_if_exists="Append to", 919 parent=qubx.GTK.get_active_window(), 920 filters=[('HTML files', '.html')]) 921 if fname: 922 self.txtFname.set_text(fname) 923 return True 924 return False
925
926 927 -class Notebook_Table_Extensions(object):
928 - def __init__(self):
929 self.__ref = Reffer() 930 self.__data = None 931 self.items = [] 932 self.__activated = self.__activating = False
933 - def register(self, global_name, name, nbFactory):
934 self.items.append(Anon(global_name=global_name, name=name, nbFactory=nbFactory))
935 - def register_and_write(self, global_name, name, columns, NbSubTable, activate=True):
936 open(os.path.join(qubx.pyenv.env.folder, 'notebook_table_extensions.py'), 'a').write(""" 937 qubx.notebookGTK.TableExtensions.register(%s, %s, %s(%s, %s)) 938 """ % (repr(global_name), repr(name), NbSubTable, repr(name), repr(columns))) 939 self.items.append(Anon(global_name=global_name, name=name, nbFactory=qubx.pyenv.env.eval_str(NbSubTable)(name, columns))) 940 if activate: 941 self.activate()
942 - def activate(self):
943 QubX = qubx.pyenv.env.globals['QubX'] 944 if not self.__activated: 945 self.__activated = self.__activating = True 946 self.__read(os.path.join(qubx.pyenv.env.folder, 'notebook_table_extensions.py')) 947 QubX.Data.OnSwitch += self.__ref(self.__onSwitchData) 948 if QubX.has_modeling: 949 QubX.Models.OnSwitch += self.__ref(self.__onSwitchModel) 950 QubX.Trials.OnSwitch += self.__ref(self.__onSwitchTrials) 951 self.__activating = False 952 if not self.__activating: 953 self.__onSwitchData(QubX.Data, QubX.Data.file) 954 if QubX.has_modeling: 955 self.__onSwitchModel(QubX.Models, QubX.Models.file) 956 self.__onSwitchTrials(QubX.Trials.trialset)
957 - def __read(self, path):
958 if os.path.exists(path): 959 qubx.pyenv.env.exec_file(path)
960 - def __connect_to_tables(self, *tables):
961 for item in self.items: 962 try: 963 table = qubx.pyenv.env.eval_str(item.global_name, eat_exceptions=False) 964 if table in tables: 965 table.add_notebook_item(item.name, item.nbFactory(table)) 966 except: 967 if qubx.global_namespace.DEBUG: 968 traceback.print_exc() 969 print 'in string %s' % repr(item.global_name)
970 - def __onSwitchData(self, Data, file):
971 if self.__data: 972 try: 973 self.__data.lists.OnSwitchList -= self.__ref(self.__onSwitchList) 974 except: 975 traceback.print_exc() 976 self.__data = file 977 if file and file.signals: 978 self.__connect_to_tables(file.constants, file.signals, file.stimuli, file.segments, file.list, Data.view.signals, Data.view, Data.view.hires) 979 file.lists.OnSwitchList += self.__ref(self.__onSwitchList)
980 - def __onSwitchList(self, lists, index, lst):
981 self.__connect_to_tables(lst)
982 - def __onSwitchModel(self, Models, file):
983 if file: 984 self.__connect_to_tables(file.states, file.rates, file.classes, file.constraints_kin)
985 - def __onSwitchTrials(self, trialset):
988
989 990 -class NbSubTablePicker(qubx.notebook.NbTable):
991 - def __init__(self, table, global_name, get_caption, NbSubTable="qubx.notebook.NbSubTable", **kw):
992 qubx.notebook.NbTable.__init__(self, mnu_caption="Pick columns...", global_name=global_name, get_caption=get_caption, 993 get_shape=self.__get_shape, get_headers=self.__get_headers, get_row=self.__get_row, get_col=self.__get_col, 994 get_col_format=self.__get_col_format, get_type=self.__get_type, 995 table=table, **kw) 996 self.NbSubTable = NbSubTable 997 self.__picked = self.__last_picked = None
998 - def get_picked(self):
999 if not self.__picked: 1000 QubX = qubx.pyenv.env.globals['QubX'] 1001 dlg = gtk.Dialog('%s - %s - Pick columns...' % (QubX.appname, self.table.label), qubx.GTK.get_active_window(), gtk.DIALOG_MODAL) 1002 dlg.set_size_request(400, 500) 1003 line = pack_item(gtk.HBox(), dlg.vbox) 1004 pack_label('Pick which fields to write:', line) 1005 checks = qubx.GTK.CheckList() 1006 for field in self.table.fields: 1007 checks.append(True, field) 1008 pack_scrolled(checks, dlg.vbox, expand=True) 1009 line = pack_item(gtk.HBox(), dlg.vbox) 1010 chkAdd = pack_check('Add to menu as ', line) 1011 txtName = pack_item(qubx.GTK.NumEntry(''), line, expand=True) 1012 1013 def on_change_name(txt, val): 1014 chkAdd.set_active(bool(val))
1015 txtName.OnChange += on_change_name 1016 1017 dlg.add_button('Cancel', gtk.RESPONSE_REJECT) 1018 dlg.add_button('OK', gtk.RESPONSE_ACCEPT) 1019 response = dlg.run() 1020 add_name = txtName.value if chkAdd.get_active() else "" 1021 columns = [field for i, field in enumerate(self.table.fields) if checks.get_active(i)] 1022 dlg.destroy() 1023 1024 if response == gtk.RESPONSE_ACCEPT: 1025 if add_name: 1026 TableExtensions.register_and_write(self.table.global_name, add_name, columns, self.NbSubTable) 1027 try: 1028 self.__picked = self.__last_picked = qubx.pyenv.env.eval_str(self.NbSubTable, eat_exceptions=False)(self.table.label, columns)(self.table) 1029 gobject.timeout_add(NB_SUBTABLE_FREEZE_MS, self.__onTimeout) 1030 except: 1031 self.__picked = None 1032 if qubx.global_namespace.DEBUG: 1033 traceback.print_exc() 1034 print 'in string %s' % repr(self.NbSubTable) 1035 return self.__picked
1036 - def __onTimeout(self):
1037 self.__picked = None
1038 - def __get_shape(self):
1039 picked = self.get_picked() 1040 return picked.get_shape() if picked else (0, 0)
1041 - def __get_headers(self):
1042 picked = self.get_picked() 1043 return picked.get_headers() if picked else []
1044 - def __get_row(self, r):
1045 picked = self.__last_picked if r else self.get_picked() 1046 return picked.get_row(r) if picked else []
1047 - def __get_col(self, c):
1048 picked = self.__last_picked if c else self.get_picked() # not the first column? maybe overtime? no new picks 1049 return picked.get_col(c) if picked else []
1050 - def __get_col_format(self, c):
1051 picked = self.get_picked() 1052 return picked.get_col_format(c) if picked else str
1053 - def __get_type(self):
1054 picked = self.get_picked() 1055 return picked.get_type() if picked else str
1056
1057 1058 -def Init():
1059 qubx.notebook.Init() 1060 target_html = NbTarget_HTML() 1061 controls_html = NbControls_HTML(target_html) 1062 qubx.notebook.Notebook.register_target(target_html, 'html', 'HTML log file', 'Log to HTML:', controls_html) 1063 qubx.notebook.Notebook.register_target(NbTarget_SaveAs(), 'saveas', 'Ask me each time', 'Save:', None) 1064 qubx.notebook.Notebook.register_target(NbTarget_Clipboard(), 'copy', 'Clipboard', 'Copy to clipboard:', None)
1065
1066 -def InitAfter():
1067 global TableExtensions 1068 # after global QubX is set up 1069 qubx.notebook.RegisterTargets() # console, discard; last in menu 1070 if not qubx.notebook.Notebook.init_settings(): 1071 qubx.notebook.Notebook.target_id = 'html' 1072 TableExtensions = Notebook_Table_Extensions() 1073 TableExtensions.activate()
1074