Skip to content

Commit e84956d

Browse files
committed
Fix GTK4 Port: Layout, Focus Management, and Arrow Navigation
1 parent f3bedcf commit e84956d

File tree

2 files changed

+296
-296
lines changed

2 files changed

+296
-296
lines changed

calculate.py

Lines changed: 99 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@
2626
_logger = logging.getLogger('Calculate')
2727

2828
import gi
29-
gi.require_version('Gtk', '3.0')
29+
gi.require_version('Gtk', '4.0')
3030
from gi.repository import Gtk
3131
from gi.repository import Gdk
3232
import base64
3333

34-
import sugar3.profile
35-
from sugar3.graphics.xocolor import XoColor
34+
import sugar4.profile
35+
from sugar4.graphics.xocolor import XoColor
3636

3737
from shareable_activity import ShareableActivity
3838
from layout import CalcLayout
@@ -64,8 +64,7 @@ def findchar(text, chars, ofs=0):
6464

6565
def _textview_realize_cb(widget):
6666
'''Change textview properties once window is created.'''
67-
win = widget.get_window(Gtk.TextWindowType.TEXT)
68-
win.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND1))
67+
widget.set_cursor_from_name("pointer")
6968
return False
7069

7170

@@ -244,32 +243,51 @@ def create_history_object(self):
244243
return self.result.get_image()
245244

246245
w = Gtk.TextView()
247-
w.modify_base(
248-
Gtk.StateType.NORMAL, Gdk.color_parse(self.color.get_fill_color()))
249-
w.modify_bg(
250-
Gtk.StateType.NORMAL,
251-
Gdk.color_parse(self.color.get_stroke_color()))
246+
w.set_editable(False)
247+
w.set_cursor_visible(False)
248+
249+
provider = Gtk.CssProvider()
250+
fill_color = self.color.get_fill_color()
251+
stroke_color = self.color.get_stroke_color()
252+
253+
css_str = """
254+
textview {
255+
background-color: %s;
256+
color: %s;
257+
}
258+
textview text {
259+
background-color: %s;
260+
color: %s;
261+
}
262+
""" % (stroke_color, fill_color, stroke_color, fill_color)
263+
264+
provider.load_from_data(css_str.encode())
265+
w.get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
266+
252267
w.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
253-
w.set_border_window_size(Gtk.TextWindowType.LEFT, 4)
254-
w.set_border_window_size(Gtk.TextWindowType.RIGHT, 4)
255-
w.set_border_window_size(Gtk.TextWindowType.TOP, 4)
256-
w.set_border_window_size(Gtk.TextWindowType.BOTTOM, 4)
268+
w.set_left_margin(4)
269+
w.set_right_margin(4)
270+
w.set_top_margin(4)
271+
w.set_bottom_margin(4)
257272
w.connect('realize', _textview_realize_cb)
258273
buf = w.get_buffer()
259274

260275
tagsmall = buf.create_tag(font=CalcLayout.FONT_SMALL)
261276
tagsmallnarrow = buf.create_tag(font=CalcLayout.FONT_SMALL_NARROW)
262277
tagbig = buf.create_tag(font=CalcLayout.FONT_BIG,
263278
justification=Gtk.Justification.RIGHT)
264-
# TODO Fix for old Sugar 0.82 builds, red_float not available
265-
bright = (
266-
Gdk.color_parse(self.color.get_fill_color()).red_float +
267-
Gdk.color_parse(self.color.get_fill_color()).green_float +
268-
Gdk.color_parse(self.color.get_fill_color()).blue_float) / 3.0
269-
if bright < 0.5:
270-
col = 'white'
279+
280+
# Calculate brightness
281+
c = Gdk.RGBA()
282+
if c.parse(fill_color):
283+
bright = (c.red + c.green + c.blue) / 3.0
284+
if bright < 0.5:
285+
col = 'white'
286+
else:
287+
col = 'black'
271288
else:
272289
col = 'black'
290+
273291
tagcolor = buf.create_tag(foreground=col)
274292

275293
# Add label, equation and result
@@ -374,23 +392,29 @@ def __init__(self, handle):
374392
self.KEYMAP['divide'] = self.ml.div_sym
375393
self.KEYMAP['equal'] = self.ml.equ_sym
376394

377-
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
395+
self.clipboard = Gdk.Display.get_default().get_clipboard()
378396
self.select_reason = self.SELECT_SELECT
379397
self.buffer = ""
380398
self.showing_version = 0
381399
self.showing_error = False
382400
self.ans_inserted = False
383401
self.show_vars = False
384402

385-
self.connect("key_press_event", self.keypress_cb)
386-
self.connect("destroy", self.cleanup_cb)
387-
self.color = sugar3.profile.get_color()
403+
# Input handling for GTK4
404+
key_controller = Gtk.EventControllerKey()
405+
key_controller.connect("key-pressed", self.keypress_cb)
406+
self.add_controller(key_controller)
407+
408+
# self.connect("destroy", self.cleanup_cb) # handled by activity?
409+
410+
self.color = sugar4.profile.get_color()
388411

389412
self.layout = CalcLayout(self)
390413
self.label_entry = self.layout.label_entry
391414
self.text_entry = self.layout.text_entry
392415
self.last_eq_sig = None
393416
self.last_eqn_textview = None
417+
self.last_eqn_controller = None
394418

