PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/graphingwiki/graphingwiki/plugin/macro/ClarifiedTopology.py

https://bitbucket.org/clarifiednetworks/graphingwiki/
Python | 429 lines | 413 code | 5 blank | 11 comment | 4 complexity | 2ebeb18d037fe0686241e332283fd545 MD5 | raw file
  1. # -*- coding: utf-8 -*-"
  2. """
  3. ClarifiedTopology macro plugin to MoinMoin/Graphingwiki
  4. - Shows the Topology information generated by Clarified
  5. Analyser as an image
  6. @copyright: 2008 by Juhani Eronen <exec@iki.fi>
  7. @license: MIT <http://www.opensource.org/licenses/mit-license.php>
  8. Permission is hereby granted, free of charge, to any person
  9. obtaining a copy of this software and associated documentation
  10. files (the "Software"), to deal in the Software without
  11. restriction, including without limitation the rights to use, copy,
  12. modify, merge, publish, distribute, sublicense, and/or sell copies
  13. of the Software, and to permit persons to whom the Software is
  14. furnished to do so, subject to the following conditions:
  15. The above copyright notice and this permission notice shall be
  16. included in all copies or substantial portions of the Software.
  17. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  19. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  21. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  22. WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  24. DEALINGS IN THE SOFTWARE.
  25. """
  26. import math
  27. import csv
  28. from MoinMoin.action import cache
  29. from MoinMoin.action import AttachFile
  30. from graphingwiki import cairo, cairo_surface_to_png
  31. from graphingwiki.plugin.action.ShowGraph import GraphShower
  32. from graphingwiki.editing import metatable_parseargs, get_metas
  33. from graphingwiki.util import (form_escape, make_tooltip, cache_key,
  34. cache_exists, latest_edit, encode_page,
  35. decode_page, render_error)
  36. Dependencies = ['metadata']
  37. def draw_topology(request, args, key):
  38. args = [x.strip() for x in args.split(',')]
  39. topology, flowfile, color = '', '', ''
  40. rotate, width = '', ''
  41. graph = GraphShower(request.page.page_name, request)
  42. # take flow file specification from arguments as flow=k.csv,
  43. # otherwise assume that the argument specifies the topology
  44. for arg in args:
  45. if '=' in arg:
  46. key, val = [x.strip() for x in arg.split('=', 1)]
  47. if key == 'color':
  48. color = val
  49. if key == 'flow':
  50. flowfile = val
  51. if key == 'rotate':
  52. rotate = True
  53. if key == 'width':
  54. try:
  55. width = float(val)
  56. except ValueError:
  57. pass
  58. else:
  59. topology = arg
  60. _ = request.getText
  61. # Get all containers
  62. args = 'CategoryContainer, %s=/.+/' % (topology)
  63. # Note, metatable_parseargs deals with permissions
  64. pagelist, metakeys, styles = metatable_parseargs(request, args,
  65. get_all_keys=True)
  66. if not pagelist:
  67. return (False, "", render_error("%s: %s" %
  68. (_("No such topology or empty topology"), topology)))
  69. coords = dict()
  70. images = dict()
  71. aliases = dict()
  72. areas = dict()
  73. colors = dict()
  74. # Make a context to calculate font sizes with
  75. # There must be a better way to do this, I just don't know it!
  76. surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0)
  77. ctx = cairo.Context(surface)
  78. ctx.select_font_face("Times-Roman", cairo.FONT_SLANT_NORMAL,
  79. cairo.FONT_WEIGHT_BOLD)
  80. ctx.set_font_size(12)
  81. allcoords = list()
  82. for page in pagelist:
  83. data = get_metas(request, page,
  84. [topology, 'gwikishapefile', 'tia-name', color],
  85. checkAccess=False, formatLinks=True)
  86. crds = [x.split(',') for x in data.get(topology, list)]
  87. if not crds:
  88. continue
  89. crds = [x.strip() for x in crds[0]]
  90. if not len(crds) == 2:
  91. continue
  92. try:
  93. start_x, start_y = int(crds[0]), int(crds[1])
  94. except ValueError:
  95. continue
  96. coords[page] = start_x, start_y
  97. allcoords.append((start_x, start_y))
  98. img = data.get('gwikishapefile', list())
  99. if color:
  100. clr = data.get(color, list())
  101. if clr:
  102. colors[page] = clr[0]
  103. alias = data.get('tia-name', list())
  104. # Newer versions of analyzer do not use aliases anymore
  105. if not alias:
  106. alias = [page]
  107. aliases[page] = alias[0]
  108. if img:
  109. # Get attachment name, name of the page it's on, strip
  110. # link artifacts, find filesys name
  111. img = img[0].split(':')[1]
  112. pname = '/'.join(img.split('/')[:-1])
  113. img = img.split('/')[-1]
  114. img = img.split('|')[0]
  115. img = img.rstrip('}').rstrip(']]')
  116. imgname = AttachFile.getFilename(request, pname, img)
  117. try:
  118. images[page] = cairo.ImageSurface.create_from_png(imgname)
  119. end_x = start_x + images[page].get_width()
  120. end_y = start_y + images[page].get_height()
  121. except cairo.Error:
  122. end_x = start_x
  123. end_y = start_y
  124. pass
  125. text_len = ctx.text_extents(aliases[page])[4]
  126. text_end = start_x + text_len
  127. if text_end > end_x:
  128. end_x = text_end
  129. # If there was no image or a problem with loading the image
  130. if page not in images:
  131. # Lack of image -> black 10x10 rectangle is drawn
  132. end_x, end_y = start_x + 10, start_y + 10
  133. allcoords.append((end_x, end_y))
  134. if flowfile:
  135. flowcoords = list()
  136. flowname = AttachFile.getFilename(request, topology, flowfile)
  137. try:
  138. flows = csv.reader(file(flowname, 'r').readlines(), delimiter=';')
  139. except IOError:
  140. return (False, "", render_error("%s: %s" %
  141. (_("No such flowfile as attachment on topology page"),
  142. flowfile)))
  143. flows.next()
  144. for line in flows:
  145. if not line:
  146. continue
  147. try:
  148. flowcoords.append((line[0], line[6]))
  149. except IndexError:
  150. # Pasted broken lines?
  151. pass
  152. max_x = max([x[0] for x in allcoords])
  153. min_x = min([x[0] for x in allcoords])
  154. max_y = max([x[1] for x in allcoords])
  155. min_y = min([x[1] for x in allcoords])
  156. # Make room for text under pictures
  157. if rotate:
  158. surface_y = max_y - min_y
  159. surface_x = max_x - min_x + 25
  160. else:
  161. surface_y = max_y - min_y + 25
  162. surface_x = max_x - min_x
  163. try:
  164. # Get background image, if any
  165. toponame = AttachFile.getFilename(request, topology, 'shapefile.png')
  166. background = cairo.ImageSurface.create_from_png(toponame)
  167. h = background.get_height()
  168. w = background.get_width()
  169. diff_x = w - surface_x
  170. diff_y = h - surface_y
  171. if diff_x > 0:
  172. surface_x = w
  173. else:
  174. diff_x = 0
  175. if diff_y > 0:
  176. surface_y = h
  177. else:
  178. diff_y = 0
  179. except cairo.Error:
  180. background = None
  181. diff_x = 0
  182. diff_y = 0
  183. pass
  184. # Setup Cairo
  185. surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
  186. int(surface_x), int(surface_y))
  187. # request.write(repr([surface_x, surface_y]))
  188. ctx = cairo.Context(surface)
  189. ctx.select_font_face("Times-Roman", cairo.FONT_SLANT_NORMAL,
  190. cairo.FONT_WEIGHT_BOLD)
  191. ctx.set_font_size(12)
  192. ctx.set_source_rgb(1.0, 1.0, 1.0)
  193. ctx.rectangle(0, 0, surface_x, surface_y)
  194. ctx.fill()
  195. if background:
  196. # Center background if not large. Again, I'm just guessing
  197. # where the background image should be, and trying to mimic
  198. # analyzer.
  199. h = background.get_height()
  200. w = background.get_width()
  201. start_x, start_y = 0, 0
  202. if w < surface_x:
  203. start_x = start_x - min_x - w/2
  204. if h < surface_y:
  205. start_y = start_y - min_y - h/2
  206. ctx.set_source_surface(background, start_x, start_y)
  207. ctx.rectangle(start_x, start_y, w, h)
  208. ctx.fill()
  209. midcoords = dict()
  210. for page in pagelist:
  211. if page not in coords:
  212. continue
  213. x, y = coords[page]
  214. # FIXME need more data to align different backgrounds
  215. # correctly, this is just guessing
  216. start_x = x - min_x + (diff_x / 2)
  217. start_y = y - min_y + (diff_y / 3)
  218. w, h = 10, 10
  219. if page not in images:
  220. ctx.set_source_rgb(0, 0, 0)
  221. else:
  222. h = images[page].get_height()
  223. w = images[page].get_width()
  224. if page in colors:
  225. clr = graph.hashcolor(colors[page])
  226. r, g, b = [int(''.join(i), 16) / 255.0 for i in
  227. zip(clr[1::2], clr[2::2])]
  228. ctx.set_source_rgb(r, g, b)
  229. else:
  230. ctx.set_source_rgb(1, 1, 1)
  231. midcoords[page] = (start_x + w / 2, start_y + h / 2)
  232. ctx.rectangle(start_x, start_y, w, h)
  233. ctx.fill()
  234. if page in images:
  235. ctx.set_source_surface(images[page], start_x, start_y)
  236. ctx.rectangle(start_x, start_y, w, h)
  237. ctx.fill()
  238. text = make_tooltip(request, page)
  239. areas["%s,%s,%s,%s" % (start_x, start_y, start_x + w, start_y + h)] = \
  240. [page, text, 'rect']
  241. if page in aliases:
  242. ctx.set_source_rgb(0, 0, 0)
  243. if rotate:
  244. ctx.move_to(start_x + w + 10, start_y + h)
  245. else:
  246. ctx.move_to(start_x, start_y + h + 10)
  247. # FIXME, should parse links more nicely, now just removes
  248. # square brackets
  249. text = aliases[page].lstrip('[').rstrip(']')
  250. if rotate:
  251. ctx.rotate(-90.0*math.pi/180.0)
  252. ctx.show_text(text)
  253. if rotate:
  254. ctx.rotate(90.0*math.pi/180.0)
  255. if flowfile:
  256. ctx.set_line_width(1)
  257. ctx.set_source_rgb(0, 0, 0)
  258. for start, end in flowcoords:
  259. if (start not in midcoords) or (end not in midcoords):
  260. continue
  261. sx, sy = midcoords[start]
  262. ex, ey = midcoords[end]
  263. ctx.move_to(sx, sy)
  264. ctx.line_to(ex, ey)
  265. ctx.stroke()
  266. s2 = surface
  267. if width:
  268. # For scaling
  269. new_surface_y = width
  270. factor = surface_y/new_surface_y
  271. new_surface_x = surface_x / factor
  272. # Recalculate image map data
  273. newareas = dict()
  274. for coords, data in areas.iteritems():
  275. corners = [float(i) for i in coords.split(',')]
  276. corners = tuple(i / factor for i in corners)
  277. newareas['%s,%s,%s,%s' % corners] = data
  278. areas = newareas
  279. else:
  280. new_surface_y = surface_y
  281. new_surface_x = surface_x
  282. if rotate:
  283. temp = new_surface_x
  284. new_surface_x = new_surface_y
  285. new_surface_y = temp
  286. temp = surface_x
  287. surface_x = surface_y
  288. surface_y = temp
  289. s2 = cairo.ImageSurface(cairo.FORMAT_ARGB32,
  290. int(new_surface_x), int(new_surface_y))
  291. ctx = cairo.Context(s2)
  292. if rotate:
  293. ctx.rotate(90.0*math.pi/180.0)
  294. # Recalculate image map data
  295. newareas = dict()
  296. for coords, data in areas.iteritems():
  297. corners = coords.split(',')
  298. corners = [float(i) for i in coords.split(',')]
  299. corners = tuple([new_surface_x - corners[1], corners[0],
  300. new_surface_x - corners[3], corners[2]])
  301. newareas['%s,%s,%s,%s' % corners] = data
  302. areas = newareas
  303. if width:
  304. ctx.scale(new_surface_x/surface_x, new_surface_y/surface_y)
  305. if rotate:
  306. ctx.translate(0, -surface_x)
  307. ctx.set_source_surface(surface, 0, 0)
  308. ctx.paint()
  309. data = cairo_surface_to_png(s2)
  310. map = ''
  311. for coords in areas:
  312. name, text, shape = areas[coords]
  313. pagelink = request.script_root + u'/' + name
  314. tooltip = "%s\n%s" % (name, text)
  315. map += u'<area href="%s" shape="%s" coords="%s" title="%s">\n' % \
  316. (form_escape(pagelink), shape, coords, tooltip)
  317. return True, data, map
  318. def execute(macro, args):
  319. formatter = macro.formatter
  320. macro.request.page.formatter = formatter
  321. request = macro.request
  322. _ = request.getText
  323. if not args:
  324. args = request.page.page_name
  325. key = cache_key(request, (macro.name, args, latest_edit(request)))
  326. map_text = 'usemap="#%s" ' % (key)
  327. if not cache_exists(request, key):
  328. succ, data, mappi = draw_topology(request, args, key)
  329. if not succ:
  330. return mappi
  331. mappi = encode_page(mappi)
  332. cache.put(request, key, data, content_type='image/png')
  333. cache.put(request, key + '-map', mappi, content_type='text/html')
  334. else:
  335. mappifile = cache._get_datafile(request, key + '-map')
  336. mappi = mappifile.read()
  337. mappifile.close()
  338. div = u'<div class="ClarifiedTopology">\n' + \
  339. u'<img %ssrc="%s" alt="%s">\n</div>\n' % \
  340. (map_text, cache.url(request, key), _('topology'))
  341. map = u'<map id="%s" name="%s">\n' % (key, key)
  342. map += decode_page(mappi)
  343. map += u'</map>\n'
  344. return div + map