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
47 stdout, stderr = sys.stdout, sys.stderr
48 sys.stdout = sys.stderr = Anon(write=lambda s: None)
49 try:
50 import tifffile
51 except:
52 traceback.print_exc()
53 sys.stdout, sys.stderr = stdout, stderr
54
55
56 import xml.etree.ElementTree
57 import xml.etree.cElementTree
58 import numpy
59
60 NB_SUBTABLE_FREEZE_MS = 5000
61
62
63 try:
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
76 if end is None:
77 end = start + 0.0
78 start = 0.0
79 assert inc
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
97
105
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
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):
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
190 - def __onSend(self, item, target_ix, nb_item):
193 - def draw(self, context, w, h, appearance):
204
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)
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):
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
344 - def __init__(self, mnu_caption, global_name, get_shape, draw, save_png=None, get_pixbuf=None):
351 w, h = self.get_shape()
352 return "[%s: Picture (%d x %d)]" % (self.mnu_caption, w, h)
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)
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
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]
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
412 pix = pixbuf.get_pixels()
413 stride = pixbuf.get_rowstride()
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
462
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)
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())
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()
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()
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)
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:
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
873 __explore_featured = ['target', 'save_as']
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:
910 QubX.Admin.Notebook.request_show()
911 elif response == 22:
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
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):
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()
988
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
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
1037 self.__picked = None
1039 picked = self.get_picked()
1040 return picked.get_shape() if picked else (0, 0)
1042 picked = self.get_picked()
1043 return picked.get_headers() if picked else []
1045 picked = self.__last_picked if r else self.get_picked()
1046 return picked.get_row(r) if picked else []
1048 picked = self.__last_picked if c else self.get_picked()
1049 return picked.get_col(c) if picked else []
1054 picked = self.get_picked()
1055 return picked.get_type() if picked else str
1056
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
1074