demo-text-hypertext.lua 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. return function(parent, dir)
  2. local lgi = require 'lgi'
  3. local Gtk = lgi.Gtk
  4. local Gdk = lgi.Gdk
  5. local Pango = lgi.Pango
  6. local window = Gtk.Window {
  7. title = "Hypertext",
  8. default_width = 450,
  9. default_height = 450,
  10. Gtk.ScrolledWindow {
  11. Gtk.TextView {
  12. id = 'textview',
  13. wrap_mode = 'WORD',
  14. },
  15. },
  16. }
  17. -- Sample hypertext content.
  18. local content = {
  19. intro = [[
  20. Some text to show that simple [hypertext hypertext] can easily be realized with [tags tags].
  21. ]],
  22. hypertext = [[
  23. *hypertext:*
  24. machine-readable text that is not sequential but is organized so that related items of information are connected.
  25. [intro Go back]
  26. ]],
  27. tags = [[
  28. A tag is an attribute that can be applied to some range of text. For example, a tag might be called "bold" and make the text inside the tag bold. However, the tag concept is more general than that; tags don't have to affect appearance. They can instead affect the behavior of mouse and key presses, "lock" a range of text so the user can't edit it, or countless other things.
  29. [intro Go back]
  30. ]],
  31. }
  32. local active_links
  33. local handlers = {
  34. ['^([^%[%*]+)'] =
  35. -- Plaintext.
  36. function(text)
  37. return text
  38. end,
  39. ['^%[(%w+) ([^%]]+)%]'] =
  40. -- Link.
  41. function(link, text)
  42. local tag = Gtk.TextTag {
  43. foreground = 'blue',
  44. underline = Pango.Underline.SINGLE,
  45. }
  46. active_links[tag] = link
  47. return text, tag
  48. end,
  49. ['^%*([^%*]+)%*'] =
  50. -- Bold text.
  51. function(text)
  52. return text, Gtk.TextTag {
  53. weight = Pango.Weight.BOLD,
  54. }
  55. end,
  56. }
  57. local function fill_page(page)
  58. local buffer = window.child.textview.buffer
  59. buffer.text = ''
  60. active_links = {}
  61. if not page then return end
  62. local iter = buffer:get_iter_at_offset(0)
  63. local pos = 1
  64. repeat
  65. for pattern, handler in pairs(handlers) do
  66. local start, stop, m1, m2 = page:find(pattern, pos)
  67. if start then
  68. -- Extract next part of the text.
  69. local text, tag = handler(m1, m2)
  70. -- Add text into the buffer.
  71. start = iter:get_offset()
  72. buffer:insert(iter, text, -1)
  73. -- Apply tag, if available.
  74. if tag then
  75. buffer.tag_table:add(tag)
  76. buffer:apply_tag(tag, buffer:get_iter_at_offset(start), iter)
  77. end
  78. -- Prepare for the next iteration.
  79. pos = stop + 1
  80. break
  81. end
  82. end
  83. until pos >= #page
  84. end
  85. local cursors = {
  86. [true] = Gdk.Cursor.new('HAND2'),
  87. [false] = Gdk.Cursor.new('XTERM'),
  88. }
  89. local hovering = false
  90. local function set_cursor_if_appropriate(view, x, y)
  91. local tags = view:get_iter_at_location(x, y):get_tags()
  92. local should_hover = false
  93. for i = 1, #tags do
  94. if active_links[tags[i]] then
  95. should_hover = true
  96. break
  97. end
  98. end
  99. if hovering ~= should_hover then
  100. hovering = should_hover
  101. view:get_window('TEXT'):set_cursor(cursors[hovering])
  102. end
  103. end
  104. local textview = window.child.textview
  105. function textview:on_motion_notify_event(event)
  106. set_cursor_if_appropriate(
  107. self, self:window_to_buffer_coords('WIDGET', event.x, event.y))
  108. return false
  109. end
  110. function textview:on_visibility_notify_event(event)
  111. local x, y = self.window:get_pointer()
  112. if x and y then
  113. set_cursor_if_appropriate(
  114. self, self:window_to_buffer_coords('WIDGET', x, y))
  115. end
  116. return false
  117. end
  118. local function follow_if_link(view, iter)
  119. for _, tag in ipairs(iter:get_tags()) do
  120. if active_links[tag] then
  121. fill_page(content[active_links[tag]])
  122. break
  123. end
  124. end
  125. end
  126. function textview:on_event_after(event)
  127. if event.type == 'BUTTON_RELEASE' and event.button.button == 1 then
  128. -- Don't follow link if anything is selected.
  129. local start, stop = self.buffer:get_selection_bounds()
  130. if not start or not stop or start:get_offset() == stop:get_offset() then
  131. follow_if_link(self, self:get_iter_at_location(
  132. self:window_to_buffer_coords(
  133. 'WIDGET', event.button.x, event.button.y)))
  134. end
  135. end
  136. end
  137. function textview:on_key_press_event(event)
  138. if event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter then
  139. follow_if_link(
  140. self, self.buffer:get_iter_at_mark(self.buffer:get_insert()))
  141. end
  142. return false
  143. end
  144. -- Initially fill the intro page.
  145. fill_page(content.intro)
  146. window:show_all()
  147. return window
  148. end,
  149. "Text Widget/Hypertext",
  150. table.concat {
  151. [[Usually, tags modify the appearance of text in the view, ]],
  152. [[e.g. making it bold or colored or underlined. But tags are not ]],
  153. [[restricted to appearance. They can also affect the behavior of ]],
  154. [[mouse and key presses, as this demo shows.]],
  155. }