PageRenderTime 44ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/textmap.py

https://github.com/ddev/textmap
Python | 1199 lines | 832 code | 195 blank | 172 comment | 215 complexity | 47fd939ed6f8eeb532417048fa11caeb MD5 | raw file
  1. # Copyright 2011, Dan Gindikin <dgindikin@gmail.com>
  2. #
  3. # This program is free software; you can redistribute it and/or modify it under
  4. # the terms of the GNU General Public License as published by the Free Software
  5. # Foundation; either version 2 of the License, or (at your option) any later
  6. # version.
  7. #
  8. # This program is distributed in the hope that it will be useful, but WITHOUT
  9. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  10. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  11. # details.
  12. #
  13. # You should have received a copy of the GNU General Public License along with
  14. # this program; if not, write to the Free Software Foundation, Inc., 51
  15. # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. import gtk
  17. import time
  18. import gobject
  19. import gedit
  20. import sys
  21. import math
  22. import cairo
  23. import re
  24. import copy
  25. import platform
  26. version = "0.2 beta"
  27. # ------------------------------------------------------------------------------
  28. # These regular expressions are applied in sequence ot each line, to determine
  29. # whether it is a section start or not
  30. SectionREs = (
  31. re.compile('def\s*(\w+)\s*\('), # python method
  32. re.compile('class\s*(\w+)\s*'), # python/java class
  33. re.compile('cdef\s*class\s*(\w+)\s*[(:]'), # cython class
  34. re.compile('cdef\s*(?:[\w\.]*?\**\s*)?(\w+)\s*\('), # cython method
  35. re.compile('^(\w*)\s*\('), # C method
  36. re.compile('^\w+[\w\s\*]*?(\w*)\s*\('), # C method
  37. re.compile('^function\s*(\w+)\s*\('), # javascript method
  38. re.compile('\w+[\w\s]*?class (\w*)'), # java class
  39. )
  40. SubsectionREs = (
  41. re.compile('\s+def\s*(\w+)\s*\('), # python class method
  42. re.compile('\s+cdef\s*(?:[\w\.]*?\**\s*)?(\w+)\s*\('), # cython class method
  43. re.compile('\s+(?:public|static|private|final)[\w\s]*?(\w+)\s*\('), # java method
  44. )
  45. # ------------------------------------------------------------------------------
  46. class struct:pass
  47. class TimeRec:
  48. def __init__(M):
  49. M.tot = M.N = M.childtot = M.heretot = 0
  50. M.start_ = None
  51. class Timer:
  52. 'L == label'
  53. def __init__(M):
  54. M.dat = {}
  55. M.stack = []
  56. def push(M,L):
  57. assert L not in M.stack,(L,M.stack)
  58. M.stack.append(L)
  59. tmrec = M.dat.setdefault(L,TimeRec())
  60. tmrec.start_=time.time()
  61. def pop(M,L):
  62. assert M.stack[-1]==L,(L,M.stack)
  63. M.stack.pop()
  64. tmrec = M.dat[L]
  65. dur = time.time()-tmrec.start_
  66. tmrec.start_ = None
  67. tmrec.tot += dur
  68. tmrec.N += 1
  69. #for parent in M.stack:
  70. # M.dat[parent].childtot += dur
  71. if M.stack <> []:
  72. M.dat[M.stack[-1]].childtot += dur
  73. def print_(M):
  74. for tmrec in M.dat.values():
  75. tmrec.heretot = tmrec.tot-tmrec.childtot
  76. R = sorted(M.dat.items(),lambda x,y:-cmp(x[1].heretot,y[1].heretot))
  77. print '%7s %7s %5s' % ('Tm Here', 'Tm Avg', 'Count')
  78. for L,tmrec in R:
  79. print '%7s %7s %5d %s' % ('%.3f'%tmrec.heretot, '%.3f'%(tmrec.heretot/float(tmrec.N)), tmrec.N, L)
  80. print
  81. #TIMER = Timer()
  82. TIMER = None
  83. def indent(s):
  84. x = 0
  85. for c in s:
  86. if c == ' ':
  87. x += 1
  88. elif c == '\t':
  89. x += 8
  90. else:
  91. break
  92. return x
  93. def probj(ob,*substrs):
  94. meths = dir(ob)
  95. meths.sort()
  96. print ob,type(ob)
  97. for m in meths:
  98. doprint=True
  99. if substrs:
  100. doprint=False
  101. for s in substrs:
  102. if s in m:
  103. doprint=True
  104. break
  105. if doprint:
  106. print '%40s'%m
  107. def match_RE_list(str, REs):
  108. for r in REs:
  109. m = r.match(str)
  110. if m:
  111. return m.groups()[0]
  112. return None
  113. def document_lines(document):
  114. if not document:
  115. return None
  116. #print 'document_lines',document
  117. STR = document.get_property('text')
  118. lines = STR.split('\n')
  119. ans = []
  120. for i,each in enumerate(lines):
  121. x = struct()
  122. x.i = i
  123. x.len = len(each)
  124. x.indent = indent(each)
  125. x.raw = each
  126. x.section = match_RE_list(x.raw,SectionREs)
  127. x.subsection = None
  128. x.search_match = False
  129. if not x.section:
  130. x.subsection = match_RE_list(x.raw,SubsectionREs)
  131. if x.section or x.subsection:
  132. match = Split_Off_Indent_Pattern.match(x.raw)
  133. x.indentSTR = None
  134. x.justextSTR = None
  135. if match:
  136. groups = match.groups()
  137. if len(groups) == 2:
  138. x.indentSTR, x.justextSTR = groups
  139. ans.append(x)
  140. return ans
  141. def lines_add_section_len(lines):
  142. line_prevsection = None
  143. counter = 0
  144. for i, line in enumerate(lines):
  145. if line.section:
  146. if line_prevsection:
  147. line_prevsection.section_len = counter
  148. line_prevsection = line
  149. counter = 0
  150. counter += 1
  151. if line_prevsection:
  152. line_prevsection.section_len = counter
  153. return lines
  154. def lines_mark_changed_sections(lines):
  155. sec = None
  156. subsec = None
  157. for line in lines:
  158. if line.section:
  159. line.sectionchanged = False
  160. sec = line
  161. subsec = None
  162. if line.subsection:
  163. line.subsectionchanged = False
  164. subsec = line
  165. if line.changed:
  166. if sec is not None:
  167. sec.sectionchanged = True
  168. if subsec is not None:
  169. subsec.subsectionchanged = True
  170. return lines
  171. BUG_MASK = 0
  172. BUG_CAIRO_MAC_FONT_REF = 1
  173. BUG_CAIRO_TEXT_EXTENTS = 2
  174. BUG_DOC_GET_SEARCH_TEXT = 4
  175. if platform.system() == 'Darwin':
  176. BUG_MASK |= BUG_CAIRO_MAC_FONT_REF # extra decref causes aborts, use less font ops
  177. major,minor,patch = gedit.version
  178. if major<=2 and minor<28:
  179. BUG_MASK |= BUG_CAIRO_TEXT_EXTENTS # some reference problem
  180. BUG_MASK |= BUG_DOC_GET_SEARCH_TEXT # missing INCREF then
  181. def text_extents(str,cr):
  182. "code around bug in older cairo"
  183. if BUG_MASK & BUG_CAIRO_TEXT_EXTENTS:
  184. if str:
  185. x, y = cr.get_current_point()
  186. cr.move_to(0,-5)
  187. cr.show_text(str)
  188. nx,ny = cr.get_current_point()
  189. cr.move_to(x,y)
  190. else:
  191. nx = 0
  192. ny = 0
  193. #print repr(str),x,nx,y,ny
  194. ascent, descent, height, max_x_advance, max_y_advance = cr.font_extents()
  195. return nx, height
  196. else:
  197. x_bearing, y_bearing, width, height, x_advance, y_advance = cr.text_extents(str)
  198. return width, height
  199. def pr_text_extents(s,cr):
  200. x_bearing, y_bearing, width, height, x_advance, y_advance = cr.text_extents(s)
  201. print repr(s),':','x_bearing',x_bearing,'y_bearing',y_bearing,'width',width,'height',height,'x_advance',x_advance,'y_advance',y_advance
  202. def show_section_label(str, fg, bg, cr):
  203. tw,th = text_extents(str,cr)
  204. x,y = cr.get_current_point()
  205. cr.set_source_rgba(bg[0],bg[1],bg[2],.75)
  206. cr.rectangle(x,y-th+3,tw,th)
  207. cr.fill()
  208. cr.move_to(x,y)
  209. cr.set_source_rgb(*fg)
  210. cr.show_text(str)
  211. def fit_text(str, w, h, fg, bg, cr):
  212. moved_down = False
  213. originalx,_ = cr.get_current_point()
  214. sofarH = 0
  215. rn = []
  216. if dark(*bg):
  217. bg_rect_C = lighten(.1,*bg)
  218. else:
  219. bg_rect_C = darken(.1,*bg)
  220. while 1:
  221. # find the next chunk of the string that fits
  222. for i in range(len(str)):
  223. tw, th = text_extents(str[:i],cr)
  224. if tw > w:
  225. break
  226. disp = str[:i+1]
  227. str = str[i+1:]
  228. tw, th = text_extents(disp,cr)
  229. sofarH += th
  230. if sofarH > h:
  231. return rn
  232. if not moved_down:
  233. moved_down = True
  234. cr.rel_move_to(0, th)
  235. # bg rectangle
  236. x,y = cr.get_current_point()
  237. #cr.set_source_rgba(46/256.,52/256.,54/256.,.75)
  238. cr.set_source_rgba(bg_rect_C[0],bg_rect_C[1],bg_rect_C[2],.75)
  239. if str:
  240. cr.rectangle(x,y-th+2,tw,th+3)
  241. else: # last line does not need a very big rectangle
  242. cr.rectangle(x,y-th+2,tw,th)
  243. cr.fill()
  244. cr.move_to(x,y)
  245. # actually display
  246. cr.set_source_rgb(*fg)
  247. cr.show_text(disp)
  248. # remember
  249. rec = struct()
  250. rec.x = x
  251. rec.y = y
  252. rec.th = th
  253. rec.tw = tw
  254. rn.append(rec)
  255. cr.rel_move_to(0,th+3)
  256. x,y = cr.get_current_point()
  257. cr.move_to(originalx,y)
  258. if not str:
  259. break
  260. return rn
  261. def downsample_lines(lines, h, min_scale, max_scale):
  262. n = len(lines)
  263. # pick scale
  264. for scale in range(max_scale,min_scale-1,-1):
  265. maxlines_ = h/(.85*scale)
  266. if n < 2*maxlines_:
  267. break
  268. if n <= maxlines_:
  269. downsampled = False
  270. return lines, scale, downsampled
  271. # need to downsample
  272. lines[0].score = sys.maxint # keep the first line
  273. for i in range(1, len(lines)):
  274. if lines[i].section: # keep sections
  275. lines[i].score = sys.maxint
  276. elif lines[i].subsection:
  277. lines[i].score = sys.maxint/2
  278. elif lines[i].changed or lines[i].search_match:
  279. lines[i].score = sys.maxint/2
  280. else:
  281. if 0: # get rid of lines that are very different
  282. lines[i].score = abs(lines[i].indent-lines[i-1].indent) \
  283. + abs(len(lines[i].raw)-len(lines[i-1].raw))
  284. if 1: # get rid of lines randomly
  285. lines[i].score = hash(lines[i].raw)
  286. if lines[i].score > sys.maxint/2:
  287. lines[i].score -= sys.maxint/2
  288. scoresorted = sorted(lines, lambda x,y: cmp(x.score,y.score))
  289. erasures_ = int(math.ceil(n - maxlines_))
  290. #print 'erasures_',erasures_
  291. scoresorted[0:erasures_]=[]
  292. downsampled = True
  293. return sorted(scoresorted, lambda x,y:cmp(x.i,y.i)), scale, downsampled
  294. def visible_lines_top_bottom(geditwin):
  295. view = geditwin.get_active_view()
  296. rect = view.get_visible_rect()
  297. topiter = view.get_line_at_y(rect.y)[0]
  298. botiter = view.get_line_at_y(rect.y+rect.height)[0]
  299. return topiter.get_line(), botiter.get_line()
  300. def dark(r,g,b):
  301. "return whether the color is light or dark"
  302. if r+g+b < 1.5:
  303. return True
  304. else:
  305. return False
  306. def darken(fraction,r,g,b):
  307. return r-fraction*r,g-fraction*g,b-fraction*b
  308. def lighten(fraction,r,g,b):
  309. return r+(1-r)*fraction,g+(1-g)*fraction,b+(1-b)*fraction
  310. def scrollbar(lines,topI,botI,w,h,bg,cr,scrollbarW=10):
  311. "top and bot a passed as line indices"
  312. # figure out location
  313. topY = None
  314. botY = None
  315. for line in lines:
  316. if not topY:
  317. if line.i >= topI:
  318. topY = line.y
  319. if not botY:
  320. if line.i >= botI:
  321. botY = line.y
  322. if topY is None:
  323. topY = 0
  324. if botY is None:
  325. botY = lines[-1].y
  326. if 0: # bg rectangle
  327. cr.set_source_rgba(.1,.1,.1,.35)
  328. cr.rectangle(w-scrollbarW,0,scrollbarW,topY)
  329. cr.fill()
  330. cr.rectangle(w-scrollbarW,botY,scrollbarW,h-botY)
  331. cr.fill()
  332. if 0: # scheme 1
  333. cr.set_line_width(1)
  334. #cr.set_source_rgb(0,0,0)
  335. #cr.set_source_rgb(1,1,1)
  336. cr.set_source_rgb(0xd3/256.,0xd7/256.,0xcf/256.)
  337. if 0: # big down line
  338. cr.set_source_rgb(0xd3/256.,0xd7/256.,0xcf/256.)
  339. cr.move_to(w-scrollbarW/2.,0)
  340. cr.line_to(w-scrollbarW/2.,topY)
  341. cr.stroke()
  342. cr.move_to(w-scrollbarW/2.,botY)
  343. cr.line_to(w-scrollbarW/2.,h)
  344. cr.stroke()
  345. if 0:
  346. cr.rectangle(w-scrollbarW,topY,scrollbarW-1,botY-topY)
  347. cr.stroke()
  348. if 1: # bottom lines
  349. #cr.set_line_width(2)
  350. #cr.move_to(w-scrollbarW,topY)
  351. cr.move_to(0,topY)
  352. cr.line_to(w,topY)
  353. cr.stroke()
  354. cr.move_to(0,botY)
  355. cr.line_to(w,botY)
  356. cr.stroke()
  357. if 0: # rect
  358. cr.set_source_rgba(.5,.5,.5,.1)
  359. #cr.set_source_rgba(.1,.1,.1,.35)
  360. #cr.rectangle(w-scrollbarW,topY,scrollbarW,botY-topY)
  361. cr.rectangle(0,topY,w,botY-topY)
  362. cr.fill()
  363. if 0: # scheme 2
  364. cr.set_line_width(3)
  365. cr.set_source_rgb(0xd3/256.,0xd7/256.,0xcf/256.)
  366. if 1: # bottom lines
  367. cr.move_to(0,topY)
  368. cr.line_to(w,topY)
  369. cr.stroke()
  370. cr.move_to(0,botY)
  371. cr.line_to(w,botY)
  372. cr.stroke()
  373. if 1: # side lines
  374. cr.set_line_width(2)
  375. len = (botY-topY)/8
  376. margin = 1
  377. if 0: # left
  378. cr.move_to(margin,topY)
  379. cr.line_to(margin,topY+len)
  380. cr.stroke()
  381. cr.move_to(margin,botY-len)
  382. cr.line_to(margin,botY)
  383. cr.stroke()
  384. if 1: # right
  385. cr.move_to(w-margin,topY)
  386. cr.line_to(w-margin,topY+len)
  387. cr.stroke()
  388. cr.move_to(w-margin,botY-len)
  389. cr.line_to(w-margin,botY)
  390. cr.stroke()
  391. if 0: # center
  392. len = (botY-topY)/5
  393. cx = w/2
  394. cy = topY+(botY-topY)/2
  395. if 1: # vert
  396. for x in (cx,):#(cx-len/2,cx,cx+len/2):
  397. cr.move_to(x,cy-len/2)
  398. cr.line_to(x,cy+len/2)
  399. cr.stroke()
  400. if 0: # horiz
  401. cr.move_to(cx-len/2,cy)
  402. cr.line_to(cx+len/2,cy)
  403. cr.stroke()
  404. if 0: # view indicator
  405. cr.set_source_rgba(.5,.5,.5,.5)
  406. #cr.set_source_rgba(.1,.1,.1,.35)
  407. cr.rectangle(w-scrollbarW,topY,scrollbarW,botY-topY)
  408. cr.fill()
  409. cr.rectangle(w-scrollbarW,topY,scrollbarW-1,botY-topY)
  410. cr.set_line_width(.5)
  411. cr.set_source_rgb(1,1,1)
  412. #cr.set_source_rgb(0,0,0)
  413. cr.stroke()
  414. if 0: # lines
  415. cr.set_source_rgb(1,1,1)
  416. cr.move_to(w,0)
  417. cr.line_to(w-scrollbarW,topY)
  418. cr.line_to(w-scrollbarW,botY)
  419. cr.line_to(w,h)
  420. cr.stroke()
  421. if 0: # scheme 3
  422. if 1: # black lines
  423. cr.set_line_width(2)
  424. cr.set_source_rgb(0,0,0)
  425. cr.move_to(0,topY)
  426. cr.line_to(w,topY)
  427. cr.stroke()
  428. cr.move_to(0,botY)
  429. cr.line_to(w,botY)
  430. cr.stroke()
  431. if 1: # white lines
  432. cr.set_line_width(2)
  433. cr.set_dash([1,2])
  434. cr.set_source_rgb(1,1,1)
  435. cr.move_to(0,topY)
  436. cr.line_to(w,topY)
  437. cr.stroke()
  438. cr.move_to(0,botY)
  439. cr.line_to(w,botY)
  440. cr.stroke()
  441. if 0: # scheme 4
  442. pat = cairo.LinearGradient(0,topY-10,0,topY)
  443. pat.add_color_stop_rgba(0, 1, 1, 1,1)
  444. pat.add_color_stop_rgba(1, .2,.2,.2,1)
  445. pat.add_color_stop_rgba(2, 0, 0, 0,1)
  446. cr.rectangle(0,topY-10,w,10)
  447. cr.set_source(pat)
  448. cr.fill()
  449. if 0: # triangle right
  450. # triangle
  451. size=12
  452. midY = topY+(botY-topY)/2
  453. cr.set_line_width(2)
  454. cr.set_source_rgb(1,1,1)
  455. cr.move_to(w-size-1,midY)
  456. cr.line_to(w-1,midY-size/2)
  457. #cr.stroke_preserve()
  458. cr.line_to(w-1,midY+size/2)
  459. #cr.stroke_preserve()
  460. cr.line_to(w-size-1,midY)
  461. cr.fill()
  462. # line
  463. cr.move_to(w-2,topY+2)
  464. cr.line_to(w-2,botY-2)
  465. cr.stroke()
  466. if dark(*bg):
  467. color = (1,1,1)
  468. else:
  469. color = (0,0,0)
  470. if 0: # triangle left
  471. # triangle
  472. size=12
  473. midY = topY+(botY-topY)/2
  474. cr.set_line_width(2)
  475. cr.set_source_rgb(*color)
  476. cr.move_to(size+1,midY)
  477. cr.line_to(1,midY-size/2)
  478. #cr.stroke_preserve()
  479. cr.line_to(1,midY+size/2)
  480. #cr.stroke_preserve()
  481. cr.line_to(size+1,midY)
  482. cr.fill()
  483. # line
  484. #cr.move_to(2,topY+2)
  485. #cr.line_to(2,botY-2)
  486. #cr.stroke()
  487. if 1: # dashed lines
  488. cr.set_line_width(2)
  489. cr.set_source_rgb(*color)
  490. cr.set_dash([8,8])
  491. #cr.rectangle(2,topY,w-4,botY-topY)
  492. cr.move_to(4,topY); cr.line_to(w,topY)
  493. cr.stroke()
  494. cr.move_to(4,botY); cr.line_to(w,botY)
  495. cr.stroke()
  496. def queue_refresh(textmapview):
  497. try:
  498. win = textmapview.darea.get_window()
  499. except AttributeError:
  500. win = textmapview.darea.window
  501. if win:
  502. w,h = win.get_size()
  503. textmapview.darea.queue_draw_area(0,0,w,h)
  504. def str2rgb(s):
  505. assert s.startswith('#') and len(s)==7,('not a color string',s)
  506. r = int(s[1:3],16)/256.
  507. g = int(s[3:5],16)/256.
  508. b = int(s[5:7],16)/256.
  509. return r,g,b
  510. def init_original_lines_info(doc,lines):
  511. rn = []
  512. # now we insert marks at the end of every line
  513. iter = doc.get_start_iter()
  514. n = 0
  515. while 1:
  516. if n>=len(lines):
  517. break
  518. more_left = iter.forward_line()
  519. rec = struct()
  520. lines[n].mark = doc.create_mark(None,iter,False)
  521. n+=1
  522. if not more_left:
  523. break
  524. assert n>=len(lines)-1,(n,len(lines),'something off with our iterator logic')
  525. if n==len(lines)-1:
  526. lines[-1].mark=doc.create_mark(None,doc.get_end_iter(),False)
  527. return lines
  528. def mark_changed_lines(doc,original,current):
  529. 'unfortunate choice of name, has nothing to do with GtkTextBuffer marks'
  530. # presume all current lines are changed
  531. for line in current:
  532. line.changed = True
  533. # mark any original lines we find as unchanged
  534. start = doc.get_start_iter()
  535. c=0
  536. for oline in original:
  537. end = doc.get_iter_at_mark(oline.mark)
  538. slice = doc.get_slice(start,end)
  539. # see if the first line between the marks is the original line
  540. if slice.split('\n',1)[0] == oline.raw:
  541. current[c].changed = False
  542. # forward through all the slice lines
  543. c += slice.count('\n')
  544. start = end
  545. return current
  546. def lines_mark_search_matches(lines,docrec):
  547. for line in lines:
  548. if docrec.search_text and docrec.search_text in line.raw:
  549. line.search_match = True
  550. else:
  551. line.search_match = False
  552. return lines
  553. Split_Off_Indent_Pattern = re.compile('(\s*)(.*)$')
  554. class TextmapView(gtk.VBox):
  555. def __init__(me, geditwin):
  556. gtk.VBox.__init__(me)
  557. me.geditwin = geditwin
  558. darea = gtk.DrawingArea()
  559. darea.connect("expose-event", me.expose)
  560. darea.add_events(gtk.gdk.BUTTON_PRESS_MASK)
  561. darea.connect("button-press-event", me.button_press)
  562. darea.connect("scroll-event", me.on_darea_scroll_event)
  563. darea.add_events(gtk.gdk.ENTER_NOTIFY_MASK)
  564. darea.connect("enter-notify-event", me.on_darea_enter_notify_event)
  565. darea.add_events(gtk.gdk.LEAVE_NOTIFY_MASK)
  566. darea.connect("leave-notify-event", me.on_darea_leave_notify_event)
  567. darea.add_events(gtk.gdk.POINTER_MOTION_MASK)
  568. darea.connect("motion-notify-event", me.on_darea_motion_notify_event)
  569. me.pack_start(darea, True, True)
  570. me.darea = darea
  571. #probj(me.darea)
  572. me.connected = {}
  573. me.draw_scrollbar_only = False
  574. me.draw_sections = False
  575. me.topL = None
  576. me.surface_textmap = None
  577. me.line_count = 0
  578. me.doc_attached_data = {}
  579. me.show_all()
  580. # need this bc of a cairo bug, keep references to all our font faces
  581. me.font_face_keepalive = None
  582. me.lines = None
  583. #'''
  584. # gtk.gdk.SCROLL_UP,
  585. # gtk.gdk.SCROLL_DOWN,
  586. # gtk.gdk.SCROLL_LEFT,
  587. # gtk.gdk.SCROLL_RIGHT
  588. #
  589. #Example:
  590. #
  591. # def on_button_scroll_event(button, event):
  592. # if event.direction == gtk.gdk.SCROLL_UP:
  593. # print "You scrolled up"
  594. #
  595. #event = gtk.gdk.Event(gtk.gdk.EXPOSE)
  596. #
  597. # def motion_notify(ruler, event):
  598. # return ruler.emit("motion_notify_event", event)
  599. # self.area.connect_object("motion_notify_event", motion_notify,
  600. # self.hruler)
  601. # self.area.connect_object("motion_notify_event", motion_notify,
  602. # self.vruler)
  603. #'''
  604. def on_darea_motion_notify_event(me, widget, event):
  605. #probj(event)
  606. #print event.type
  607. if event.state & gtk.gdk.BUTTON1_MASK:
  608. me.scroll_from_y_mouse_pos(event.y)
  609. def on_darea_enter_notify_event(me, widget, event):
  610. if event.mode.value_name == 'GDK_CROSSING_GTK_UNGRAB':
  611. return
  612. #print 'in here enter'
  613. me.draw_sections = True
  614. queue_refresh(me)
  615. def on_darea_leave_notify_event(me, widget, event):
  616. #print 'in here leaving'
  617. me.draw_sections = False
  618. queue_refresh(me)
  619. def on_darea_scroll_event(me, widget, event):
  620. pass
  621. #print 'XXX on_darea_scroll_event'
  622. # this scheme does not work
  623. # somehow pass this on, scroll the document/view
  624. #print type(widget),widget,type(event),event
  625. #probj(event)
  626. #view = me.geditwin.get_active_view()
  627. #if not view:
  628. # return
  629. #return view.emit('scroll-event',event)
  630. # the following crashes
  631. #pagesize = 12
  632. #topI,botI = visible_lines_top_bottom(me.geditwin)
  633. #if event.direction == gtk.gdk.SCROLL_UP:
  634. # newI = topI - pagesize
  635. #elif event.direction == gtk.gdk.SCROLL_DOWN:
  636. # newI = botI + pagesize
  637. #else:
  638. # return
  639. #
  640. #view = me.geditwin.get_active_view()
  641. #doc = me.geditwin.get_active_tab().get_document()
  642. #view.scroll_to_iter(doc.get_iter_at_line_index(newI,0),0,False,0,0)
  643. #
  644. #queue_refresh(me)
  645. def on_doc_cursor_moved(me, doc):
  646. #new_line_count = doc.get_line_count()
  647. #print 'new_line_count',new_line_count
  648. topL = visible_lines_top_bottom(me.geditwin)[0]
  649. if topL <> me.topL:
  650. queue_refresh(me)
  651. me.draw_scrollbar_only = True
  652. def on_insert_text(me, doc, piter, text, len):
  653. pass
  654. #if len < 20 and '\n' in text:
  655. # print 'piter',piter,'text',repr(text),'len',len
  656. def scroll_from_y_mouse_pos(me,y):
  657. for line in me.lines:
  658. if line.y > y:
  659. break
  660. #print line.i, repr(line.raw)
  661. view = me.geditwin.get_active_view()
  662. doc = me.geditwin.get_active_tab().get_document()
  663. #doc.place_cursor(doc.get_iter_at_line_index(line.i,0))
  664. #view.scroll_to_cursor()
  665. #print view
  666. view.scroll_to_iter(doc.get_iter_at_line_index(line.i,0),0,True,0,.5)
  667. queue_refresh(me)
  668. def button_press(me, widget, event):
  669. me.scroll_from_y_mouse_pos(event.y)
  670. def on_scroll_finished(me):
  671. #print 'in here',me.last_scroll_time,time.time()-me.last_scroll_time
  672. if time.time()-me.last_scroll_time > .47:
  673. if me.draw_sections:
  674. me.draw_sections = False
  675. me.draw_scrollbar_only = False
  676. queue_refresh(me)
  677. return False
  678. def on_scroll_event(me,view,event):
  679. me.last_scroll_time = time.time()
  680. if 0:
  681. if me.draw_sections: # we are in the middle of scrolling
  682. me.draw_scrollbar_only = True
  683. else:
  684. me.draw_sections = True # for the first scroll, turn on section names
  685. gobject.timeout_add(500,me.on_scroll_finished) # this will fade out sections
  686. me.draw_scrollbar_only = True
  687. queue_refresh(me)
  688. def on_search_highlight_updated(me,doc,t,u):
  689. #print 'on_search_highlight_updated:',repr(doc.get_search_text())
  690. if id(doc) not in me.doc_attached_data:
  691. queue_refresh(me)
  692. return
  693. docrec = me.doc_attached_data[id(doc)]
  694. s = doc.get_search_text()[0]
  695. if s <> docrec.search_text:
  696. docrec.search_text = s
  697. queue_refresh(me)
  698. def test_event(me, ob, event):
  699. print 'here',ob
  700. def save_refs_to_all_font_faces(me, cr, *scales):
  701. me.font_face_keepalive = []
  702. for each in scales:
  703. cr.set_font_size(each)
  704. me.font_face_keepalive.append(cr.get_font_face())
  705. def expose(me, widget, event):
  706. doc = me.geditwin.get_active_tab().get_document()
  707. if not doc: # nothing open yet
  708. return
  709. if id(doc) not in me.connected:
  710. me.connected[id(doc)] = True
  711. doc.connect("cursor-moved", me.on_doc_cursor_moved)
  712. doc.connect("insert-text", me.on_insert_text)
  713. doc.connect("search-highlight-updated", me.on_search_highlight_updated)
  714. view = me.geditwin.get_active_view()
  715. if not view:
  716. return
  717. if TIMER: TIMER.push('expose')
  718. if id(view) not in me.connected:
  719. me.connected[id(view)] = True
  720. view.connect("scroll-event", me.on_scroll_event)
  721. #view.connect("start-interactive-goto-line", me.test_event)
  722. #view.connect("start-interactive-search", me.test_event)
  723. #view.connect("reset-searched-text", me.test_event)
  724. bg = (0,0,0)
  725. fg = (1,1,1)
  726. try:
  727. style = doc.get_style_scheme().get_style('text')
  728. if style is None: # there is a style scheme, but it does not specify default
  729. bg = (1,1,1)
  730. fg = (0,0,0)
  731. else:
  732. fg,bg = map(str2rgb, style.get_properties('foreground','background'))
  733. except Exception,e:
  734. pass # probably an older version of gedit, no style schemes yet
  735. changeCLR = (1,0,1)
  736. #search_match_style = None
  737. #try:
  738. # search_match_style = doc.get_style_scheme().get_style('search-match')
  739. #except:
  740. # pass
  741. #if search_match_style is None:
  742. # searchFG = fg
  743. # searchBG = (0,1,0)
  744. #else:
  745. # searchFG,searchBG = map(str2rgb, style.get_properties('foreground','background'))
  746. searchFG = fg
  747. searchBG = (0,1,0)
  748. #print doc
  749. try:
  750. win = widget.get_window()
  751. except AttributeError:
  752. win = widget.window
  753. w,h = map(float,win.get_size())
  754. cr = widget.window.cairo_create()
  755. #probj(cr,'rgb')
  756. # Are we drawing everything, or just the scrollbar?
  757. fontfamily = 'sans-serif'
  758. cr.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  759. me.topL,botL = visible_lines_top_bottom(me.geditwin)
  760. # bg
  761. if 1:
  762. #cr.set_source_rgb(46/256.,52/256.,54/256.)
  763. cr.set_source_rgb(*bg)
  764. cr.move_to(0,0)
  765. cr.rectangle(0,0,w,h)
  766. cr.fill()
  767. cr.move_to(0,0)
  768. search_text = doc.get_search_text()[0]
  769. #if not search_text and not me.draw_sections:
  770. # return
  771. if me.surface_textmap is None or not me.draw_scrollbar_only:
  772. if TIMER: TIMER.push('document_lines')
  773. lines = document_lines(doc)
  774. if TIMER: TIMER.pop('document_lines')
  775. if TIMER: TIMER.push('draw textmap')
  776. if id(doc) not in me.doc_attached_data:
  777. docrec = struct()
  778. me.doc_attached_data[id(doc)] = docrec
  779. docrec.original_lines_info = None # we skip the first one, its empty
  780. docrec.search_text = None
  781. for l in lines:
  782. l.changed = False
  783. else:
  784. docrec = me.doc_attached_data[id(doc)]
  785. if docrec.original_lines_info == None:
  786. docrec.original_lines_info = init_original_lines_info(doc,lines)
  787. lines = mark_changed_lines(doc, docrec.original_lines_info, lines)
  788. if BUG_MASK & BUG_DOC_GET_SEARCH_TEXT:
  789. pass
  790. else:
  791. docrec.search_text = search_text
  792. lines = lines_mark_search_matches(lines,docrec)
  793. cr.push_group()
  794. if not lines:
  795. return
  796. # translate everthing in
  797. margin = 3
  798. cr.translate(margin,0)
  799. w -= margin # an d here
  800. if TIMER: TIMER.push('downsample')
  801. max_scale = 3
  802. lines, scale, downsampled = downsample_lines(lines, h, 2, max_scale)
  803. if TIMER: TIMER.pop('downsample')
  804. smooshed = False
  805. if downsampled or scale < max_scale:
  806. smooshed = True
  807. if TIMER: TIMER.push('lines_add_section_len')
  808. lines = lines_add_section_len(lines)
  809. if TIMER: TIMER.pop('lines_add_section_len')
  810. if TIMER: TIMER.push('lines_mark_changed_sections')
  811. lines = lines_mark_changed_sections(lines)
  812. if TIMER: TIMER.pop('lines_mark_changed_sections')
  813. n = len(lines)
  814. lineH = h/n
  815. #print 'doc',doc.get_uri(), lines[0].raw
  816. if BUG_MASK & BUG_CAIRO_MAC_FONT_REF and me.font_face_keepalive is None:
  817. me.save_refs_to_all_font_faces(cr,scale,scale+3,10,12)
  818. cr.set_font_size(scale)
  819. whitespaceW = text_extents('.',cr)[0]
  820. #print pr_text_extents(' ',cr)
  821. #print pr_text_extents('.',cr)
  822. #print pr_text_extents(' .',cr)
  823. # ------------------------ display text silhouette -----------------------
  824. if TIMER: TIMER.push('draw silhouette')
  825. if dark(*fg):
  826. faded_fg = lighten(.5,*fg)
  827. else:
  828. faded_fg = darken(.5,*fg)
  829. rectH = h/float(len(lines))
  830. sofarH= 0
  831. sections = []
  832. for i, line in enumerate(lines):
  833. line.y = sofarH
  834. lastH = sofarH
  835. cr.set_font_size(scale)
  836. if line.raw.strip(): # there is some text here
  837. tw,th = text_extents(line.raw,cr)
  838. if line.search_match:
  839. cr.set_source_rgb(*searchBG)
  840. elif line.changed:
  841. cr.set_source_rgb(*changeCLR)
  842. elif me.draw_sections:
  843. cr.set_source_rgb(*faded_fg)
  844. else:
  845. cr.set_source_rgb(*fg)
  846. if line.section or line.subsection:
  847. #cr.select_font_face(fontfamily, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
  848. cr.set_font_size(scale+3)
  849. if line.justextSTR:
  850. x,y = cr.get_current_point()
  851. cr.move_to(whitespaceW*line.indent,y)
  852. cr.show_text(line.justextSTR)
  853. else:
  854. cr.show_text(line.raw)
  855. else:
  856. #cr.select_font_face(fontfamily, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  857. cr.set_font_size(scale)
  858. cr.show_text(line.raw)
  859. if smooshed:
  860. sofarH += lineH
  861. else:
  862. sofarH += th
  863. else: # empty line
  864. if smooshed:
  865. sofarH += lineH
  866. else:
  867. sofarH += scale-1
  868. if line.section:
  869. sections.append((line, lastH))
  870. cr.move_to(0, sofarH)
  871. if TIMER: TIMER.pop('draw silhouette')
  872. # ------------------- display sections and subsections labels ------------------
  873. if me.draw_sections:
  874. # Subsections
  875. if TIMER: TIMER.push('draw subsections')
  876. if dark(*bg):
  877. bg_rect_C = lighten(.1,*bg)
  878. else:
  879. bg_rect_C = darken(.1,*bg)
  880. if 0: # - blot out the background -
  881. cr.set_source_rgba(bg_rect_C[0],bg_rect_C[1],bg_rect_C[2],.5)
  882. cr.rectangle(0,0,w,h)
  883. cr.fill()
  884. cr.new_path()
  885. cr.set_line_width(1.5)
  886. subsW = 10
  887. subsmargin = 10
  888. cr.set_font_size(10)
  889. for line in lines:
  890. if line.subsection:
  891. if 0:
  892. cr.move_to(subsmargin,line.y)
  893. cr.line_to(subsmargin+subsW,line.y)
  894. #if line.subsectionchanged:
  895. # cr.set_source_rgb(*changeCLR)
  896. #else:
  897. # cr.set_source_rgb(*fg)
  898. if 0:
  899. cr.set_source_rgb(*fg)
  900. cr.arc(subsmargin,line.y+3,2,0,6.28)
  901. cr.stroke()
  902. if 1:
  903. #cr.move_to(20,line.y)
  904. cr.set_source_rgb(*fg)
  905. #cr.show_text(line.subsection)
  906. cr.move_to(whitespaceW*line.indent,line.y)
  907. #cr.move_to(10,line.y)
  908. #fit_text(line.subsection, 10000, 10000, fg, bg, cr)
  909. show_section_label(line.subsection, fg, bg_rect_C, cr)
  910. if TIMER: TIMER.pop('draw subsections')
  911. # Sections
  912. if TIMER: TIMER.push('draw sections')
  913. cr.set_font_size(12)
  914. for line, lastH in sections:
  915. if 0: # section lines
  916. cr.move_to(0, lastH)
  917. cr.set_line_width(1)
  918. cr.set_source_rgb(*fg)
  919. cr.line_to(w,lastH)
  920. cr.stroke()
  921. if 1: # section heading
  922. cr.move_to(0,lastH)
  923. #if line.sectionchanged:
  924. # cr.set_source_rgb(*changeCLR)
  925. #else:
  926. # cr.set_source_rgb(*fg)
  927. cr.set_source_rgb(*fg)
  928. #dispnfo = fit_text(line.section,4*w/5,line.section_len*rectH,fg,bg,cr)
  929. show_section_label(line.section, fg, bg_rect_C, cr)
  930. if 0 and dispnfo: # section hatches
  931. cr.set_line_width(1)
  932. r=dispnfo[0] # first line
  933. cr.move_to(r.x+r.tw+2,r.y-r.th/2+2)
  934. cr.line_to(w,r.y-r.th/2+2)
  935. cr.stroke()
  936. if TIMER: TIMER.pop('draw sections')
  937. # ------------------ translate back for the scroll bar -------------------
  938. cr.translate(-margin,0)
  939. w += margin
  940. # -------------------------- mark lines markers --------------------------
  941. if TIMER: TIMER.push('draw line markers')
  942. for line in lines:
  943. if line.search_match:
  944. clr = searchBG
  945. elif line.changed:
  946. clr = changeCLR
  947. else:
  948. continue # nothing interesting has happened with this line
  949. cr.set_source_rgb(*clr)
  950. cr.rectangle(w-3,line.y-2,2,5)
  951. cr.fill()
  952. if TIMER: TIMER.pop('draw line markers')
  953. if TIMER: TIMER.pop('draw textmap')
  954. # save
  955. me.surface_textmap = cr.pop_group() # everything but the scrollbar
  956. me.lines = lines
  957. # END DISPLAY SILHOUETTE
  958. if TIMER: TIMER.push('surface_textmap')
  959. cr.set_source(me.surface_textmap)
  960. cr.rectangle(0,0,w,h)
  961. cr.fill()
  962. if TIMER: TIMER.pop('surface_textmap')
  963. # ------------------------------- scrollbar -------------------------------
  964. if TIMER: TIMER.push('scrollbar')
  965. if me.topL==0 and botL==doc.get_end_iter().get_line():
  966. pass # everything is visible, don't draw scrollbar
  967. else:
  968. scrollbar(me.lines,me.topL,botL,w,h,bg,cr)
  969. if TIMER: TIMER.pop('scrollbar')
  970. me.draw_scrollbar_only = False
  971. if TIMER: TIMER.pop('expose')
  972. if TIMER: TIMER.print_()
  973. class TextmapWindowHelper:
  974. def __init__(me, plugin, window):
  975. me.window = window
  976. me.plugin = plugin
  977. panel = me.window.get_side_panel()
  978. image = gtk.Image()
  979. image.set_from_stock(gtk.STOCK_DND_MULTIPLE, gtk.ICON_SIZE_BUTTON)
  980. me.textmapview = TextmapView(me.window)
  981. me.ui_id = panel.add_item(me.textmapview, "TextMap", image)
  982. me.panel = panel
  983. def deactivate(me):
  984. me.window = None
  985. me.plugin = None
  986. me.textmapview = None
  987. def update_ui(me):
  988. queue_refresh(me.textmapview)
  989. class TextmapPlugin(gedit.Plugin):
  990. def __init__(me):
  991. gedit.Plugin.__init__(me)
  992. me._instances = {}
  993. def activate(me, window):
  994. me._instances[window] = TextmapWindowHelper(me, window)
  995. def deactivate(me, window):
  996. if window in me._instances:
  997. me._instances[window].deactivate()
  998. def update_ui(me, window):
  999. # Called whenever the window has been updated (active tab
  1000. # changed, etc.)
  1001. #print 'plugin.update_ui'
  1002. if window in me._instances:
  1003. me._instances[window].update_ui()
  1004. #window.do_expose_event()