395419
self.reset()
396420
self.layout.show_it()
@@ -792,67 +816,66 @@ def expand_selection(self, dir):
792816

793817
def text_copy(self):
794818
if self.layout.graph_selected is not None:
795-
self.clipboard.set_image(
796-
self.layout.graph_selected.get_child().get_pixbuf())
797-
self.layout.toggle_select_graph(self.layout.graph_selected)
819+
# TODO: Port image copying for GTK4 (requires Texture)
820+
pass
821+
# texture = Gdk.Texture.new_for_pixbuf(self.layout.graph_selected.get_child().get_pixbuf())
822+
# self.clipboard.set_texture(texture)
823+
# self.layout.toggle_select_graph(self.layout.graph_selected)
798824
else:
799825
str = self.text_entry.get_text()
800826
sel = self.text_entry.get_selection_bounds()
801827
# _logger.info('text_copy, sel: %r, str: %s', sel, str)
802828
if len(sel) == 2:
803829
(start, end) = sel
804-
self.clipboard.set_text(str[start:end], -1)
830+
self.clipboard.set_text(str[start:end])
805831

806832
def text_select_all(self):
807833
end = self.text_entry.get_text_length()
808834
self.text_entry.select_region(0, end)
809835

810-
def get_clipboard_text(self):
811-
text = self.clipboard.wait_for_text()
812-
if text is None:
813-
return ""
814-
else:
815-
return text
816-
817836
def text_paste(self):
818-
self.button_pressed(self.TYPE_TEXT, self.get_clipboard_text())
837+
self.clipboard.read_text_async(None, self._on_paste_text_received, None)
838+
839+
def _on_paste_text_received(self, clipboard, result, user_data):
840+
try:
841+
text = clipboard.read_text_finish(result)
842+
if text:
843+
self.button_pressed(self.TYPE_TEXT, text)
844+
except Exception as e:
845+
_logger.error(f"Error pasting text: {e}")
819846

820847
def text_cut(self):
821848
self.text_copy()
822849
self.remove_character(1)
823850

824-
def keypress_cb(self, widget, event):
825-
if not self.text_entry.is_focus():
826-
return
827-
828-
key = Gdk.keyval_name(event.keyval)
829-
if event.hardware_keycode == 219:
830-
if (event.get_state() & Gdk.ModifierType.SHIFT_MASK):
831-
key = 'divide'
851+
def keypress_cb(self, controller, keyval, keycode, state):
852+
keyname = Gdk.keyval_name(keyval)
853+
854+
is_ctrl = (state & Gdk.ModifierType.CONTROL_MASK)
855+
is_shift = (state & Gdk.ModifierType.SHIFT_MASK) and not is_ctrl
856+
857+
if is_ctrl:
858+
if keyname in self.CTRL_KEYMAP:
859+
self.CTRL_KEYMAP[keyname](self)
860+
return True
861+
elif is_shift:
862+
if keyname in self.SHIFT_KEYMAP:
863+
self.SHIFT_KEYMAP[keyname](self)
864+
return True
865+
866+
if "KP_" in keyname:
867+
if keyname == "KP_Enter":
868+
keyname = "Return"
869+
870+
if keyname in self.KEYMAP:
871+
action = self.KEYMAP[keyname]
872+
if callable(action):
873+
action(self)
832874
else:
833-
key = 'multiply'
834-
_logger.debug('Key: %s (%r, %r)', key,
835-
event.keyval, event.hardware_keycode)
836-
837-
if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
838-
if key in self.CTRL_KEYMAP:
839-
f = self.CTRL_KEYMAP[key]
840-
return f(self)
841-
elif (event.get_state() & Gdk.ModifierType.SHIFT_MASK) and \
842-
key in self.SHIFT_KEYMAP:
843-
f = self.SHIFT_KEYMAP[key]
844-
return f(self)
845-
elif str(key) in self.IDENTIFIER_CHARS:
846-
self.button_pressed(self.TYPE_TEXT, key)
847-
elif key in self.KEYMAP:
848-
f = self.KEYMAP[key]
849-
if isinstance(f, str) or \
850-
isinstance(f, str):
851-
self.button_pressed(self.TYPE_TEXT, f)
852-
else:
853-
return f(self)
875+
self.button_pressed(self.TYPE_TEXT, action)
876+
return True
854877

855-
return True
878+
return False
856879

857880
def get_older(self):
858881
self.showing_version = max(0, self.showing_version - 1)
@@ -973,10 +996,15 @@ def format_insert_ans(self):
973996

974997

975998
def main():
976-
win = Gtk.Window(Gtk.WindowType.TOPLEVEL)
977-
Calculate(win)
978-
Gtk.main()
979-
return 0
999+
app = Gtk.Application(application_id="org.laptop.Calculate.Test")
1000+
1001+
def activate(app):
1002+
win = Calculate(None)
1003+
win.set_application(app)
1004+
win.present()
1005+
1006+
app.connect("activate", activate)
1007+
app.run(None)
9801008

9811009

9821010
if __name__ == "__main__":

0 commit comments

Comments
 (0)