-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgnc_html.py
More file actions
565 lines (422 loc) · 22.3 KB
/
gnc_html.py
File metadata and controls
565 lines (422 loc) · 22.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# this uses some hackery to work with the C GncHtmlWebkit subclass
# - the advantage of this hackery is that all(?) the other url handling
# is still maintained so eg account names as hyperlinks still open
# the account register as in the C/scheme reports
import os
import sys
import ctypes
import gc
# need to load these differently for testing purposes
if __name__ == '__main__':
import gi
gi.require_version('GIRepository', '2.0')
from gi.repository import GIRepository
rep = GIRepository.Repository.get_default()
addrep = os.path.join(sys.path[0],"girepository")
rep.prepend_search_path(addrep)
gi.require_version('Gtk', '3.0')
gi.require_version('URLType', '1.0')
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import URLType
import pdb
import traceback
def N_(msg):
return msg
# well great - gnc_build_url is not defined in the include file!!
# - it is in swig bindings though!! (but not python??)
# great - after all this importing webkit locks gnucash up
# and just importing WebView doesnt help either
#print("Before webview import", file=sys.stderr)
#import webkit
#from webkit import WebView
# OK have pinned this down to the gobject.threads_init()
# this is called in the __init__.py for webkit - and this locks up in Python callback from menu
# the following does not include __init__.py and does not lock up
# UPDATE - now calling gobject.threads_init again
#sys.path.insert(0,"/opt/local/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/webkit")
#webkit = __import__("webkit")
#print("After webview import", file=sys.stderr)
# try accessing through gnucash type
# THIS WORKS AND IS THE PREFERRED METHOD FOR THE MOMENT
# - this allows some functionality to work automagically eg account hyper links
# opening up account register
# NOTE that in addition this needs the GncHtml GType defined
from gi.repository import GncHtmlWebkit
import glib_ctypes
#import pygignchelpers
import girepo
from girepo import GObjectField
# OK - I give up - we need to get access to the private instance data
# - create duplicate ctype definitions here
# lets try re-creating the private data structure (DANGEROUS) and
# accessing the value via ctypes
# - not of real use until sort out field offset issue of gir
GtkWidgetOpaque = ctypes.c_void_p
gchar_p = ctypes.c_char_p
URLTypeOpaque = ctypes.c_char_p
GHashTableOpaque = ctypes.c_void_p
GncHTMLUrltypeCBOpaque = ctypes.c_void_p
GncHTMLLoadCBOpaque = ctypes.c_void_p
GncHTMLFlyoverCBOpaque = ctypes.c_void_p
GncHTMLButtonCBOpaque = ctypes.c_void_p
gpointer = ctypes.c_void_p
gnc_html_history_opaque = ctypes.c_void_p
class GncHtmlPrivate(ctypes.Structure):
_fields_ = [ \
("parent", GtkWidgetOpaque), # /* window this html goes into */
("container", GtkWidgetOpaque), # /* parent of the webkit widget */
("current_link", gchar_p), # /* link under mouse pointer */
("base_type", URLTypeOpaque), # /* base of URL (path - filename) */
("base_location", gchar_p),
("request_info", GHashTableOpaque), # /* hash uri to GList of GtkHTMLStream * */
# /* callbacks */
("urltype_cb", GncHTMLUrltypeCBOpaque), # /* is this type OK for this instance? */
("load_cb", GncHTMLLoadCBOpaque),
("flyover_cb", GncHTMLFlyoverCBOpaque),
("button_cb", GncHTMLButtonCBOpaque),
("flyover_cb_data", gpointer),
("load_cb_data", gpointer),
("button_cb_data", gpointer),
("history", gnc_html_history_opaque),
]
WebKitWebViewOpaque = ctypes.c_void_p
class GncHtmlWebKitPrivate(ctypes.Structure):
_fields_ = [ \
("base", GncHtmlPrivate),
("web_view", WebKitWebViewOpaque),
("html_string", gchar_p),
#("html_string", ctypes.c_void_p),
]
ctypes.pythonapi.PyUnicode_AsUTF8.argtypes = (ctypes.py_object,)
ctypes.pythonapi.PyUnicode_AsUTF8.restype = ctypes.c_void_p
def fixup_html_string (privfld, docstr):
#pdb.set_trace()
# this does the impl_webkit_show_data storage of the html string in the
# private data of GncHtmlWebkit
priv_ptr = privfld.get_object_value()
webkit_private = ctypes.cast( priv_ptr, ctypes.POINTER( GncHtmlWebKitPrivate ) ).contents
print("python priv addr %x"%ctypes.addressof(webkit_private))
web_view_ptr = webkit_private.web_view
# we cannot take addresses of structure members
#print("python priv webview addr %x"%ctypes.addressof(webkit_private.web_view))
# given up - make this a c_void_p and cast for string - and just store pointer
html_string = ""
if webkit_private.html_string != None:
html_string = ctypes.cast(webkit_private.html_string, gchar_p).value
#
# but we can get the offset
print("python priv html_string offset %x"%GncHtmlWebKitPrivate.html_string.offset)
# what to do about unicode??
# this was sneakily using the address of the string object as the C pointer
# we need to correct this to use the python object directly
# note docstr could be very long so this could use a lot of memory
# for the utf8 copy
#new_value_str_ptr = ctypes.pythonapi.PyString_AsString(id(docstr))
new_value_str_ptr = ctypes.pythonapi.PyUnicode_AsUTF8(docstr)
print("python str address %x"%new_value_str_ptr)
# here Im worried about freeing data - html_string is freed by gnc_html_webkit_dispose
# and Im not sure if python will be able handle something freed under it
# one solution is to do as impl_webkit_show_data and use g_strdup
# via ctypes
# does ctypes.c_char_p copy the string?
copy_value_str_ptr = glib_ctypes.libglib.g_strdup(new_value_str_ptr)
# lets assume for the moment we may have issues
#webkit_private.html_string = ctypes.c_char_p(docstr)
webkit_private.html_string = ctypes.cast(copy_value_str_ptr,ctypes.c_char_p)
#webkit_private.html_string = ctypes.cast(ctypes.c_char_p(docstr), ctypes.c_void_p)
# finally - this is how we do pointer assignment given plain addresses
# we must cast to a pointer type - its confusing because the pointed to type is also a generic
# pointer!!
#value_ptr = priv_ptr + GncHtmlWebKitPrivate.html_string.offset
#print("python html_string address %x"%value_ptr)
#ctypes.cast(value_ptr, ctypes.POINTER(ctypes.c_void_p)).contents.value = new_value_str_ptr
# make a subclass to track the virtual calls
class PythonHtmlWebkit(GncHtmlWebkit.HtmlWebkit):
def __init__ (self):
super(PythonHtmlWebkit,self).__init__()
# apparently for gobject introspection virtual methods (which GI recognises)
# are overridden by adding do_ to their name
# we need to re-implement impl_webkit_show_data and impl_webkit_export_to_file
# in order to deal with the usage of the C private data html_string
# this is a pure re-implementation
# the big issue is now need access to the webkit library for the
# call to webkit_web_view_load_uri or webkit_web_view_load_html_string
# in impl_webkit_show_data
# well thats not going to work either because we need the private variable
# web_view for the webkit function calls!!
def do_show_data (self, data, datalen):
print("do_show_data",data[0:20],str(datalen))
pdb.set_trace()
TEMPLATE_REPORT_FILE_NAME = "gnc-report-XXXXXX.html"
def do_export_to_file (self, filename):
print("do_export_to_file",filename)
pdb.set_trace()
print("do_export_to_file",filename)
# GncHtml has register handlers which allow a url type
# to be associated with function calls - a subclass definition
# (in this case the subclass is GncHtmlWebkit) of the show_url
# function may use these handlers
# it also has a table for stream handlers - these are used in the
# load to stream function
# this a ctypes implementation to load new python handlers into
# the C functions
# not sure this is going to work - the problem is we have no simple
# way to determine the existing handlers loaded - unless we directly
# access the GHashTables the handlers are stored in
# - and what we want to do is call the existing handler in most cases
def register_url_handler (url_type, handler):
if url_type.lower() in url_handlers:
unregister_url_handler(url_type)
url_handlers[url_type.lower()] = handler
def unregister_url_handler (url_type):
if url_type.lower() in url_handlers:
del url_handlers[url_type.lower()]
# hmm - the actual graphics drawer is somehow in html
# - the Report class seems to be something else
# not sure whether should still do this
# for now using separate class to implement drawing
class HtmlView(object):
def __init__ (self,use_pywebkit=False,use_gncwebkit=True):
# story so far
# the python webkit displays blank for the jqplot
# - normal html is displayed, javascript text display works
# but jqplot fails to display
# using the gnucash webkit GObject everything works - jqplot is displayed!!
# (the show data function writes the html to a temporary file before
# displaying using webkit show_uri - is this the issue??)
# cant see immediately what the difference - only difference I can see
# so far is gnucash sets a default encoding and font
self.use_pywebkit = use_pywebkit
self.use_gncwebkit = use_gncwebkit
if self.use_pywebkit:
# create raw widget here - no use of gnucash html stuff at all
self.widget = Gtk.ScrolledWindow()
self.widget.set_policy(Gtk.POLICY_AUTOMATIC,Gtk.POLICY_AUTOMATIC)
# this is pywebkit access - fails - locks up the whole gnucash GUI
# unless just access the underlying webkit bindings
self.webview = webkit.WebView()
self.widget.add(self.webview)
if self.use_gncwebkit:
# use gnucash access to webkit via gi wrapper
self.html = GncHtmlWebkit.HtmlWebkit()
print(GObject.signal_list_names(GncHtmlWebkit.HtmlWebkit), file=sys.stderr)
#pdb.set_trace()
#self.html = PythonHtmlWebkit()
# so this is annoying - the gir bindings are generating
# the wrong offset for the priv field (but not for the parent field)
# probably some inconsistency in the Gtk.Bin gir definition
# (which GncHtml is a subclass of)
# yes - Gtk.Bin is a subclass of Gtk.Container which has bit fields
# at end of instance structure and apparently introspection cant handle
# bit fields - so all offsets after Gtk.Container are wrong!!
# added a hack to GObjectField to handle this if know offset
#prnt = GObjectField.Setup(self.html,"parent_instance")
# for Gtk 2
#self.privfld = GObjectField.Setup(self.html,"priv",offset_adjust_hack=-16,check_adjust_hack=144)
# for Gtk 3
self.privfld = GObjectField.Setup(self.html,"priv",offset_adjust_hack=-8,check_adjust_hack=56)
# note this creates the base scrolled window widget internally
# we get the widget (a scrolled window) via html.get_widget()
self.widget = self.html.get_widget()
print("webkit widget",type(self.widget))
print("webkit widget",self.widget)
print("webkit widget",self.widget.get_child())
# so the story so far is as follows
# the problem with impl_webkit_show_url is where to inject the python report
# - this function is driven by handlers
# - the problem is if we replace handlers with python handlers ALL reports will
# go through python - even though it may just fall through to the C function
# note there is also the issue that elements of the report may need to call out
# to the original report handlers - sort of ignoring this at the moment
# for the moment we have re-implemented the show_url function (which we partially do below)
# the original show_url eventually leads to a call to impl_webkit_show_data to
# actually display the rendered html string - which we can use except that
# it does a direct call to impl_webkit_export_to_file which relies on setting
# the html string into the private C variable html_string
# unfortunately re-implementing impl_webkit_show_data also needs access to the
# the other C private variable web_view so Im stuck either way
# pygignchelpers creates a special function in C to set the html_string variable
# and then call impl_webkit_show_data and we see a report
# we also now have an introspection/ctypes way to set this value ie without
# compiling a module
# (note we still have no way to fire up a python report via those url handlers
# or even detect if we have a python report)
# one advantage of hi-jacking the C version is that call outs from the report seem
# to be handled by the base impl_webkit_show_url functions
# so for the moment we have a minimal impl_webkit_show_url re-implementation
# - this is only called from create_widget so we know we have a report url
# (for a python report) and we dont bother trying to implement all the other
# url handling - we just need to inject the eventual html string into the
# impl_webkit_show_data C function
def show_url (self,url_type,url_location,url_label,report_cb=None):
# try drawing something here
#button = Gtk.Button(label="My Button")
#self.widget.add(button)
# call stack showing how we get to report renderer function
# gnc_run_report calls the scheme gnc:report-run function
# which eventually calls the renderer via
# gnc:report-render-html (in report.scm)
#0 0x00000001001a5bd4 in gnc_run_report ()
#1 0x00000001001a5cf3 in gnc_run_report_id_string ()
#2 0x00000001000408a3 in gnc_html_report_stream_cb ()
#3 0x00000001001c853e in load_to_stream ()
#4 0x00000001001c8d97 in impl_webkit_show_url ()
#5 0x00000001001c5fb1 in gnc_html_show_url ()
#6 0x000000010003e8d6 in gnc_plugin_page_report_create_widget ()
#pdb.set_trace()
# in impl_webkit_show_url the load_cb is called AFTER loading the page
# why did I put it before??
# in scheme something to do with loading updated options and creating new version
# of a report based on those updated options
self.load_cb(url_type,url_location,url_label)
# the C impl_webkit_show_url does a lot with urls - getting a specific
# handler for different types of url and calling that to handle the url
# it also does something about creating a history or urls accessed
# (which Im not sure how is used at the moment)
# looks like eventually load_to_stream is called to actually display
# any generated data - if the url would generate display data
# more closely following the C code
self.load_to_stream(url_type,url_location,url_label,report_cb=report_cb)
# why is this here??
# the self.container.show_all() in create_widget should show this widget
# indeed this is gone in 2.6.21a and 3.2
#self.widget.show_all()
def reload (self):
pdb.set_trace()
#gc.collect()
# something with history
# for the moment use the saved instance mapped to a function call via lambda
# is this ever called??
# no - the reload virtual function is set to impl_webkit_reload which will
# get called - and as impl_webkit_reload is based on the gnc_history_html
# which if history exists (which we dont have for python reports)
# impl_webkit_reload just re-calls gnc_html_show_url ie impl_webkit_show_url
# which wont handle python reports
self.show_url(None,None,None,report_cb=lambda : self.report_backup)
def load_to_stream (self, url_type, url_location, url_label, report_cb=None):
#pdb.set_trace()
# here we pass in report_cb the function to generate the html display
# data
# we ignore all the stream handlers code in this routine
# we assume this is only passed a url_type of report
if report_cb:
# this code is partially implementing the report stream handler
# as defined in gnc_html_report_stream_cb in window-report.c
# which seems to be the primary callout to scheme to run the report
report = report_cb()
# im going to stuff this in a variable for reload temporarily
self.report_backup = report
baddocstr = "<html><body><h3>%s</h3>\n <p>%s</p></body></html>"%( \
N_("Report error"),
N_("An error occurred while running the report."))
try:
# try an implementation closer to the C/scheme
docstr = report.run()
except Exception as errexc:
traceback.print_exc()
docstr = baddocstr
else:
if docstr == None:
docstr = baddocstr
self.load_string(docstr)
else:
summary = "<html><body>The report is <b>not</b> defined in show_url.</body></html>";
# in the C code the progress bar is reset here
# this would be our equivalent
#gnc_report_utilities.report_finished()
self.load_string(summary)
def load_string (self, docstr):
#pdb.set_trace()
# junky function to map the calls
if self.use_pywebkit:
self.webview.load_string(docstr,"text/html", "UTF-8", "gnucash:report")
if self.use_gncwebkit:
# major hack until sort out impl_webkit_show_url
# this calls impl_webkit_show_data but prior to this storing docstr
# in private gnc_html variable html_string - which is required
# for the impl_webkit_export_to_file call in impl_webkit_show_data
# for the moment use our tricky helper function to accomplish this
#pdb.set_trace()
print("hash self.html","%x"%hash(self.html))
#pygignchelpers.show_data(self.html,docstr,len(docstr))
# hacked up introspection/ctypes way to set html_string variable
fixup_html_string(self.privfld,docstr)
# this calls the impl_webkit_show_data GncHtmlWebkit function
# NOTA BENE - impl_webkit_show_data seems to copy the generated report
# string to a file via the impl_webkit_export_to_file call
# AND THEN RESETS the uri to a file uri
# the html string is displayed by a call to webkit_web_view_load_uri
# which almost immediately does the webkit_navigation_requested_cb
# callback which basically ignores file uris!!
# note that its webkit_navigation_requested_cb which responds to links
# etc in report pages
self.html.show_data(docstr,len(docstr))
# so looks as though what we need to do is re-implement impl_webkit_show_data
# in python because it does a direct call to impl_webkit_export_to_file rather
# than through the virtual function and then we re-implement impl_webkit_export_to_file
# to use the python saved document string rather than inaccessible C private data
# structure
# not so fast - no point until we figure out the priv pointer offset issue
# calling the show data function here does indeed end up calling the do_show_data function
# of the PythonHtmlWebkit subclass
#pdb.set_trace()
#self.html.show_data(docstr,len(docstr))
#print("junk")
def set_load_cb (self, load_cb, load_cb_data=None):
#pdb.set_trace()
self.load_cb = load_cb
self.load_cb_data = load_cb_data
def export_to_file (self, filepath):
#pdb.set_trace()
#if self.use_pywebkit:
# retval = self.webview.export_to_file(filepath)
if self.use_gncwebkit:
retval = self.html.export_to_file(filepath)
return retval
type_2_proto = { \
URLType.TYPE_FILE : "file",
URLType.TYPE_JUMP : "",
URLType.TYPE_HTTP : "http",
URLType.TYPE_FTP : "ftp",
URLType.TYPE_SECURE : "https",
URLType.TYPE_REGISTER : "gnc-register",
URLType.TYPE_ACCTTREE : "gnc-acct-tree",
URLType.TYPE_REPORT : "gnc-report",
URLType.TYPE_OPTIONS : "gnc-options",
URLType.TYPE_SCHEME : "gnc-scm",
URLType.TYPE_HELP : "gnc-help",
URLType.TYPE_XMLDATA : "gnc-xml",
URLType.TYPE_PRICE : "gnc-price",
URLType.TYPE_BUDGET : "gnc-budget",
URLType.TYPE_OTHER : "",
}
proto_2_type = {}
def build_url (type, location, label):
#type_name = gnc_html_type_to_proto_hash(type)
type_name = "file"
if label:
urlstr = "%s%s%s#%s"%(type_name,':' if type_name != "" else '', location if location != "" else "", label if label != "" else "")
else:
urlstr = "%s%s%s"%(type_name,':' if type_name != "" else '', location if location != "" else "")
return urlstr
def main ():
import girepo
# so this is how to get the address of the class structure!!
#trymod_klass = hash(GObject.type_class_peek(Try.Plugin))
#print("python gobject trymod klass address %x"%hash(GObject.type_class_peek(Try.Plugin)))
pdb.set_trace()
gobjflds = GncHtmlWebkit.HtmlWebkit.__info__.get_fields()
#gobjflds = Gtk.Container.__info__.get_fields()
for gobjfld in gobjflds:
print("gobjfld",gobjfld)
print("gobjfld",gobjfld.get_name())
field_info_obj = girepo.get_field_info(gobjfld)
offset = girepo.libgirepository.g_field_info_get_offset(field_info_obj)
print("gobjfld offset",offset)
#g_field_get_value(gobjfld, trymod_klass)
#g_field_get_value(Try.PluginClass.__dict__['__info__'].get_fields()[1], trymod_klass)
#g_field_get_value(Try.PluginClass.__dict__['__info__'].get_fields()[2], trymod_klass)
pass
if __name__ == '__main__':
main()