|
26 | 26 | _logger = logging.getLogger('Calculate') |
27 | 27 |
|
28 | 28 | import gi |
29 | | -gi.require_version('Gtk', '3.0') |
| 29 | +gi.require_version('Gtk', '4.0') |
30 | 30 | from gi.repository import Gtk |
31 | 31 | from gi.repository import Gdk |
32 | 32 | import base64 |
33 | 33 |
|
34 | | -import sugar3.profile |
35 | | -from sugar3.graphics.xocolor import XoColor |
| 34 | +import sugar4.profile |
| 35 | +from sugar4.graphics.xocolor import XoColor |
36 | 36 |
|
37 | 37 | from shareable_activity import ShareableActivity |
38 | 38 | from layout import CalcLayout |
@@ -64,8 +64,7 @@ def findchar(text, chars, ofs=0): |
64 | 64 |
|
65 | 65 | def _textview_realize_cb(widget): |
66 | 66 | '''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") |
69 | 68 | return False |
70 | 69 |
|
71 | 70 |
|
@@ -244,32 +243,51 @@ def create_history_object(self): |
244 | 243 | return self.result.get_image() |
245 | 244 |
|
246 | 245 | 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 | + |
252 | 267 | 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) |
257 | 272 | w.connect('realize', _textview_realize_cb) |
258 | 273 | buf = w.get_buffer() |
259 | 274 |
|
260 | 275 | tagsmall = buf.create_tag(font=CalcLayout.FONT_SMALL) |
261 | 276 | tagsmallnarrow = buf.create_tag(font=CalcLayout.FONT_SMALL_NARROW) |
262 | 277 | tagbig = buf.create_tag(font=CalcLayout.FONT_BIG, |
263 | 278 | 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' |
271 | 288 | else: |
272 | 289 | col = 'black' |
| 290 | + |
273 | 291 | tagcolor = buf.create_tag(foreground=col) |
274 | 292 |
|
275 | 293 | # Add label, equation and result |
@@ -374,23 +392,29 @@ def __init__(self, handle): |
374 | 392 | self.KEYMAP['divide'] = self.ml.div_sym |
375 | 393 | self.KEYMAP['equal'] = self.ml.equ_sym |
376 | 394 |
|
377 | | - self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) |
| 395 | + self.clipboard = Gdk.Display.get_default().get_clipboard() |
378 | 396 | self.select_reason = self.SELECT_SELECT |
379 | 397 | self.buffer = "" |
380 | 398 | self.showing_version = 0 |
381 | 399 | self.showing_error = False |
382 | 400 | self.ans_inserted = False |
383 | 401 | self.show_vars = False |
384 | 402 |
|
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() |
388 | 411 |
|
389 | 412 | self.layout = CalcLayout(self) |
390 | 413 | self.label_entry = self.layout.label_entry |
391 | 414 | self.text_entry = self.layout.text_entry |
392 | 415 | self.last_eq_sig = None |
393 | 416 | self.last_eqn_textview = None |
| 417 | + self.last_eqn_controller = None |
394 | 418 |
|
395 | 419 | self.reset() |
396 | 420 | self.layout.show_it() |
@@ -792,67 +816,66 @@ def expand_selection(self, dir): |
792 | 816 |
|
793 | 817 | def text_copy(self): |
794 | 818 | 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) |
798 | 824 | else: |
799 | 825 | str = self.text_entry.get_text() |
800 | 826 | sel = self.text_entry.get_selection_bounds() |
801 | 827 | # _logger.info('text_copy, sel: %r, str: %s', sel, str) |
802 | 828 | if len(sel) == 2: |
803 | 829 | (start, end) = sel |
804 | | - self.clipboard.set_text(str[start:end], -1) |
| 830 | + self.clipboard.set_text(str[start:end]) |
805 | 831 |
|
806 | 832 | def text_select_all(self): |
807 | 833 | end = self.text_entry.get_text_length() |
808 | 834 | self.text_entry.select_region(0, end) |
809 | 835 |
|
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 | | - |
817 | 836 | 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}") |
819 | 846 |
|
820 | 847 | def text_cut(self): |
821 | 848 | self.text_copy() |
822 | 849 | self.remove_character(1) |
823 | 850 |
|
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) |
832 | 874 | 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 |
854 | 877 |
|
855 | | - return True |
| 878 | + return False |
856 | 879 |
|
857 | 880 | def get_older(self): |
858 | 881 | self.showing_version = max(0, self.showing_version - 1) |
@@ -973,10 +996,15 @@ def format_insert_ans(self): |
973 | 996 |
|
974 | 997 |
|
975 | 998 | 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) |
980 | 1008 |
|
981 | 1009 |
|
982 | 1010 | if __name__ == "__main__": |
|
0 commit comments