window.lua

changeset 0
98e4b0c9fcac
child 1
4d7540af8518
equal deleted inserted replaced
-1:000000000000 0:98e4b0c9fcac
1 ------------------
2 -- Window class --
3 ------------------
4
5 -- Window class table
6 window = {}
7
8 -- Widget construction aliases
9 local function entry() return widget{type="entry"} end
10 local function eventbox() return widget{type="eventbox"} end
11 local function hbox() return widget{type="hbox"} end
12 local function label() return widget{type="label"} end
13 local function notebook() return widget{type="notebook"} end
14 local function vbox() return widget{type="vbox"} end
15
16 -- Build and pack window widgets
17 function window.build()
18 -- Create a table for widgets and state variables for a window
19 local w = {
20 win = widget{type="window"},
21 ebox = eventbox(),
22 layout = vbox(),
23 tabs = notebook(),
24 -- Tab bar widgets
25 tbar = {
26 layout = hbox(),
27 ebox = eventbox(),
28 titles = { },
29 },
30 -- Status bar widgets
31 sbar = {
32 layout = hbox(),
33 ebox = eventbox(),
34 -- Left aligned widgets
35 l = {
36 layout = hbox(),
37 ebox = eventbox(),
38 uri = label(),
39 loaded = label(),
40 },
41 -- Fills space between the left and right aligned widgets
42 sep = eventbox(),
43 -- Right aligned widgets
44 r = {
45 layout = hbox(),
46 ebox = eventbox(),
47 buf = label(),
48 ssl = label(),
49 tabi = label(),
50 scroll = label(),
51 },
52 },
53 -- Input bar widgets
54 ibar = {
55 layout = hbox(),
56 ebox = eventbox(),
57 prompt = label(),
58 input = entry(),
59 },
60 }
61
62 -- Assemble window
63 w.ebox:set_child(w.layout)
64 w.win:set_child(w.ebox)
65
66 -- Pack tab bar
67 local t = w.tbar
68 t.ebox:set_child(t.layout, false, false, 0)
69 w.layout:pack_start(t.ebox, false, false, 0)
70
71 -- Pack notebook
72 w.layout:pack_start(w.tabs, true, true, 0)
73
74 -- Pack left-aligned statusbar elements
75 local l = w.sbar.l
76 l.layout:pack_start(l.uri, false, false, 0)
77 l.layout:pack_start(l.loaded, false, false, 0)
78 l.ebox:set_child(l.layout)
79
80 -- Pack right-aligned statusbar elements
81 local r = w.sbar.r
82 r.layout:pack_start(r.buf, false, false, 0)
83 r.layout:pack_start(r.ssl, false, false, 0)
84 r.layout:pack_start(r.tabi, false, false, 0)
85 r.layout:pack_start(r.scroll, false, false, 0)
86 r.ebox:set_child(r.layout)
87
88 -- Pack status bar elements
89 local s = w.sbar
90 s.layout:pack_start(l.ebox, false, false, 0)
91 s.layout:pack_start(s.sep, true, true, 0)
92 s.layout:pack_start(r.ebox, false, false, 0)
93 s.ebox:set_child(s.layout)
94 w.layout:pack_start(s.ebox, false, false, 0)
95
96 -- Pack input bar
97 local i = w.ibar
98 i.layout:pack_start(i.prompt, false, false, 0)
99 i.layout:pack_start(i.input, true, true, 0)
100 i.ebox:set_child(i.layout)
101 w.layout:pack_start(i.ebox, false, false, 0)
102
103 -- Other settings
104 i.input.show_frame = false
105 w.tabs.show_tabs = false
106 l.loaded:hide()
107 l.uri.selectable = true
108 r.ssl:hide()
109
110 return w
111 end
112
113 -- Table of functions to call on window creation. Normally used to add signal
114 -- handlers to the new windows widgets.
115 window.init_funcs = {
116 -- Attach notebook widget signals
117 notebook_signals = function (w)
118 w.tabs:add_signal("page-added", function (nbook, view, idx)
119 w:update_tab_count(idx)
120 w:update_tab_labels()
121 end)
122 w.tabs:add_signal("switch-page", function (nbook, view, idx)
123 w:set_mode()
124 w:update_tab_count(idx)
125 w:update_win_title(view)
126 w:update_uri(view)
127 w:update_progress(view)
128 w:update_tab_labels(idx)
129 w:update_buf()
130 w:update_ssl(view)
131 end)
132 end,
133
134 last_win_check = function (w)
135 w.win:add_signal("destroy", function ()
136 -- call the quit function if this was the last window left
137 if #luakit.windows == 0 then luakit.quit() end
138 end)
139 end,
140
141 key_press_match = function (w)
142 w.win:add_signal("key-press", function (_, mods, key)
143 -- Reset command line completion
144 if w:get_mode() == "command" and key ~= "Tab" and w.compl_start then
145 w:update_uri()
146 w.compl_index = 0
147 end
148
149 if w:hit(mods, key) then
150 return true
151 end
152 end)
153 end,
154
155 apply_window_theme = function (w)
156 local theme = lousy.theme.get()
157 local s, i = w.sbar, w.ibar
158
159 -- Set foregrounds
160 for wi, v in pairs({
161 [s.l.uri] = theme.uri_sbar_fg,
162 [s.l.loaded] = theme.loaded_sbar_fg,
163 [s.r.buf] = theme.buf_sbar_fg,
164 [s.r.tabi] = theme.tabi_sbar_fg,
165 [s.r.scroll] = theme.scroll_sbar_fg,
166 [i.prompt] = theme.prompt_ibar_fg,
167 [i.input] = theme.input_ibar_fg,
168 }) do wi.fg = v end
169
170 -- Set backgrounds
171 for wi, v in pairs({
172 [s.l.ebox] = theme.sbar_bg,
173 [s.r.ebox] = theme.sbar_bg,
174 [s.sep] = theme.sbar_bg,
175 [s.ebox] = theme.sbar_bg,
176 [i.ebox] = theme.ibar_bg,
177 [i.input] = theme.input_ibar_bg,
178 }) do wi.bg = v end
179
180 -- Set fonts
181 for wi, v in pairs({
182 [s.l.uri] = theme.uri_sbar_font,
183 [s.l.loaded] = theme.loaded_sbar_font,
184 [s.r.buf] = theme.buf_sbar_font,
185 [s.r.ssl] = theme.ssl_sbar_font,
186 [s.r.tabi] = theme.tabi_sbar_font,
187 [s.r.scroll] = theme.scroll_sbar_font,
188 [i.prompt] = theme.prompt_ibar_font,
189 [i.input] = theme.input_ibar_font,
190 }) do wi.font = v end
191 end,
192 }
193
194 -- Helper functions which operate on the window widgets or structure.
195 window.methods = {
196 -- Return the widget in the currently active tab
197 get_current = function (w) return w.tabs:atindex(w.tabs:current()) end,
198 -- Check if given widget is the widget in the currently active tab
199 is_current = function (w, wi) return w.tabs:indexof(wi) == w.tabs:current() end,
200
201 get_tab_title = function (w, view)
202 if not view then view = w:get_current() end
203 return view:get_prop("title") or view.uri or "(Untitled)"
204 end,
205
206 new_tab = function (w, uri)
207 local view = webview.new(w, uri)
208 w.tabs:append(view)
209 w:update_tab_count()
210 return view
211 end,
212
213 -- Wrapper around the bind plugin's hit method
214 hit = function (w, mods, key)
215 local caught, newbuf = lousy.bind.hit(w.binds or {}, mods, key, w.buffer, w:is_mode("normal"), w)
216 if w.win then
217 w.buffer = newbuf
218 w:update_buf()
219 end
220 return caught
221 end,
222
223 -- Wrapper around the bind plugin's match_cmd method
224 match_cmd = function (w, buffer)
225 return lousy.bind.match_cmd(binds.commands, buffer, w)
226 end,
227
228 -- enter command or characters into command line
229 enter_cmd = function (w, cmd)
230 local i = w.ibar.input
231 w:set_mode("command")
232 i.text = cmd
233 i:set_position(-1)
234 end,
235
236 -- insert a string into the command line at the current cursor position
237 insert_cmd = function (w, str)
238 if not str then return end
239 local i = w.ibar.input
240 local text = i.text
241 local pos = i:get_position()
242 local left, right = string.sub(text, 1, pos), string.sub(text, pos+1)
243 i.text = left .. str .. right
244 i:set_position(pos + #str + 1)
245 end,
246
247 -- Command line completion of available commands
248 cmd_completion = function (w)
249 local i = w.ibar.input
250 local s = w.sbar.l.uri
251 local cmpl = {}
252
253 -- Get last completion (is reset on key press other than <Tab>)
254 if not w.compl_start or w.compl_index == 0 then
255 w.compl_start = "^" .. string.sub(i.text, 2)
256 w.compl_index = 1
257 end
258
259 -- Get suitable commands
260 for _, b in ipairs(binds.commands) do
261 for _, c in pairs(b.commands) do
262 if c and string.match(c, w.compl_start) then
263 table.insert(cmpl, c)
264 end
265 end
266 end
267
268 table.sort(cmpl)
269
270 if #cmpl > 0 then
271 local text = ""
272 for index, comp in pairs(cmpl) do
273 if index == w.compl_index then
274 i.text = ":" .. comp .. " "
275 i:set_position(-1)
276 end
277 if text ~= "" then
278 text = text .. " | "
279 end
280 text = text .. comp
281 end
282
283 -- cycle through all possible completions
284 if w.compl_index == #cmpl then
285 w.compl_index = 1
286 else
287 w.compl_index = w.compl_index + 1
288 end
289 s.text = lousy.util.escape(text)
290 end
291 end,
292
293 del_word = function (w)
294 local i = w.ibar.input
295 local text = i.text
296 local pos = i:get_position()
297 if text and #text > 1 and pos > 1 then
298 local left, right = string.sub(text, 2, pos), string.sub(text, pos+1)
299 if not string.find(left, "%s") then
300 left = ""
301 elseif string.find(left, "%w+%s*$") then
302 left = string.sub(left, 0, string.find(left, "%w+%s*$") - 1)
303 elseif string.find(left, "%W+%s*$") then
304 left = string.sub(left, 0, string.find(left, "%W+%s*$") - 1)
305 end
306 i.text = string.sub(text, 1, 1) .. left .. right
307 i:set_position(#left + 2)
308 end
309 end,
310
311 del_line = function (w)
312 local i = w.ibar.input
313 if i.text ~= ":" then
314 i.text = ":"
315 i:set_position(-1)
316 end
317 end,
318
319 -- Search history adding
320 srch_hist_add = function (w, srch)
321 if not w.srch_hist then w.srch_hist = {} end
322 -- Check overflow
323 local max_hist = globals.max_srch_history or 100
324 if #w.srch_hist > (max_hist + 5) then
325 while #w.srch_hist > max_hist do
326 table.remove(w.srch_hist, 1)
327 end
328 end
329 table.insert(w.srch_hist, srch)
330 end,
331
332 -- Search history traversing
333 srch_hist_prev = function (w)
334 if not w.srch_hist then w.srch_hist = {} end
335 if not w.srch_hist_cursor then
336 w.srch_hist_cursor = #w.srch_hist + 1
337 w.srch_hist_current = w.ibar.input.text
338 end
339 local c = w.srch_hist_cursor - 1
340 if w.srch_hist[c] then
341 w.srch_hist_cursor = c
342 w.ibar.input.text = w.srch_hist[c]
343 w.ibar.input:set_position(-1)
344 end
345 end,
346
347 srch_hist_next = function (w)
348 if not w.srch_hist then w.srch_hist = {} end
349 local c = (w.srch_hist_cursor or #w.srch_hist) + 1
350 if w.srch_hist[c] then
351 w.srch_hist_cursor = c
352 w.ibar.input.text = w.srch_hist[c]
353 w.ibar.input:set_position(-1)
354 elseif w.srch_hist_current then
355 w.srch_hist_cursor = nil
356 w.ibar.input.text = w.srch_hist_current
357 w.ibar.input:set_position(-1)
358 end
359 end,
360
361 -- Command history adding
362 cmd_hist_add = function (w, cmd)
363 if not w.cmd_hist then w.cmd_hist = {} end
364 -- Make sure history doesn't overflow
365 local max_hist = globals.max_cmd_hist or 100
366 if #w.cmd_hist > (max_hist + 5) then
367 while #w.cmd_hist > max_hist do
368 table.remove(w.cmd_hist, 1)
369 end
370 end
371 table.insert(w.cmd_hist, cmd)
372 end,
373
374 -- Command history traversing
375 cmd_hist_prev = function (w)
376 if not w.cmd_hist then w.cmd_hist = {} end
377 if not w.cmd_hist_cursor then
378 w.cmd_hist_cursor = #w.cmd_hist + 1
379 w.cmd_hist_current = w.ibar.input.text
380 end
381 local c = w.cmd_hist_cursor - 1
382 if w.cmd_hist[c] then
383 w.cmd_hist_cursor = c
384 w.ibar.input.text = w.cmd_hist[c]
385 w.ibar.input:set_position(-1)
386 end
387 end,
388
389 cmd_hist_next = function (w)
390 if not w.cmd_hist then w.cmd_hist = {} end
391 local c = (w.cmd_hist_cursor or #w.cmd_hist) + 1
392 if w.cmd_hist[c] then
393 w.cmd_hist_cursor = c
394 w.ibar.input.text = w.cmd_hist[c]
395 w.ibar.input:set_position(-1)
396 elseif w.cmd_hist_current then
397 w.cmd_hist_cursor = nil
398 w.ibar.input.text = w.cmd_hist_current
399 w.ibar.input:set_position(-1)
400 end
401 end,
402
403 -- GUI content update functions
404 update_tab_count = function (w, i, t)
405 w.sbar.r.tabi.text = string.format("[%d/%d]", i or w.tabs:current(), t or w.tabs:count())
406 end,
407
408 update_win_title = function (w, view)
409 if not view then view = w:get_current() end
410 local title = view:get_prop("title")
411 local uri = view.uri
412 if not title and not uri then
413 w.win.title = "luakit"
414 else
415 w.win.title = (title or "luakit") .. " - " .. (uri or "about:blank")
416 end
417 end,
418
419 update_uri = function (w, view, uri)
420 if not view then view = w:get_current() end
421 w.sbar.l.uri.text = lousy.util.escape((uri or (view and view.uri) or "about:blank"))
422 end,
423
424 update_progress = function (w, view, p)
425 if not view then view = w:get_current() end
426 if not p then p = view:get_prop("progress") end
427 local loaded = w.sbar.l.loaded
428 if not view:loading() or p == 1 then
429 loaded:hide()
430 else
431 loaded:show()
432 loaded.text = string.format("(%d%%)", p * 100)
433 end
434 end,
435
436 update_scroll = function (w, view)
437 if not view then view = w:get_current() end
438 local scroll = w.sbar.r.scroll
439 if view then
440 local val, max = view:get_scroll_vert()
441 if max == 0 then val = "All"
442 elseif val == 0 then val = "Top"
443 elseif val == max then val = "Bot"
444 else val = string.format("%2d%%", (val/max) * 100)
445 end
446 if scroll.text ~= val then scroll.text = val end
447 scroll:show()
448 else
449 scroll:hide()
450 end
451 end,
452
453 update_ssl = function (w, view)
454 if not view then view = w:get_current() end
455 local trusted = view:ssl_trusted()
456 local theme = lousy.theme.get()
457 local ssl = w.sbar.r.ssl
458 if trusted ~= nil and not w.checking_ssl then
459 ssl.fg = theme.notrust_fg
460 ssl.text = "(nocheck)"
461 ssl:show()
462 elseif trusted == true then
463 ssl.fg = theme.trust_fg
464 ssl.text = "(trust)"
465 ssl:show()
466 elseif trusted == false then
467 ssl.fg = theme.notrust_fg
468 ssl.text = "(notrust)"
469 ssl:show()
470 else
471 ssl:hide()
472 end
473 end,
474
475 update_buf = function (w)
476 local buf = w.sbar.r.buf
477 if w.buffer then
478 buf.text = lousy.util.escape(string.format(" %-3s", w.buffer))
479 buf:show()
480 else
481 buf:hide()
482 end
483 end,
484
485 update_binds = function (w, mode)
486 -- Generate the list of active key & buffer binds for this mode
487 w.binds = lousy.util.table.join(binds.mode_binds[mode], binds.mode_binds.all)
488 -- Clear & hide buffer
489 w.buffer = nil
490 w:update_buf()
491 end,
492
493 -- Tab label functions
494 -- TODO: Move these functions into a module (I.e. lousy.widget.tablist)
495 make_tab_label = function (w, pos)
496 local theme = lousy.theme.get()
497 local t = {
498 label = label(),
499 sep = eventbox(),
500 ebox = eventbox(),
501 layout = hbox(),
502 }
503 t.label.font = theme.tab_font
504 t.label:set_width(1)
505 t.layout:pack_start(t.label, true, true, 0)
506 t.layout:pack_start(t.sep, false, false, 0)
507 t.ebox:set_child(t.layout)
508 t.ebox:add_signal("button-release", function (e, m, b)
509 if b == 1 then
510 w.tabs:switch(pos)
511 return true
512 elseif b == 2 then
513 w:close_tab(w.tabs:atindex(pos))
514 return true
515 end
516 end)
517 return t
518 end,
519
520 destroy_tab_label = function (w, t)
521 if not t then t = table.remove(w.tbar.titles) end
522 -- Destroy widgets without their own windows first (I.e. labels)
523 for _, wi in ipairs{ t.label, t.sep, t.ebox, t.layout } do wi:destroy() end
524 end,
525
526 update_tab_labels = function (w, current)
527 local tb = w.tbar
528 local count, current = w.tabs:count(), current or w.tabs:current()
529 tb.ebox:hide()
530
531 -- Leave the tablist hidden if there is only one tab open
532 if count <= 1 then
533 return nil
534 end
535
536 if count ~= #tb.titles then
537 -- Grow the number of labels
538 while count > #tb.titles do
539 local t = w:make_tab_label(#tb.titles + 1)
540 tb.layout:pack_start(t.ebox, true, true, 0)
541 table.insert(tb.titles, t)
542 end
543 -- Prune number of labels
544 while count < #tb.titles do
545 w:destroy_tab_label()
546 end
547 end
548
549 if count ~= 0 then
550 for i = 1, count do
551 local view = w.tabs:atindex(i)
552 local t = tb.titles[i]
553 local title = " " ..i.. " "..w:get_tab_title(view)
554 t.label.text = lousy.util.escape(string.format("%-40s", title))
555 w:apply_tablabel_theme(t, i == current)
556 end
557 end
558 tb.ebox:show()
559 end,
560
561 -- Theme functions
562 apply_tablabel_theme = function (w, t, selected)
563 local theme = lousy.theme.get()
564 selected = (selected and "_selected") or ""
565 t.label.fg = theme[string.format("tab%s_fg", selected)]
566 t.ebox.bg = theme[string.format("tab%s_bg", selected)]
567 end,
568
569 close_win = function (w)
570 -- Close all tabs
571 while w.tabs:count() ~= 0 do
572 w:close_tab()
573 end
574
575 -- Recursively remove widgets from window
576 local children = lousy.util.recursive_remove(w.win)
577 -- Destroy all widgets
578 for i, c in ipairs(lousy.util.table.join(children, {w.win})) do
579 if c.hide then c:hide() end
580 c:destroy()
581 end
582
583 -- Clear window struct
584 w = setmetatable(w, {})
585 for k, _ in pairs(w) do w[k] = nil end
586
587 -- Quit if closed last window
588 if #luakit.windows == 0 then luakit.quit() end
589 end,
590 }
591
592 -- Ordered list of class index functions. Other classes (E.g. webview) are able
593 -- to add their own index functions to this list.
594 window.indexes = {
595 -- Find function in window.methods first
596 function (w, k) return window.methods[k] end
597 }
598
599 -- Create new window
600 function window.new(uris)
601 local w = window.build()
602
603 -- Set window metatable
604 setmetatable(w, {
605 __index = function (_, k)
606 -- Check widget structure first
607 local v = rawget(w, k)
608 if v then return v end
609 -- Call each window index function
610 for _, index in ipairs(window.indexes) do
611 v = index(w, k)
612 if v then return v end
613 end
614 end,
615 })
616
617 -- Call window init functions
618 for _, func in pairs(window.init_funcs) do
619 func(w)
620 end
621
622 -- Populate notebook with tabs
623 for _, uri in ipairs(uris or {}) do
624 w:new_tab(uri)
625 end
626
627 -- Make sure something is loaded
628 if w.tabs:count() == 0 then
629 w:new_tab(globals.homepage)
630 end
631
632 -- Set initial mode
633 w:set_mode()
634
635 return w
636 end
637
638 -- vim: et:sw=4:ts=8:sts=4:tw=80

mercurial