• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Ruby GTK3移行後のメインリポジトリ


Commit MetaInfo

Revisão0bfbaaa049fec0604a0bf9d7253a86cbc1472157 (tree)
Hora2014-06-04 23:42:49
AutorShyouzou Sugitani <shy@user...>
CommiterShyouzou Sugitani

Mensagem de Log

add balloon.rb

Mudança Sumário

Diff

--- /dev/null
+++ b/lib/ninix/balloon.rb
@@ -0,0 +1,2007 @@
1+# -*- coding: utf-8 -*-
2+#
3+# Copyright (C) 2001, 2002 by Tamito KAJIYAMA
4+# Copyright (C) 2002, 2003 by MATSUMURA Namihiko <nie@counterghost.net>
5+# Copyright (C) 2002-2014 by Shyouzou Sugitani <shy@users.sourceforge.jp>
6+# Copyright (C) 2003 by Shun-ichi TAHARA <jado@flowernet.gr.jp>
7+#
8+# This program is free software; you can redistribute it and/or modify it
9+# under the terms of the GNU General Public License (version 2) as
10+# published by the Free Software Foundation. It is distributed in the
11+# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
12+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13+# PURPOSE. See the GNU General Public License for more details.
14+#
15+
16+require "gtk3"
17+
18+require "ninix/pix"
19+
20+
21+module Balloon
22+
23+ class Balloon
24+
25+ def initialize
26+ @parent = nil
27+ @synchronized = []
28+ @user_interaction = false
29+ @window = []
30+ # create communicatebox
31+ @communicatebox = CommunicateBox.new()
32+ @communicatebox.set_responsible(self)
33+ # create teachbox
34+ @teachbox = TeachBox.new()
35+ @teachbox.set_responsible(self)
36+ # create inputbox
37+ @inputbox = InputBox.new()
38+ @inputbox.set_responsible(self)
39+ # create passwordinputbox
40+ @passwordinputbox = PasswordInputBox.new()
41+ @passwordinputbox.set_responsible(self)
42+ end
43+
44+ def set_responsible(parent)
45+ @parent = parent
46+ end
47+
48+ def handle_request(event_type, event, *arglist, **argdict)
49+ #assert event_type in ['GET', 'NOTIFY']
50+ handlers = {
51+ 'reset_user_interaction' => 'reset_user_interaction',
52+ }
53+# handler = handlers.get(event)
54+# if handler == nil
55+ if !handlers.include?(event)
56+ result = @parent.handle_request(
57+ event_type, event, *arglist, **argdict)
58+ else
59+ result = handler(*arglist, **argdict)
60+ end
61+ if event_type == 'GET'
62+ return result
63+ end
64+ end
65+
66+ def reset_user_interaction
67+ @user_interaction = false
68+ end
69+
70+ def get_text_count(side)
71+ if @window.length > side
72+ return @window[side].get_text_count()
73+ else
74+ return 0
75+ end
76+ end
77+
78+ def get_window(side)
79+ if @window.length > side
80+ return @window[side].window ## FIXME
81+ else
82+ return nil
83+ end
84+ end
85+
86+ def reset_text_count(side)
87+ if @window.length > side
88+ @window[side].reset_text_count()
89+ end
90+ end
91+
92+ def reset_balloon
93+ for balloon_window in @window
94+ balloon_window.reset_balloon()
95+ end
96+ end
97+
98+ def create_gtk_window(title)
99+ window = Pix::TransparentWindow.new()
100+ window.set_title(title)
101+ window.set_skip_pager_hint(false)
102+ window.set_skip_taskbar_hint(true)
103+ window.signal_connect('delete_event') do |w, e|
104+ delete(w, e)
105+ end
106+ window.realize()
107+ return window
108+ end
109+
110+ def identify_window(win)
111+ for balloon_window in @window
112+ if win == balloon_window.window.window
113+ return true
114+ end
115+ end
116+ return false
117+ end
118+
119+ def delete(window, event)
120+ return true
121+ end
122+
123+ def finalize
124+ for balloon_window in @window
125+ balloon_window.destroy()
126+ end
127+ @window = []
128+ @communicatebox.destroy()
129+ @teachbox.destroy()
130+ @inputbox.destroy()
131+ @passwordinputbox.destroy()
132+ end
133+
134+ def new_(desc, balloon)
135+ @desc = desc
136+ @directory = balloon['balloon_dir'][0]
137+ balloon0 = {}
138+ balloon1 = {}
139+ communicate0 = nil
140+ communicate1 = nil
141+ communicate2 = nil
142+ communicate3 = nil
143+ for key in balloon.keys
144+ value = balloon[key]
145+# for key, value in balloon.items()
146+ if ['arrow0', 'arrow1'].include?(key)
147+ balloon0[key] = value
148+ balloon1[key] = value
149+ elsif key == 'sstp'
150+ balloon0[key] = value # sstp marker
151+ elsif key.start_with?('s')
152+ balloon0[key] = value # Sakura
153+ elsif key.start_with?('k')
154+ balloon1[key] = value # Unyuu
155+ elsif key == 'c0'
156+ communicate0 = value # send box
157+ elsif key == 'c1'
158+ communicate1 = value # communicate box
159+ elsif key == 'c2'
160+ communicate2 = value # teach box
161+ elsif key == 'c3'
162+ communicate3 = value # input box
163+ end
164+ end
165+ @balloon0 = balloon0
166+ @balloon1 = balloon1
167+ # create balloon windows
168+ for balloon_window in @window
169+ balloon_window.destroy()
170+ end
171+ @window = []
172+ add_window(0)
173+ add_window(1)
174+ # configure communicatebox
175+ @communicatebox.new_(desc, communicate1)
176+ # configure teachbox
177+ @teachbox.new_(desc, communicate2)
178+ # configure inputbox
179+ @inputbox.new_(desc, communicate3)
180+ # configure passwordinputbox
181+ @passwordinputbox.new_(desc, communicate3)
182+ end
183+
184+ def add_window(side)
185+# assert @window.length == side
186+ if side == 0
187+ name = 'balloon.sakura'
188+ id_format = 's'
189+ balloon = @balloon0
190+ elsif side == 1
191+ name = 'balloon.kero'
192+ id_format = 'k'
193+ balloon = @balloon1
194+ else
195+ name = 'balloon.char' + side.to_s
196+ id_format = 'k'
197+ balloon = @balloon1
198+ end
199+ gtk_window = create_gtk_window(name)
200+ balloon_window = BalloonWindow.new(
201+ gtk_window, side, @desc, balloon,
202+ id_format)
203+ balloon_window.set_responsible(self)
204+ @window << balloon_window
205+ end
206+
207+ def reset_fonts
208+ for window in @window
209+ window.reset_fonts()
210+ end
211+ end
212+
213+ def get_balloon_directory
214+ return @directory
215+ end
216+
217+ def get_balloon_size(side)
218+ if @window.length > side
219+ return @window[side].get_balloon_size()
220+ else
221+ return [0, 0]
222+ end
223+ end
224+
225+ def get_balloon_windowposition(side)
226+ if @window.length > side
227+ return @window[side].get_balloon_windowposition()
228+ else
229+ return [0, 0]
230+ end
231+ end
232+
233+ def set_balloon_default
234+ default_id = @parent.handle_request('GET', 'get_balloon_default_id')
235+ begin
236+ default_id = default_id.to_i
237+ rescue # except:
238+ default_id = 0
239+ end
240+ for side in range(@window.length)
241+ @window[side].set_balloon(default_id)
242+ end
243+ end
244+
245+ def set_balloon(side, num)
246+ if @window.length > side
247+ @window[side].set_balloon(num)
248+ end
249+ end
250+
251+ def set_position(side, base_x, base_y)
252+ if @window.length > side
253+ @window[side].set_position(base_x, base_y)
254+ end
255+ end
256+
257+ def get_position(side)
258+ if @window.length > side
259+ return @window[side].get_position()
260+ else
261+ return [0, 0]
262+ end
263+ end
264+
265+ def set_autoscroll(flag)
266+ for side in range(@window.length)
267+ @window[side].set_autoscroll(flag)
268+ end
269+ end
270+
271+ def is_shown(side)
272+ if @window.length <= side
273+ return 0
274+ else
275+ return @window[side].is_shown()
276+ end
277+ end
278+
279+ def show(side)
280+ if @window.length > side
281+ @window[side].show()
282+ end
283+ end
284+
285+ def hide_all
286+ for side in range(@window.length)
287+ @window[side].hide()
288+ end
289+ end
290+
291+ def hide(side)
292+ if @window.length > side
293+ @window[side].hide()
294+ end
295+ end
296+
297+ def raise_all
298+ for side in range(@window.length)
299+ @window[side].raise_()
300+ end
301+ end
302+
303+ def raise_(side)
304+ if @window.length > side
305+ @window[side].raise_()
306+ end
307+ end
308+
309+ def lower_all
310+ for side in range(@window.length)
311+ @window[side].lower()
312+ end
313+ end
314+
315+ def lower(side)
316+ if @window.length > side
317+ @window[side].lower()
318+ end
319+ end
320+
321+ def synchronize(list)
322+ @synchronized = list
323+ end
324+
325+ def clear_text_all
326+ for side in range(@window.length)
327+ clear_text(side)
328+ end
329+ end
330+
331+ def clear_text(side)
332+ if not @synchronized.empty?
333+ for side in @synchronized
334+ if @window.length > side
335+ @window[side].clear_text()
336+ end
337+ end
338+ else
339+ if @window.length > side
340+ @window[side].clear_text()
341+ end
342+ end
343+ end
344+
345+ def append_text(side, text)
346+ if not @synchronized.empty?
347+ for side in @synchronized
348+ if @window.length > side
349+ @window[side].append_text(text)
350+ end
351+ end
352+ else
353+ if @window.length > side
354+ @window[side].append_text(text)
355+ end
356+ end
357+ end
358+
359+ def append_sstp_marker(side)
360+ if @window.length > side
361+ @window[side].append_sstp_marker()
362+ end
363+ end
364+
365+ def append_link_in(side, label)
366+ if not @synchronized.empty?
367+ for side in @synchronized
368+ if @window.length > side
369+ @window[side].append_link_in(label)
370+ end
371+ end
372+ else
373+ if @window.length > side
374+ @window[side].append_link_in(label)
375+ end
376+ end
377+ end
378+
379+ def append_link_out(side, label, value)
380+ if not @synchronized.empty?
381+ for side in @synchronized
382+ if @window.length > side
383+ @window[side].append_link_out(label, value)
384+ end
385+ end
386+ else
387+ if @window.length > side
388+ @window[side].append_link_out(label, value)
389+ end
390+ end
391+ end
392+
393+ def append_link(side, label, value, newline_required=0)
394+ if not @synchronized.empty?
395+ for side in @synchronized
396+ if @window.length > side
397+ @window[side].append_link_in(label)
398+ @window[side].append_text(value)
399+ @window[side].append_link_out(label, value)
400+ if newline_required
401+ @window[side].set_newline()
402+ end
403+ end
404+ end
405+ else
406+ if @window.length > side
407+ @window[side].append_link_in(label)
408+ @window[side].append_text(value)
409+ @window[side].append_link_out(label, value)
410+ if newline_required
411+ @window[side].set_newline()
412+ end
413+ end
414+ end
415+ end
416+
417+ def append_meta(side, tag)
418+ if not @synchronized.empty?
419+ for side in @synchronized
420+ if @window.length > side
421+ @window[side].append_meta(tag)
422+ end
423+ end
424+ else
425+ if @window.length > side
426+ @window[side].append_meta(tag)
427+ end
428+ end
429+ end
430+
431+ def append_image(side, path, x, y)
432+ if @window.length > side
433+ @window[side].append_image(path, x, y)
434+ end
435+ end
436+
437+ def show_sstp_message(message, sender)
438+ @window[0].show_sstp_message(message, sender)
439+ end
440+
441+ def hide_sstp_message
442+ @window[0].hide_sstp_message()
443+ end
444+
445+ def open_communicatebox
446+ if not @user_interaction
447+ @user_interaction = true
448+ @communicatebox.show()
449+ end
450+ end
451+
452+ def open_teachbox
453+ if not @user_interaction
454+ @user_interaction = true
455+ @parent.handle_request('NOTIFY', 'notify_event', 'OnTeachStart')
456+ @teachbox.show()
457+ end
458+ end
459+
460+ def open_inputbox(symbol, limittime=-1, default=nil)
461+ if not @user_interaction
462+ @user_interaction = true
463+ @inputbox.set_symbol(symbol)
464+ @inputbox.set_limittime(limittime)
465+ @inputbox.show(default)
466+ end
467+ end
468+
469+ def open_passwordinputbox(symbol, limittime=-1, default=nil)
470+ if not @user_interaction
471+ @user_interaction = true
472+ @passwordinputbox.set_symbol(symbol)
473+ @passwordinputbox.set_limittime(limittime)
474+ @passwordinputbox.show(default)
475+ end
476+ end
477+
478+ def close_inputbox(symbol)
479+ if not @user_interaction
480+ return
481+ end
482+ @inputbox.close(symbol)
483+ @passwordinputbox.close(symbol)
484+ end
485+ end
486+
487+
488+ class BalloonWindow
489+
490+ def initialize(window, side, desc, balloon, id_format)
491+ @window = window
492+ @side = side
493+ @parent = nil
494+ @desc = desc
495+ @balloon = balloon
496+ @balloon_id = nil
497+ @id_format = id_format
498+ @num = 0
499+ @__shown = false
500+ @sstp_marker = []
501+ @sstp_region = nil
502+ @sstp_message = nil
503+ @images = []
504+ @width = 0
505+ @height = 0
506+ @__font_name = ''
507+ @text_count = 0
508+ @balloon_surface = nil
509+ @autoscroll = true
510+ @dragged = false
511+ @x_root = nil
512+ @y_root = nil
513+ @x_fractions = 0
514+ @y_fractions = 0
515+ @darea = @window.darea # get_child()
516+ @darea.set_events(Gdk::Event::EXPOSURE_MASK|
517+ Gdk::Event::BUTTON_PRESS_MASK|
518+ Gdk::Event::BUTTON_RELEASE_MASK|
519+ Gdk::Event::POINTER_MOTION_MASK|
520+ Gdk::Event::POINTER_MOTION_HINT_MASK|
521+ Gdk::Event::SCROLL_MASK)
522+ @darea.signal_connect('draw') do |w, e|
523+ redraw(w, e)
524+ end
525+ @darea.signal_connect('button_press_event') do |w, e|
526+ button_press(w, e)
527+ end
528+ @darea.signal_connect('button_release_event') do |w, e|
529+ button_release(w, e)
530+ end
531+ @darea.signal_connect('motion_notify_event') do |w, e|
532+ motion_notify(w, e)
533+ end
534+ @darea.signal_connect('scroll_event') do |w, e|
535+ scroll(w, e)
536+ end
537+ @layout = Pango::Layout.new(@darea.pango_context)
538+ @sstp_layout = Pango::Layout.new(@darea.pango_context())
539+ mask_r = desc.get('maskcolor.r', 128).to_i
540+ mask_g = desc.get('maskcolor.g', 128).to_i
541+ mask_b = desc.get('maskcolor.b', 128).to_i
542+ @cursor_color = [mask_r / 255.0, mask_g / 255.0, mask_b / 255.0]
543+ text_r = desc.get(['font.color.r', 'fontcolor.r'], 0).to_i
544+ text_g = desc.get(['font.color.g', 'fontcolor.g'], 0).to_i
545+ text_b = desc.get(['font.color.b', 'fontcolor.b'], 0).to_i
546+ @text_normal_color = [text_r / 255.0, text_g / 255.0, text_b / 255.0]
547+ if desc.get('maskmethod').to_i == 1
548+ text_r = 255 - text_r
549+ text_g = 255 - text_g
550+ text_b = 255 - text_b
551+ end
552+ @text_active_color = [text_r / 255.0, text_g / 255.0, text_b / 255.0]
553+ sstp_r = desc.get('sstpmessage.font.color.r', text_r).to_i
554+ sstp_g = desc.get('sstpmessage.font.color.g', text_g).to_i
555+ sstp_b = desc.get('sstpmessage.font.color.b', text_b).to_i
556+ @sstp_message_color = [sstp_r / 255.0, sstp_g / 255.0, sstp_b / 255.0]
557+ # initialize
558+ @__direction = [side, 1].min ## kluge: multi character
559+ @position = [0, 0]
560+ reset_fonts()
561+ clear_text()
562+ end
563+
564+ def set_responsible(parent)
565+ @parent = parent
566+ end
567+
568+# @property
569+ def scale
570+ scaling = @parent.handle_request('GET', 'get_preference', 'balloon_scaling')
571+ scale = @parent.handle_request('GET', 'get_preference', 'surface_scale')
572+ if scaling
573+ return scale
574+ else
575+ return 100 # [%]
576+ end
577+ end
578+
579+# @property
580+ def direction
581+ return @__direction
582+ end
583+
584+# @direction.setter
585+ def direction(direction)
586+ if @__direction != direction
587+ @__direction = direction # 0: left, 1: right
588+ reset_balloon()
589+ end
590+ end
591+
592+ def get_balloon_windowposition
593+ x = __get_with_scaling('windowposition.x', 0).to_i
594+ y = __get_with_scaling('windowposition.y', 0).to_i
595+ return x, y
596+ end
597+
598+ def get_image_surface(balloon_id)
599+ if not @balloon.include?(balloon_id)
600+ return nil
601+ end
602+ begin
603+ path, config = @balloon[balloon_id]
604+ use_pna = @parent.handle_request('GET', 'get_preference', 'use_pna')
605+ surface = Pix.create_surface_from_file(path, use_pna=use_pna)
606+ rescue # except:
607+ return nil
608+ end
609+ return surface
610+ end
611+
612+ def reset_fonts
613+ if @parent != nil
614+ font_name = @parent.handle_request('GET', 'get_preference', 'balloon_fonts')
615+ else
616+ font_name = nil
617+ end
618+ if @__font_name == font_name
619+ return
620+ end
621+ @font_desc = Pango::FontDescription.new(font_name)
622+ pango_size = @font_desc.size
623+ if pango_size == 0
624+ default_size = 12 # for Windows environment
625+ size = @desc.get(['font.height', 'font.size'], default_size).to_i
626+ pango_size = size * 3 / 4 # convert from Windows to GTK+
627+ pango_size *= Pango::SCALE
628+ end
629+ @font_desc.set_size(pango_size)
630+ @__font_name = font_name
631+ @layout.set_font_description(@font_desc)
632+ @layout.set_wrap(Pango::WRAP_CHAR) # XXX
633+ # font for sstp message
634+ if @side == 0
635+ @sstp_font_desc = Pango::FontDescription.new(font_name)
636+ pango_size = @sstp_font_desc.size
637+ if pango_size == 0
638+ default_size = 10 # for Windows environment
639+ size = @desc.get('sstpmessage.font.height', default_size).to_i
640+ pango_size = size * 3 / 4 # convert from Windows to GTK+
641+ pango_size *= Pango::SCALE
642+ end
643+ @sstp_font_desc.set_size(pango_size)
644+ @sstp_layout.set_font_description(@sstp_font_desc)
645+ @sstp_layout.set_wrap(Pango::WRAP_CHAR)
646+ end
647+ if @balloon_id != nil
648+ reset_message_regions()
649+ if @__shown
650+ @darea.queue_draw()
651+ end
652+ end
653+ end
654+
655+ def reset_sstp_marker
656+ if @side == 0
657+# assert @balloon_surface != nil
658+ w = @balloon_surface.width
659+ h = @balloon_surface.height
660+ # sstp marker position
661+ @sstp = []
662+ x = config_adjust('sstpmarker.x', w, 30)
663+ y = config_adjust('sstpmarker.y', h, -20)
664+ @sstp << [x, y] # sstp marker
665+ x = config_adjust('sstpmessage.x', w, 50)
666+ y = config_adjust('sstpmessage.y', h, -20)
667+ @sstp << [x, y] # sstp message
668+ end
669+ # sstp marker surface (not only for @side == 0)
670+ @sstp_surface = get_image_surface('sstp')
671+ end
672+
673+ def reset_arrow
674+ # arrow positions
675+ @arrow = []
676+# assert @balloon_surface != nil
677+ w = @balloon_surface.width
678+ h = @balloon_surface.height
679+ x = config_adjust('arrow0.x', w, -10)
680+ y = config_adjust('arrow0.y', h, 10)
681+ @arrow << [x, y]
682+ x = config_adjust('arrow1.x', w, -10)
683+ y = config_adjust('arrow1.y', h, -20)
684+ @arrow << [x, y]
685+ # arrow surfaces and sizes
686+ @arrow0_surface = get_image_surface('arrow0')
687+ @arrow1_surface = get_image_surface('arrow1')
688+ end
689+
690+ def reset_message_regions
691+ w, h = @layout.pixel_size
692+ @font_height = h
693+ @line_space = 1
694+ @layout.set_spacing(@line_space)
695+ # font metrics
696+ origin_x = __get('origin.x',
697+ __get('zeropoint.x',
698+ __get('validrect.left', 14).to_i).to_i).to_i
699+ origin_y = __get('origin.y',
700+ __get('zeropoint.y',
701+ __get('validrect.top', 14).to_i).to_i).to_i
702+ wpx = __get('wordwrappoint.x',
703+ __get('validrect.right', -14).to_i).to_i
704+ if wpx > 0
705+ line_width = wpx - origin_x
706+ elsif wpx < 0
707+ line_width = @width - origin_x + wpx
708+ else
709+ line_width = @width - origin_x * 2
710+ end
711+ wpy = __get('validrect.bottom', -14).to_i
712+ if wpy > 0
713+ text_height = [wpy, @height].min - origin_y
714+ elsif wpy < 0
715+ text_height = @height - origin_y + wpy
716+ else
717+ text_height = @height - origin_y * 2
718+ end
719+ line_height = @font_height + @line_space
720+ @lines = (text_height / line_height).to_i
721+ @line_regions = []
722+ y = origin_y
723+ for _ in 0..@lines
724+ @line_regions << [origin_x, y, line_width, line_height]
725+ y = y + line_height
726+ end
727+ @line_width = line_width
728+ # sstp message region
729+ if @side == 0
730+ w, h = @sstp_layout.pixel_size
731+ x, y = @sstp[1]
732+ w = line_width + origin_x - x
733+ @sstp_region = [x, y, w, h]
734+ end
735+ end
736+
737+ def update_line_regions(offset, new_y)
738+ origin_y = __get('origin.y',
739+ __get('zeropoint.y',
740+ __get('validrect.top', 14).to_i).to_i).to_i
741+ wpy = __get('validrect.bottom', -14).to_i
742+ if wpy > 0
743+ text_height = [wpy, @height].min - origin_y
744+ elsif wpy < 0
745+ text_height = @height - origin_y + wpy
746+ else
747+ text_height = @height - origin_y * 2
748+ end
749+ line_height = @font_height + @line_space
750+ origin_x, y, line_width, line_height = @line_regions[offset]
751+ @lines = offset + ((text_height - new_y) / line_height).to_i
752+ y = new_y
753+ for i in offset..(@line_regions.length - 1)
754+ @line_regions[i] = [origin_x, y, line_width, line_height]
755+ y += line_height
756+ end
757+ for i in @line_regions.length..@lines
758+ @line_regions << [origin_x, y, line_width, line_height]
759+ y += line_height
760+ end
761+ end
762+
763+ def get_balloon_size(scaling=true)
764+ w = @width
765+ h = @height
766+# scale = @scale
767+ if scaling
768+ w = (w * scale / 100.0).to_i
769+ h = (h * scale / 100.0).to_i
770+ end
771+ return w, h
772+ end
773+
774+ def reset_balloon
775+ set_balloon(@num)
776+ end
777+
778+ def set_balloon(num)
779+ @num = num
780+ balloon_id = @id_format + (num * 2 + @__direction).to_i.to_s
781+ @balloon_surface = get_image_surface(balloon_id)
782+ if @balloon_surface == nil
783+ balloon_id = @id_format + (0 + @__direction).to_i.to_s
784+ @balloon_surface = get_image_surface(balloon_id)
785+ end
786+# assert @balloon_surface != nil
787+ @balloon_id = balloon_id
788+ # change surface and window position
789+ x, y = @position
790+ @width = @balloon_surface.width
791+ @height = @balloon_surface.height
792+# scale = @scale
793+ w = (@width * scale / 100.0).to_i
794+ h = (@height * scale / 100.0).to_i
795+ @window.update_size(w, h)
796+ reset_arrow()
797+ reset_sstp_marker()
798+ reset_message_regions()
799+ @parent.handle_request('NOTIFY', 'position_balloons')
800+ if @__shown
801+ @darea.queue_draw()
802+ end
803+ end
804+
805+ def set_autoscroll(flag)
806+ @autoscroll = bool(flag)
807+ end
808+
809+ def config_adjust(name, base, default_value)
810+ path, config = @balloon[@balloon_id]
811+ value = config.get(name).to_i
812+ if value == nil
813+ value = @desc.get(name).to_i
814+ end
815+ if value == nil
816+ value = default_value
817+ end
818+ if value < 0
819+ value = base + value
820+ end
821+ return value.to_i
822+ end
823+
824+ def __get_with_type(name, conv, default_value)
825+ path, config = @balloon[@balloon_id]
826+ value = config.get_with_type(name, conv)
827+ if value == nil
828+ value = @desc.get_with_type(name, conv)
829+ if value == nil
830+ value = default_value
831+ end
832+ end
833+ return conv(value)
834+ end
835+
836+ def __get(name, default_value)
837+ path, config = @balloon[@balloon_id]
838+ value = config.get(name)
839+ if value == nil
840+ value = @desc.get(name)
841+ if value == nil
842+ value = default_value
843+ end
844+ end
845+ return value
846+ end
847+
848+# def __get_with_scaling(name, conv, default_value)
849+# path, config = @balloon[@balloon_id]
850+# value = config.get_with_type(name, conv)
851+# if value == nil
852+# value = @desc.get_with_type(name, conv)
853+# if value == nil
854+# value = default_value
855+# end
856+# end
857+# return conv(value * scale / 100)
858+# end
859+
860+ def __get_with_scaling(name, default_value)
861+ path, config = @balloon[@balloon_id]
862+ value = config.get(name)
863+ if value == nil
864+ value = @desc.get(name)
865+ if value == nil
866+ value = default_value
867+ end
868+ end
869+ return (value.to_f * scale / 100)
870+ end
871+
872+ def __move
873+ x, y = get_position()
874+ @window.move(x, y)
875+ end
876+
877+ def set_position(base_x, base_y)
878+ if @balloon_id == nil ## FIXME
879+ return
880+ end
881+ px, py = get_balloon_windowposition()
882+ w, h = get_balloon_size()
883+ if @__direction == 0
884+ x = base_x + px - w
885+ else
886+ x = base_x + px
887+ end
888+ y = base_y + py
889+ left, top, scrn_w, scrn_h = Pix.get_workarea()
890+ if y + h > scrn_h # XXX
891+ y = scrn_h - h
892+ end
893+ if y < top # XXX
894+ y = top
895+ end
896+ @position = [x, y]
897+ __move()
898+ end
899+
900+ def get_position
901+ return @position
902+ end
903+
904+ def destroy(finalize=0)
905+ @window.destroy()
906+ end
907+
908+ def is_shown
909+ if @__shown
910+ return 1
911+ else
912+ return 0
913+ end
914+ end
915+
916+ def show
917+ if @parent.handle_request('GET', 'lock_repaint')
918+ return
919+ end
920+ if @__shown
921+ return
922+ end
923+ @__shown = true
924+ # make sure window is in its position (call before showing the window)
925+ __move()
926+ @window.show()
927+ # make sure window is in its position (call after showing the window)
928+ __move()
929+ raise_()
930+ end
931+
932+ def hide
933+ if not @__shown
934+ return
935+ end
936+ @window.hide()
937+ @__shown = false
938+ @images = []
939+ end
940+
941+ def raise_
942+ if @__shown
943+ @window.window.raise
944+ end
945+ end
946+
947+ def lower
948+ if @__shown
949+ @window.get_window().lower()
950+ end
951+ end
952+
953+ def show_sstp_message(message, sender)
954+ if @sstp_region == nil
955+ show()
956+ end
957+ @sstp_message = message.to_s + " (" + sender.to_s + ")"
958+ x, y, w, h = @sstp_region
959+# @sstp_layout.set_text(@sstp_message, -1)
960+ @sstp_layout.set_text(@sstp_message)
961+ message_width, message_height = @sstp_layout.pixel_size
962+ if message_width > w
963+ @sstp_message = '... ({0})'.format(sender)
964+ i = 0
965+ while 1
966+ i += 1
967+ s = '{0}... ({1})'.format(message[0, i], sender)
968+ @sstp_layout.set_text(s, -1)
969+ message_width, message_height = \
970+ @sstp_layout.get_pixel_size()
971+ if message_width > w
972+ break
973+ end
974+ @sstp_message = s
975+ end
976+ end
977+ @darea.queue_draw()
978+ end
979+
980+ def hide_sstp_message
981+ @sstp_message = nil
982+ @darea.queue_draw()
983+ end
984+
985+ def redraw_sstp_message(widget, cr)
986+ if @sstp_message == nil
987+ return
988+ end
989+ cr.save()
990+ # draw sstp marker
991+ if @sstp_surface != nil
992+ x, y = @sstp[0]
993+ cr.set_source(@sstp_surface, x, y)
994+ cr.paint()
995+ end
996+ # draw sstp message
997+ x, y, w, h = @sstp_region
998+# @sstp_layout.set_text(@sstp_message, -1)
999+ @sstp_layout.set_text(@sstp_message)
1000+ cr.set_source_rgba(*@sstp_message_color)
1001+ cr.move_to(x, y)
1002+ cr.show_pango_layout(@sstp_layout)
1003+# PangoCairo.update_layout(cr, @sstp_layout)
1004+# PangoCairo.show_layout(cr, @sstp_layout)
1005+ cr.restore()
1006+ end
1007+
1008+ def redraw_arrow0(widget, cr)
1009+ if @lineno <= 0
1010+ return
1011+ end
1012+ cr.save()
1013+ x, y = @arrow[0]
1014+ cr.set_source(@arrow0_surface, x, y)
1015+ cr.paint()
1016+ cr.restore()
1017+ end
1018+
1019+ def redraw_arrow1(widget, cr)
1020+ if @lineno + @lines >= @text_buffer.length
1021+ return
1022+ end
1023+ cr.save()
1024+ x, y = @arrow[1]
1025+ cr.set_source(@arrow1_surface, x, y)
1026+ cr.paint()
1027+ cr.restore()
1028+ end
1029+
1030+ def set_markup(index, text)
1031+ tags_ = ['sup', 'sub', 's', 'u']
1032+ count_ = {}
1033+ for tag_ in tags_
1034+ count_[tag_] = 0
1035+ end
1036+ markup_list = []
1037+ for sl, sn, tag in @meta_buffer
1038+ if sl == index
1039+ markup_list << [sn, tag]
1040+ end
1041+ end
1042+ if not markup_list
1043+ return GLib.markup_escape_text(text)
1044+ end
1045+ markup_list.sort()
1046+ markup_list.reverse()
1047+ pn = text.length
1048+ for sn, tag in markup_list
1049+ text = [text[0, sn], tag,
1050+ GLib.markup_escape_text(text[sn, pn]), text[pn, text.length]].join('')
1051+ pn = sn
1052+ if tag[1] == '/'
1053+ tag_ = tag[2, tag.length - 1]
1054+ assert tags_.include?(tag_)
1055+ count_[tag_] -= 1
1056+ if count_[tag_] < 0
1057+ text = ['<', tag_, '>', text].join('')
1058+ count_[tag_] += 1
1059+ end
1060+ else
1061+ tag_ = tag[1, tag.length - 1]
1062+ assert tags_.include?(tag_)
1063+ count_[tag_] += 1
1064+ if count_[tag_] > 0
1065+ text = [text, '</', tag_, '>'].join('')
1066+ count_[tag_] -= 1
1067+ end
1068+ end
1069+ end
1070+ return text
1071+ end
1072+
1073+ def redraw(widget, cr)
1074+ if @parent.handle_request('GET', 'lock_repaint')
1075+ return
1076+ end
1077+ if not @__shown
1078+ return true
1079+ end
1080+# assert @balloon_surface != nil
1081+# scale = @scale
1082+ cr.scale(scale / 100.0, scale / 100.0)
1083+ cr.set_source(@balloon_surface, 0, 0)
1084+ cr.set_operator(Cairo::OPERATOR_SOURCE)
1085+ cr.paint()
1086+ cr.set_operator(Cairo::OPERATOR_OVER) # restore default
1087+ # draw images
1088+ for i in 0..(@images.length - 1)
1089+ image_surface, (x, y) = @images[i]
1090+ w = image_surface.get_width()
1091+ h = image_surface.get_height()
1092+ if x == 'centerx'
1093+ bw, bh = get_balloon_size(scaling=false)
1094+ x = (bw - w) / 2
1095+ else
1096+ begin
1097+ x = x.to_i
1098+ rescue # except:
1099+ next
1100+ end
1101+ end
1102+ if y == 'centery'
1103+ bw, bh = get_balloon_size(scaling=false)
1104+ y = (bh - h) / 2
1105+ else
1106+ begin
1107+ y = y.to_i
1108+ rescue # except:
1109+ next
1110+ end
1111+ end
1112+ cr.set_source_surface(image_surface, x, y)
1113+ cr.paint()
1114+ end
1115+ # draw text
1116+ i = @lineno
1117+ j = @text_buffer.length
1118+ line = 0
1119+ while line < @lines
1120+ if i >= j
1121+ break
1122+ end
1123+ x, y, w, h = @line_regions[line]
1124+ if @text_buffer[i].end_with?('\n[half]')
1125+ new_y = (y + (@font_height + @line_space) / 2).to_i
1126+ markup = set_markup(i, @text_buffer[i][0, @text_buffer.length - 7])
1127+ else
1128+ new_y = (y + @font_height + @line_space).to_i
1129+ markup = set_markup(i, @text_buffer[i])
1130+ end
1131+ update_line_regions(line + 1, new_y)
1132+ @layout.set_markup(markup, -1)
1133+ cr.set_source_rgba(*@text_normal_color)
1134+ cr.move_to(x, y)
1135+ cr.show_pango_layout(@layout)
1136+# PangoCairo.update_layout(cr, @layout)
1137+# PangoCairo.show_layout(cr, @layout)
1138+ if @sstp_surface != nil
1139+ for l, c in @sstp_marker
1140+ if l == i
1141+ mw = @sstp_surface.get_width()
1142+ mh = @sstp_surface.get_height()
1143+ @layout.set_text(@text_buffer[i][0, c], -1)
1144+ text_w, text_h = @layout.get_pixel_size()
1145+ mx = x + text_w
1146+ my = y + (@font_height + @line_space) / 2
1147+ my = my - mh / 2
1148+ cr.set_source_surface(@sstp_surface, mx, my)
1149+ cr.paint()
1150+ end
1151+ end
1152+ end
1153+ i += 1
1154+ line += 1
1155+ end
1156+ if @side == 0 and @sstp_message
1157+ redraw_sstp_message(widget, cr)
1158+ end
1159+ if @selection != nil
1160+ update_link_region(widget, cr, @selection)
1161+ end
1162+ redraw_arrow0(widget, cr)
1163+ redraw_arrow1(widget, cr)
1164+ return false
1165+ end
1166+
1167+ def update_link_region(widget, cr, index)
1168+ cr.save()
1169+ sl = @link_buffer[index][0]
1170+ el = @link_buffer[index][2]
1171+ if @lineno <= sl <= @lineno + @lines
1172+ sn = @link_buffer[index][1]
1173+ en = @link_buffer[index][3]
1174+ for n in range(sl, el + 1)
1175+ if n - @lineno >= @line_regions.length
1176+ break
1177+ x, y, w, h = @line_regions[n - @lineno]
1178+ if sl == el
1179+ markup = set_markup(n, @text_buffer[n][0, sn])
1180+ @layout.set_markup(markup, -1)
1181+ text_w, text_h = @layout.get_pixel_size()
1182+ x += text_w
1183+ markup = set_markup(n, @text_buffer[n][sn, en])
1184+ @layout.set_markup(markup, -1)
1185+ text_w, text_h = @layout.get_pixel_size()
1186+ w = text_w
1187+ start = sn
1188+ end_ = en
1189+ elsif n == sl
1190+ markup = set_markup(n, @text_buffer[n][0, sn])
1191+ @layout.set_markup(markup, -1)
1192+ text_w, text_h = @layout.get_pixel_size()
1193+ x += text_w
1194+ markup = set_markup(n, @text_buffer[n][sn, @text_buffer.length])
1195+ @layout.set_markup(markup, -1)
1196+ text_w, text_h = @layout.get_pixel_size()
1197+ w = text_w
1198+ start = sn
1199+ end_ = @text_buffer[n].length
1200+ elsif n == el
1201+ markup = set_markup(n, @text_buffer[n][0, en])
1202+ @layout.set_markup(markup, -1)
1203+ text_w, text_h = @layout.get_pixel_size()
1204+ w = text_w
1205+ start = 0
1206+ end_ = en
1207+ else
1208+ markup = set_markup(n, @text_buffer[n])
1209+ @layout.set_markup(markup, -1)
1210+ text_w, text_h = @layout.get_pixel_size()
1211+ w = text_w
1212+ start = 0
1213+ end_ = @text_buffer[n].length
1214+ end
1215+ markup = set_markup(n, @text_buffer[n][start, end_])
1216+ @layout.set_markup(markup, -1)
1217+ cr.set_source_rgba(*@cursor_color)
1218+ cr.rectangle(x, y, w, h)
1219+ cr.fill()
1220+ cr.move_to(x, y)
1221+ cr.set_source_rgba(*@text_active_color)
1222+ PangoCairo.update_layout(cr, @layout)
1223+ PangoCairo.show_layout(cr, @layout)
1224+ end
1225+ end
1226+ end
1227+ cr.restore()
1228+ end
1229+
1230+ def check_link_region(px, py)
1231+ new_selection = nil
1232+ for i in 0..(@link_buffer.length - 1)
1233+ sl = @link_buffer[i][0]
1234+ el = @link_buffer[i][2]
1235+ if @lineno <= sl <= @lineno + @lines
1236+ sn = @link_buffer[i][1]
1237+ en = @link_buffer[i][3]
1238+ for n in range(sl,el + 1)
1239+ if n - @lineno >= @line_regions.length
1240+ break
1241+ end
1242+ x, y, w, h = @line_regions[n - @lineno]
1243+ if n == sl
1244+ markup = set_markup(n, @text_buffer[n][0, sn])
1245+ @layout.set_markup(markup, -1)
1246+ text_w, text_h = @layout.get_pixel_size()
1247+ x += text_w
1248+ end
1249+ if n == sl and n == el
1250+ markup = set_markup(n, @text_buffer[n][sn, en])
1251+ elsif n == el
1252+ markup = set_markup(n, @text_buffer[n][0, en])
1253+ else
1254+ markup = set_markup(n, @text_buffer[n])
1255+ end
1256+ @layout.set_markup(markup, -1)
1257+ text_w, text_h = @layout.get_pixel_size()
1258+ w = text_w
1259+ if x <= px < x + w and y <= py < y + h
1260+ new_selection = i
1261+ break
1262+ end
1263+ end
1264+ end
1265+ end
1266+ if new_selection != nil
1267+ if @selection != new_selection
1268+ sl, sn, el, en, link_id, raw_text, text = \
1269+ @link_buffer[new_selection]
1270+ @parent.handle_request(
1271+ 'NOTIFY', 'notify_event',
1272+ 'OnChoiceEnter', raw_text, link_id, @selection)
1273+ end
1274+ else
1275+ if @selection != nil
1276+ @parent.handle_request('NOTIFY', 'notify_event', 'OnChoiceEnter')
1277+ end
1278+ end
1279+ if new_selection == @selection
1280+ return 0
1281+ else
1282+ @selection = new_selection
1283+ return 1 # dirty flag
1284+ end
1285+ end
1286+
1287+ def motion_notify(widget, event)
1288+ if event.hint?
1289+ _, x, y, state = widget.window.get_device_position(event.device)
1290+ else
1291+ x, y, state = event.x, event.y, event.get_state()
1292+ end
1293+# scale = @scale
1294+ px, py = @window.winpos_to_surfacepos(x, y, scale)
1295+ if @link_buffer
1296+ if check_link_region(px, py)
1297+ widget.queue_draw()
1298+ end
1299+ end
1300+ if not @parent.handle_request('GET', 'busy')
1301+ if state & Gdk.ModifierType.BUTTON1_MASK
1302+ if @x_root != nil and \
1303+ @y_root != nil
1304+ @dragged = true
1305+ x_delta = (event.x_root - @x_root) * 100 / scale + @x_fractions
1306+ y_delta = (event.y_root - @y_root) * 100 / scale + @y_fractions
1307+ @x_fractions = x_delta - x_delta.to_i
1308+ @y_fractions = y_delta - y_delta.to_i
1309+ @parent.handle_request(
1310+ 'NOTIFY', 'update_balloon_offset',
1311+ @side, x_delta.to_i, y_delta.to_i)
1312+ @x_root = event.x_root
1313+ @y_root = event.y_root
1314+ end
1315+ end
1316+ end
1317+ return true
1318+ end
1319+
1320+ def scroll(darea, event)
1321+ px, py = @window.winpos_to_surfacepos(
1322+ event.x.to_i, event.y.to_i, scale)
1323+ if event.direction == Gdk.ScrollDirection.UP
1324+ if @lineno > 0
1325+ @lineno = [@lineno - 2, 0].max
1326+ check_link_region(px, py)
1327+ @darea.queue_draw()
1328+ end
1329+ elsif event.direction == Gdk.ScrollDirection.DOWN
1330+ if @lineno + @lines < @text_buffer.length
1331+ @lineno = [@lineno + 2,
1332+ @text_buffer.length - @lines].min
1333+ check_link_region(px, py)
1334+ @darea.queue_draw()
1335+ end
1336+ end
1337+ return true
1338+ end
1339+
1340+ def button_press(darea, event)
1341+ @parent.handle_request('NOTIFY', 'reset_idle_time')
1342+ if event.event_type == Gdk::Event::BUTTON_PRESS
1343+ click = 1
1344+ else
1345+ click = 2
1346+ end
1347+ if @parent.handle_request('GET', 'is_paused')
1348+ @parent.handle_request('NOTIFY', 'notify_balloon_click',
1349+ event.button, click, @side)
1350+ return true
1351+ end
1352+ # arrows
1353+ px, py = @window.winpos_to_surfacepos(
1354+ event.x.to_i, event.y.to_i, scale)
1355+ # up arrow
1356+ w = @arrow0_surface.get_width()
1357+ h = @arrow0_surface.get_height()
1358+ x, y = @arrow[0]
1359+ if x <= px <= x + w and y <= py <= y + h
1360+ if @lineno > 0
1361+ @lineno = [@lineno - 2, 0].max
1362+ @darea.queue_draw()
1363+ end
1364+ return true
1365+ end
1366+ # down arrow
1367+ w = @arrow1_surface.get_width()
1368+ h = @arrow1_surface.get_height()
1369+ x, y = @arrow[1]
1370+ if x <= px <= x + w and y <= py <= y + h
1371+ if @lineno + @lines < @text_buffer.length
1372+ @lineno = [@lineno + 2,
1373+ @text_buffer.length - @lines].min
1374+ @darea.queue_draw()
1375+ end
1376+ return true
1377+ end
1378+ # links
1379+ if @selection != nil
1380+ sl, sn, el, en, link_id, raw_text, text = \
1381+ @link_buffer[@selection]
1382+ @parent.handle_request('NOTIFY', 'notify_link_selection',
1383+ link_id, raw_text, @selection)
1384+ return true
1385+ end
1386+ # balloon's background
1387+ @parent.handle_request('NOTIFY', 'notify_balloon_click',
1388+ event.button, click, @side)
1389+ @x_root = event.x_root
1390+ @y_root = event.y_root
1391+ return true
1392+ end
1393+
1394+ def button_release(window, event)
1395+ x, y = @window.winpos_to_surfacepos(
1396+ event.x.to_i, event.y.to_i, scale)
1397+ if @dragged
1398+ @dragged = false
1399+ end
1400+ @x_root = nil
1401+ @y_root = nil
1402+ @y_fractions = 0
1403+ @y_fractions = 0
1404+ return true
1405+ end
1406+
1407+ def clear_text
1408+ @selection = nil
1409+ @lineno = 0
1410+ @text_buffer = []
1411+ @meta_buffer = []
1412+ @link_buffer = []
1413+ @newline_required = 0
1414+ @images = []
1415+ @sstp_marker = []
1416+ @darea.queue_draw()
1417+ end
1418+
1419+ def get_text_count
1420+ return @text_count
1421+ end
1422+
1423+ def reset_text_count
1424+ @text_count = 0
1425+ end
1426+
1427+ def set_newline
1428+ @newline_required = 1
1429+ end
1430+
1431+ def append_text(text)
1432+ if @text_buffer.empty?
1433+ s = ''
1434+ column = 0
1435+ index = 0
1436+ elsif @newline_required
1437+ s = ''
1438+ column = 0
1439+ @newline_required = 0
1440+ index = @text_buffer.length
1441+ else
1442+ index = @text_buffer.length - 1
1443+ s = @text_buffer.pop(-1)
1444+ column = s.length
1445+ end
1446+ i = s.length
1447+ text = [s, text].join('')
1448+ j = text.length
1449+ @text_count += j
1450+ p = 0
1451+ while 1
1452+ if i >= j
1453+ @text_buffer << text[p, i]
1454+ draw_last_line(column)
1455+ break
1456+ end
1457+ if text[i] == '\n'
1458+ if j >= i + 7 and text[i, i + 7] == '\n[half]'
1459+ @text_buffer << [text[p, i], '\n[half]'].join('')
1460+ p = i = i + 7
1461+ else
1462+ @text_buffer << text[p, i]
1463+ p = i = i + 1
1464+ end
1465+ draw_last_line(column)
1466+ column = 0
1467+ next
1468+ end
1469+ n = i + 1
1470+ if not @__shown
1471+ show()
1472+ end
1473+ markup = set_markup(index, text[p, n])
1474+ @layout.set_markup(markup, -1)
1475+ text_width, text_height = @layout.pixel_size
1476+ if text_width > @line_width
1477+ @text_buffer << text[p, i]
1478+ draw_last_line(column)
1479+ column = 0
1480+ p = i
1481+ end
1482+ i = n
1483+ end
1484+ end
1485+
1486+ def append_sstp_marker
1487+ if @sstp_surface != nil
1488+ return
1489+ end
1490+ if @text_buffer.empty?
1491+ line = 0
1492+ offset = 0
1493+ else
1494+ line = @text_buffer.length - 1
1495+ offset = @text_buffer[-1].length
1496+ end
1497+ if @newline_required
1498+ line = line + 1
1499+ offset = 0
1500+ end
1501+ @sstp_marker << [line, offset]
1502+ w = @sstp_surface.get_width()
1503+ h = @sstp_surface.get_height()
1504+ i = 1
1505+ while 1
1506+ space = '\u3000' * i ## FIXME
1507+ @layout.set_text(space, -1)
1508+ text_w, text_h = @layout.get_pixel_size()
1509+ if text_w > w
1510+ break
1511+ else
1512+ i += 1
1513+ end
1514+ end
1515+ append_text(space)
1516+ draw_last_line(offset)
1517+ end
1518+
1519+ def append_link_in(link_id)
1520+ if @text_buffer.empty?
1521+ sl = 0
1522+ sn = 0
1523+ else
1524+ sl = @text_buffer.length - 1
1525+ sn = @text_buffer[-1].length
1526+ end
1527+ @link_buffer << [sl, sn, sl, sn, link_id, '', '']
1528+ end
1529+
1530+ def append_link_out(link_id, text)
1531+ if not text
1532+ return
1533+ end
1534+ raw_text = text
1535+ if @text_buffer.empty?
1536+ el = 0
1537+ en = 0
1538+ else
1539+ el = @text_buffer.length - 1
1540+ en = @text_buffer[-1].length
1541+ end
1542+ for i in range(@link_buffer.length)
1543+ if @link_buffer[i][4] == link_id
1544+ sl = @link_buffer[i][0]
1545+ sn = @link_buffer[i][1]
1546+ @link_buffer.pop(i)
1547+ @link_buffer.insert(i, [sl, sn, el, en, link_id, raw_text, text])
1548+ break
1549+ end
1550+ end
1551+ end
1552+
1553+ def append_meta(tag)
1554+ if not tag
1555+ return
1556+ end
1557+ if @text_buffer.empty?
1558+ sl = 0
1559+ sn = 0
1560+ else
1561+ sl = @text_buffer.length - 1
1562+ sn = @text_buffer[-1].length
1563+ end
1564+ @meta_buffer << [sl, sn, tag]
1565+ end
1566+
1567+ def append_image(path, x, y)
1568+ begin
1569+ image_surface = Pix.create_surface_from_file(path)
1570+ rescue # except:
1571+ return
1572+ end
1573+ show()
1574+ @images << [image_surface, [x, y]]
1575+ @darea.queue_draw()
1576+ end
1577+
1578+ def draw_last_line(column=0)
1579+ if not @__shown
1580+ return
1581+ end
1582+ line = @text_buffer.length - 1
1583+ if @lineno <= line && line < @lineno + @lines
1584+ x, y, w, h = @line_regions[line - @lineno]
1585+ if @text_buffer[line].end_with?('\n[half]')
1586+ offset = line - @lineno + 1
1587+ new_y = (y + (@font_height + @line_space) / 2).to_i
1588+ update_line_regions(offset, new_y)
1589+ else
1590+ @darea.queue_draw()
1591+ end
1592+ if @sstp_surface != nil
1593+ for l, c in @sstp_marker
1594+ if l == line
1595+ mw = @sstp_surface.get_width()
1596+ mh = @sstp_surface.get_height()
1597+ @layout.set_text(@text_buffer[l][0, c], -1)
1598+ text_w, text_h = @layout.get_pixel_size()
1599+ mx = x + text_w
1600+ my = y + (@font_height + @line_space) / 2
1601+ my = my - mh / 2
1602+ cr = @darea.get_window().cairo_create()
1603+ cr.set_source_surface(@sstp_surface, mx, my)
1604+ cr.paint()
1605+ del cr
1606+ end
1607+ end
1608+ end
1609+ else
1610+ @darea.queue_draw()
1611+ if @autoscroll
1612+ while line >= @lineno + @lines
1613+ @lineno += 1
1614+ @darea.queue_draw()
1615+ end
1616+ end
1617+ end
1618+ end
1619+ end
1620+
1621+
1622+ class CommunicateWindow
1623+
1624+ NAME = ''
1625+ ENTRY = ''
1626+
1627+ def initialize
1628+ @parent = nil
1629+ @window = nil
1630+ end
1631+
1632+ def set_responsible(parent)
1633+ @parent = parent
1634+ end
1635+
1636+ def new_(desc, balloon)
1637+ if @window != nil
1638+ @window.destroy()
1639+ end
1640+ @window = Pix::BaseTransparentWindow.new()
1641+ @window.set_title('communicate')
1642+ @window.signal_connect('delete_event') do |w ,e|
1643+ delete(w, e)
1644+ end
1645+ @window.signal_connect('key_press_event') do |w, e|
1646+ key_press(w, e)
1647+ end
1648+ @window.signal_connect('button_press_event') do |w, e|
1649+ button_press(w, e)
1650+ end
1651+ @window.signal_connect('drag_data_received') do |w, e|
1652+ drag_data_received(w, e)
1653+ end
1654+ # DnD data types
1655+# dnd_targets = [Gtk.TargetEntry.new('text/plain', 0, 0)]
1656+# @window.drag_dest_set(Gtk.DestDefaults.ALL, dnd_targets,
1657+# Gdk.DragAction.COPY)
1658+# @window.drag_dest_add_text_targets()
1659+ @window.set_events(Gdk::Event::BUTTON_PRESS_MASK)
1660+ @window.set_modal(true)
1661+ @window.set_window_position(Gtk::Window::Position::CENTER)
1662+ @window.realize()
1663+ w = desc.get('communicatebox.width', 250).to_i
1664+ h = desc.get('communicatebox.height', -1).to_i
1665+ @entry = Gtk::Entry.new
1666+ @entry.signal_connect('activate') do |w|
1667+ activate(w)
1668+ end
1669+ @entry.set_inner_border(nil)
1670+ @entry.set_has_frame(false)
1671+ font_desc = Pango::FontDescription.new()
1672+ font_desc.set_size(9 * 3 / 4 * Pango::SCALE) # XXX
1673+ @entry.modify_font(font_desc)
1674+ @entry.set_size_request(w, h)
1675+ @entry.show()
1676+ surface = nil
1677+ if balloon
1678+ path, config = balloon
1679+ # load pixbuf
1680+ begin
1681+ surface = Pix.create_surface_from_file(path)
1682+ rescue # except:
1683+ surface = nil
1684+ end
1685+ end
1686+ if surface != nil
1687+ darea = Gtk::DrawingArea.new()
1688+ darea.set_events(Gdk::Event::EXPOSURE_MASK)
1689+ darea.signal_connect('draw') do |w, e|
1690+ redraw(w, e, surface)
1691+ end
1692+ darea.show()
1693+ x = desc.get('communicatebox.x', 10).to_i
1694+ y = desc.get('communicatebox.y', 20).to_i
1695+ overlay = Gtk::Overlay.new()
1696+ @entry.set_margin_left(x)
1697+ @entry.set_margin_top(y)
1698+ @entry.set_halign(Gtk::Alignment::Align::START)
1699+ @entry.set_valign(Gtk::Alignment::Align::START)
1700+ overlay.add_overlay(@entry)
1701+ overlay.add(darea)
1702+ overlay.show()
1703+ @window.add(overlay)
1704+ w = surface.width
1705+ h = surface.height
1706+ darea.set_size_request(w, h)
1707+ else
1708+ box = Gtk::HBox.new(spacing=10)
1709+ box.set_border_width(10)
1710+ if ENTRY
1711+ label = Gtk::Label.new(label=ENTRY)
1712+ box.pack_start(label, false, true, 0)
1713+ label.show()
1714+ end
1715+ box.pack_start(@entry, true, true, 0)
1716+ @window.add(box)
1717+ box.show()
1718+ end
1719+ end
1720+
1721+ def drag_data_received(widget, context, x, y, data, info, time)
1722+ @entry.set_text(data.get_text())
1723+ end
1724+
1725+ def redraw(widget, cr, surface)
1726+ cr.set_source_surface(surface, 0, 0)
1727+ cr.set_operator(cairo.OPERATOR_SOURCE)
1728+ cr.paint()
1729+ region = Gdk.cairo_region_create_from_surface(cr.get_target())
1730+ # XXX: to avoid losing focus in the text input region
1731+ x = @entry.get_margin_left()
1732+ y = @entry.get_margin_top()
1733+ w = @entry.get_allocated_width()
1734+ h = @entry.get_allocated_height()
1735+ region.union(cairo.RectangleInt(x, y, w, h))
1736+ @window.input_shape_combine_region(region)
1737+ end
1738+
1739+ def destroy
1740+ if @window
1741+ @window.destroy()
1742+ @window = nil
1743+ end
1744+ end
1745+
1746+ def delete(widget, event)
1747+ @window.hide()
1748+ cancel()
1749+ return true
1750+ end
1751+
1752+ def key_press(widget, event)
1753+ if event.keyval == Gdk.KEY_Escape
1754+ @window.hide()
1755+ cancel()
1756+ return true
1757+ end
1758+ return false
1759+ end
1760+
1761+ def button_press(widget, event)
1762+ if [1, 2].include?(event.button)
1763+ @window.begin_move_drag(
1764+ event.button, event.x_root.to_i, event.y_root.to_i,
1765+ Gtk.get_current_event_time())
1766+ end
1767+ return true
1768+ end
1769+
1770+ def activate(widget)
1771+ @window.hide()
1772+ enter()
1773+ return true
1774+ end
1775+
1776+ def show(default='')
1777+ @entry.set_text(default)
1778+ @window.show()
1779+ end
1780+
1781+# @abc.abstractmethod
1782+ def enter
1783+# pass
1784+ end
1785+
1786+# @abc.abstractmethod
1787+ def cancel
1788+# pass
1789+ end
1790+ end
1791+
1792+
1793+ class CommunicateBox < CommunicateWindow
1794+
1795+ NAME = 'communicatebox'
1796+ ENTRY = 'Communicate'
1797+
1798+ def new_(desc, balloon)
1799+ super
1800+ @window.set_modal(false)
1801+ end
1802+
1803+ def delete(widget, event)
1804+ @window.hide()
1805+ cancel()
1806+ @parent.handle_request('NOTIFY', 'reset_user_interaction')
1807+ return true
1808+ end
1809+
1810+ def key_press(widget, event)
1811+ if event.keyval == Gdk.KEY_Escape
1812+ @window.hide()
1813+ cancel()
1814+ @parent.handle_request('NOTIFY', 'reset_user_interaction')
1815+ return true
1816+ end
1817+ return false
1818+ end
1819+
1820+ def activate(widget)
1821+ enter()
1822+ @entry.set_text('')
1823+ return true
1824+ end
1825+
1826+ def enter
1827+ send(@entry.get_text())
1828+ end
1829+
1830+ def cancel
1831+ @parent.handle_request('NOTIFY', 'notify_event',
1832+ 'OnCommunicateInputCancel', '', 'cancel')
1833+ end
1834+
1835+ def send(data)
1836+ if data != nil
1837+ @parent.handle_request('NOTIFY', 'notify_event',
1838+ 'OnCommunicate', 'user', data)
1839+ end
1840+ end
1841+ end
1842+
1843+ class TeachBox < CommunicateWindow
1844+
1845+ NAME = 'teachbox'
1846+ ENTRY = 'Teach'
1847+
1848+ def enter
1849+ send(@entry.get_text())
1850+ end
1851+
1852+ def cancel
1853+ @parent.handle_request('NOTIFY', 'notify_event',
1854+ 'OnTeachInputCancel', '', 'cancel')
1855+ @parent.handle_request('NOTIFY', 'reset_user_interaction')
1856+ end
1857+
1858+ def send(data)
1859+ @parent.handle_request('NOTIFY', 'notify_user_teach', data)
1860+ @parent.handle_request('NOTIFY', 'reset_user_interaction')
1861+ end
1862+ end
1863+
1864+
1865+ class InputBox < CommunicateWindow
1866+
1867+ NAME = 'inputbox'
1868+ ENTRY = 'Input'
1869+
1870+ def new_(desc, balloon)
1871+ super
1872+ @symbol = nil
1873+ @limittime = -1
1874+ end
1875+
1876+ def set_symbol(symbol)
1877+ @symbol = symbol
1878+ end
1879+
1880+ def set_limittime(limittime)
1881+ begin
1882+ limittime = limittime.to_i
1883+ rescue # except ValueError:
1884+ limittime = -1
1885+ end
1886+ @limittime = limittime
1887+ end
1888+
1889+ def show(default)
1890+ if default != nil
1891+ begin
1892+ text = str(default)
1893+ rescue # except:
1894+ text = ''
1895+ end
1896+ else
1897+ text = ''
1898+ end
1899+ if @limittime.to_i < 0
1900+ @timeout_id = nil
1901+ else
1902+ @timeout_id = GLib.timeout_add(@limittime, @timeout)
1903+ end
1904+ CommunicateWindow.show(self, text)
1905+ end
1906+
1907+ def timeout
1908+ @window.hide()
1909+ send('timeout', timeout=true)
1910+ end
1911+
1912+ def enter
1913+ send(@entry.get_text())
1914+ end
1915+
1916+ def cancel
1917+ send(nil, cancel=true)
1918+ end
1919+
1920+ def close(symbol)
1921+ if @symbol == nil
1922+ return
1923+ end
1924+ if symbol != '__SYSTEM_ALL_INPUT__' and @symbol != symbol
1925+ return
1926+ end
1927+ @window.hide()
1928+ cancel()
1929+ end
1930+
1931+ def send(data, cancel=false, timeout=false)
1932+ if @timeout_id != nil
1933+ GLib.source_remove(@timeout_id)
1934+ end
1935+ if data == nil
1936+ data = ''
1937+ end
1938+ ## CHECK: symbol
1939+ if cancel
1940+ @parent.handle_request('NOTIFY', 'notify_event',
1941+ 'OnUserInputCancel', '', 'cancel')
1942+ elsif timeout and \
1943+ @parent.handle_request('GET', 'notify_event',
1944+ 'OnUserInputCancel', '', 'timeout')
1945+ # pass
1946+ elsif @symbol == 'OnUserInput' and \
1947+ @parent.handle_request('GET', 'notify_event', 'OnUserInput', data)
1948+ # pass
1949+ elsif @parent.handle_request('GET', 'notify_event',
1950+ 'OnUserInput', @symbol, data)
1951+ # pass
1952+ elsif @parent.handle_request('GET', 'notify_event', @symbol, data)
1953+ # pass
1954+ end
1955+ @symbol = nil
1956+ @parent.handle_request('NOTIFY', 'reset_user_interaction')
1957+ end
1958+ end
1959+
1960+ class PasswordInputBox < InputBox
1961+
1962+ NAME = 'passwordinputbox'
1963+ ENTRY = 'PasswordInput'
1964+
1965+ def new_(desc, balloon)
1966+ super
1967+ @entry.set_visibility(false)
1968+ end
1969+ end
1970+
1971+
1972+ class TEST
1973+
1974+ def initialize
1975+ require "ninix/home"
1976+ balloons = Home.search_balloons()
1977+ key = balloons.keys.sample
1978+# print(key, balloons[key], "\n")
1979+ balloon = Balloon.new
1980+ balloon.set_responsible(self)
1981+ balloon.new_(*balloons[key])
1982+ balloon.set_balloon(0, 0)
1983+ balloon.set_balloon(1, 0)
1984+ balloon.set_position(0, 200, 200)
1985+ balloon.set_position(1, 100, 100)
1986+ balloon.show(0)
1987+ balloon.show(1)
1988+ balloon.show_sstp_message("TEST: SSTP", "TEST class")
1989+ for i in 0..20
1990+ balloon.append_text(0, "TEST: SAKURA")
1991+ end
1992+ balloon.append_text(1, "TEST: KERO")
1993+ Gtk.main
1994+ end
1995+
1996+ def handle_request(event_type, event, *arglist, **argdict)
1997+ if event == 'lock_repaint'
1998+ return false
1999+ else
2000+ return 100 # XXX
2001+ end
2002+ end
2003+
2004+ end
2005+end
2006+
2007+Balloon::TEST.new()