Skip to content

Instantly share code, notes, and snippets.

@ShikiOkasaka
Last active April 30, 2024 04:15
Show Gist options
  • Save ShikiOkasaka/404a2d2a95b78cc5b2792a8b1b09d918 to your computer and use it in GitHub Desktop.
Save ShikiOkasaka/404a2d2a95b78cc5b2792a8b1b09d918 to your computer and use it in GitHub Desktop.
GTK4でIBusの周辺テキストをつかった漢字入力に対応したアプリの例。
#!/usr/bin/python3
#
# Copyright (c) 2017-2024 Esrille Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import sys
import cairo
import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import Gtk
LOGGER = logging.getLogger(__name__)
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.set_child(box)
self.canvas = Gtk.DrawingArea(focusable=True)
self.canvas.set_hexpand(True)
self.canvas.set_vexpand(True)
self.canvas.set_draw_func(self.on_draw, None)
box.append(self.canvas)
self.__text = 'こんにちは、世界!'
self.__cur = len(self.__text)
self.__blink = True
self.__active = False
self.__preedit = ('', None, 0)
evk = Gtk.EventControllerFocus.new()
evk.connect('enter', self.on_enter)
evk.connect('leave', self.on_leave)
self.canvas.add_controller(evk) # add to window
evk = Gtk.EventControllerKey.new()
evk.connect('key-pressed', self.on_key_pressed)
evk.connect('key-released', self.on_key_released)
self.add_controller(evk) # add to window
self.im_context = Gtk.IMMulticontext()
self.im_context.set_client_widget(self.canvas)
self.im_context.connect('commit', self.on_commit)
self.im_context.connect('delete-surrounding', self.on_delete_surrounding)
self.im_context.connect('retrieve-surrounding', self.on_retrieve_surrounding)
self.im_context.connect('preedit-changed', self.on_preedit_changed)
self.im_context.connect('preedit-end', self.on_preedit_end)
self.im_context.connect('preedit-start', self.on_preedit_start)
GLib.timeout_add(600, self.on_blink)
def on_blink(self):
self.__blink ^= True
self.canvas.queue_draw()
return True
def on_enter(self, controller: Gtk.EventControllerFocus):
self.__active = True
self.im_context.reset()
self.im_context.focus_in()
return True
def on_leave(self, controller: Gtk.EventControllerFocus):
self.__active = False
self.im_context.focus_out()
return True
def on_draw(self, wid, cr, width, height, data):
LOGGER.info('darw')
cr.set_source_rgb(255, 255, 255)
cr.rectangle(0, 0, width, height)
cr.fill()
cr.set_source_rgb(0, 0, 0)
cr.select_font_face('Noto Sans Mono CJK JP', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
cr.set_font_size(16)
cr.move_to(10, 26)
current = self.__text[:self.__cur]
if self.__preedit[0]:
text = current + self.__preedit[0] + self.__text[self.__cur:]
current += self.__preedit[0][:self.__preedit[2]]
else:
text = self.__text
cr.show_text(text)
ext = cr.text_extents(current)
# Display the caret:
#
# Pick x_advance not width.
# ext would look like (2.0, -14.0, 206.0, 16.0, 256.0, 0.0).
r = Gdk.Rectangle()
r.x, r.y, r.width, r.height = 10 + ext[4], 12, 2, 16
self.im_context.set_cursor_location(r)
if self.__blink and self.__active:
cr.rectangle(r.x, r.y, r.width, r.height)
cr.fill()
def on_key_pressed(self, controller: Gtk.EventControllerKey, keyval, keycode, state):
if self.im_context.filter_key(True,
self.canvas.get_native().get_surface(),
controller.get_current_event_device(),
controller.get_current_event_time(),
keycode,
state,
controller.get_group()):
return True
if keyval == Gdk.KEY_BackSpace:
if 0 < self.__cur:
self.__text = self.__text[:self.__cur - 1] + self.__text[self.__cur:]
self.__cur -= 1
self.im_context.reset()
self.canvas.queue_draw()
return True
elif keyval == Gdk.KEY_Left:
if 0 < self.__cur:
self.__cur -= 1
self.im_context.reset()
self.canvas.queue_draw()
return True
elif keyval == Gdk.KEY_Right:
if self.__cur < len(self.__text):
self.__cur += 1
self.im_context.reset()
self.canvas.queue_draw()
return True
elif keyval == Gdk.KEY_Home:
if 0 < self.__cur:
self.__cur = 0
self.im_context.reset()
self.canvas.queue_draw()
return True
elif keyval == Gdk.KEY_End:
if self.__cur < len(self.__text):
self.__cur = len(self.__text)
self.im_context.reset()
self.canvas.queue_draw()
return True
return False
def on_key_released(self, controller: Gtk.EventControllerKey, keyval, keycode, state):
if self.im_context.filter_key(False,
self.canvas.get_native().get_surface(),
controller.get_current_event_device(),
controller.get_current_event_time(),
keycode,
state,
controller.get_group()):
return True
return False
def on_commit(self, wid, str):
self.__text = self.__text[:self.__cur] + str + self.__text[self.__cur:]
self.__cur += len(str)
self.canvas.queue_draw()
def on_retrieve_surrounding(self, wid):
index = len(self.__text[:self.__cur].encode())
self.im_context.set_surrounding_with_selection(self.__text, len(self.__text.encode()), index, index)
return True
def on_delete_surrounding(self, wid, offset, n_chars):
begin = self.__cur + offset
if begin < 0:
return False
end = self.__cur + offset + n_chars
if len(self.__text) < end:
return False
self.__text = self.__text[:begin] + self.__text[end:]
if end <= self.__cur:
self.__cur -= n_chars
elif begin <= self.__cur:
self.__cur = begin
self.canvas.queue_draw()
return True
def on_preedit_changed(self, wid):
self.__preedit = self.im_context.get_preedit_string()
self.canvas.queue_draw()
return False
def on_preedit_end(self, wid):
self.__preedit = self.im_context.get_preedit_string()
self.canvas.queue_draw()
return False
def on_preedit_start(self, wid):
self.__preedit = self.im_context.get_preedit_string()
self.canvas.queue_draw()
return False
class Example(Gtk.Application):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.connect('activate', self.on_activate)
def on_activate(self, app):
self.win = MainWindow(application=app)
self.win.present()
def main():
app = Example(application_id='com.example.GtkApplication')
app.run(sys.argv)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment