def compile(input_file: Gio.File, output_file_path: str) -> int: if input_file is None: utils.error(_('No input file specified')) return 1 output_file = Gio.File.new_for_commandline_arg(output_file_path) import turtlico.lib as lib project = lib.ProjectBuffer() project.load_from_file(input_file) compiler = lib.Compiler(project) code, debug_info = compiler.compile(project.code.lines) try: if output_file.query_exists(): if not output_file.delete(): utils.error(_('Failed to overwrite output file')) outs = output_file.create(Gio.FileCreateFlags.NONE, None) outs.write_all(code.encode(), None) outs.close() except GLib.Error as e: utils.error(_('Cannot save output file: {}').format(e)) return 0
def _run_btn_set_running(self, running: bool): if running: self._run_btn_img.props.icon_name = 'media-playback-stop-symbolic' self._run_btn.props.tooltip_text = _('Stop') else: self._run_btn_img.props.icon_name = 'media-playback-start-symbolic' self._run_btn.props.tooltip_text = _('Run')
def do_command_line(self, command_line): options: GLib.VariantDict = command_line.get_options_dict() input_file: Union[Gio.File, None] = None argc = len(command_line.get_arguments()) if argc == 2: input_file = Gio.File.new_for_commandline_arg( command_line.get_arguments()[1]) if not input_file.query_exists(): utils.warn(_('Input file does not exist')) elif argc > 2: utils.error(_('Please specify only one input file')) return 1 if options.contains('compile'): outf = options.lookup_value('compile', None).get_string() return cli.compile(input_file, outf) win = self.make_window() win.present() if input_file is not None: win.open_local_file(input_file) return 0
def __init__(self, scene: lib.SceneInfo, scenebuffer: lib.SceneBuffer, colors: lib.icon.CommandColorScheme): super().__init__() self.scene = scene self._scenebuffer = scenebuffer self._colors = colors self._label = Gtk.Label.new(scene.props.name) self.append(self._label) self._entry = Gtk.Entry.new() self._entry.props.visible = False self._entry.props.secondary_icon_name = 'emblem-ok-symbolic' self._entry.props.secondary_icon_tooltip_text = _('Rename') self._entry.props.primary_icon_name = 'user-trash-symbolic' self._entry.props.primary_icon_tooltip_text = _('Delete this scene') self._entry.connect('activate', self._on_entry_activate) self._entry.connect('icon-press', self._on_entry_icon_press) self.append(self._entry) self._drag_source = Gtk.DragSource.new() self._drag_source.set_actions(Gdk.DragAction.COPY) self._drag_source.connect('prepare', self._on_drag_prepare) self.add_controller(self._drag_source)
def open_local_file(self, file: Gio.File): self._notifications.clear() if file is None: return def error(e): self._notifications.add_simple( _('Cannot load project: ') + str(e), Gtk.MessageType.ERROR) self.buffer.load_from_file(None) ext = os.path.splitext(file.get_basename())[1] if ext != '.tcp': error(Exception(_('Wrong file extenstion'))) return if not file.query_exists(): error( Exception( _('File "{}" is not available').format(file.get_uri()))) return try: self.buffer.load_from_file(file) except lib.CorruptedFileException as e: error(e) except lib.MissingPluginException as e: error(e) except lib.UnavailableCommandException as e: error(e) except GLib.Error as e: error(e)
def __str__(self) -> str: if len(self.missing_commands) > 5: commands = (', '.join([ f'"{c.definition.help}"' for c in list(self.missing_commands)[:5] ]) + _(' and others')) else: commands = ', '.join( [f'"{c.definition.help}"' for c in self.missing_commands]) return _( 'Command(s) {} from plugin "{}" are present in the program. Please remove them before disabling the plugin.' ).format( # noqa: E501 commands, self.plugin.name)
def __init__(self): super().__init__(use_header_bar=sys.platform == 'linux') self.set_modal(True) self.add_button(_('Cancel'), Gtk.ResponseType.CANCEL) self.add_button(_('Done'), Gtk.ResponseType.OK) self.set_default_response(Gtk.ResponseType.OK) self.props.title = '' content = self.get_content_area() content.props.valign = Gtk.Align.CENTER content.props.vexpand = True content.props.margin_start = 10 content.props.margin_end = 10 content.props.margin_top = 10 content.props.margin_bottom = 10
def _on_plugin_folder_btn_clicked(self, btn): folder = os.path.join(GLib.get_user_data_dir(), 'turtlico/turtlico/plugins') try: if os.path.exists(folder): if not os.path.isdir(folder): raise Exception( _('Path "{}" does exist but it is not a folder.')) else: os.makedirs(folder) except Exception as e: dialog = Gtk.MessageDialog(modal=True, transient_for=self, text=str(e), buttons=Gtk.ButtonsType.OK) def callback(dialog, reposne): dialog.close() dialog.connect('response', callback) dialog.show() return Gtk.show_uri(self, f'file://{folder}', Gdk.CURRENT_TIME)
def _on_new_scene_btn_clicked(self, btn): try: self._scenebuffer.add_scene() except GLib.Error as e: self._notifications.add_simple( _('Cannot create new scene: ') + e.message, Gtk.MessageType.ERROR)
def __init__(self, version: str): super().__init__(application_id='io.gitlab.Turtlico', flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) self._main_window = None self.version = version self.settings = Gio.Settings.new('io.gitlab.Turtlico') # CLI options self.set_option_context_parameter_string('[{}]'.format(_('FILE'))) self.add_main_option('version', b'v', GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _('Show version of the application'), None) self.add_main_option( 'compile', b'c', GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _('Compile file and save ouput to FILE (overwrites if exists)'), _('FILE'))
def _update_window_title(self): project = self.buffer.props.project_file if project is None: file_name = _('Unnamed') else: file_name = os.path.splitext(project.get_basename())[0] changed = '● ' if self.buffer.props.changed else '' self.props.title = f'{changed}{file_name} - Turtlico'
def __init__(self, application: app.Application, parent: Gtk.Window): translator_credits = _('[Name of translators]') if translator_credits == '[Name of translators]': translator_credits = None super().__init__( logo_icon_name='io.gitlab.Turtlico', program_name=_('Turtlico'), version=application.version, comments=_('The easy programming tool'), website='https://turtlico.gitlab.io', license_type=Gtk.License.GPL_3_0, authors=['saytamkenorh https://gitlab.com/matyas5'], copyright='Copyright © 2018-2021 saytamkenorh', translator_credits=translator_credits, transient_for=parent, modal=True, )
def _save(self) -> bool: if self._scenebuffer.scene_name is not None: try: self._scenebuffer.save_scene() except GLib.Error as e: self._notifications.add_simple( _('Cannot save the scene: ') + e.message, Gtk.MessageType.ERROR) return False return True
def check(ok): if not ok: return def open(file: Gio.File): self.open_local_file(file) utils.filedialog(self, _('Open project'), Gtk.FileChooserAction.OPEN, [lib.MIME_TURTLICO_PROJECT_FILTER], open)
def load_scene(self, name: str): if name is None: self._clear_scene() self.emit('scene-changed') return if name.strip() == '': raise Exception('Invalid scene name') self.scene_name = name pdir: Gio.File = self._project_file.get_parent() file = pdir.get_child(f'{self._project_prefix}{name}.tcs') if not file.query_exists(): self.props.scene_width = 1280 self.props.scene_height = 720 self.scene_sprites.clear() self.emit('scene-changed') return file_dis = Gio.DataInputStream.new(file.read()) content = file_dis.read_upto('\0', 1)[0] file_dis.close() try: struct = json.loads(content) except json.decoder.JSONDecodeError: raise GLib.Error.new_literal(0, _('File syntax is corrupted'), -1) try: self.props.scene_width = struct['width'] self.props.scene_height = struct['height'] self.scene_sprites.clear() for s in struct['sprites']: sprite = SceneSprite() sprite.x = s['x'] sprite.y = s['y'] sprite.name = s['name'] sprite.id = s['id'] self.scene_sprites.append(sprite) except KeyError as e: raise GLib.Error.new_literal(0, _('Missing entry: ') + str(e), -1) self.emit('scene-changed')
def on_checked(ok): if ok: try: self._scenebuffer.load_scene(selected_scene) except GLib.Error as e: self._notifications.add_simple( _('Cannot load the scene: ') + e.message, Gtk.MessageType.ERROR) self._scenebuffer.load_scene(None) else: self._select_scene(self._scenebuffer.scene_name)
def __update(self): # Nothing opened if self._sceneview is None: self._disable_all() return buffer: lib.SceneBuffer = self._sceneview.scenebuffer sprite: lib.SceneSprite = self._sceneview.props.selection if buffer.scene_name is None: self._disable_all() return # Scene properties if sprite is None: self._id_entry.props.text = _('Scene') self._id_entry.props.sensitive = False self._reset_spinbutton(self._x_spin) self._reset_spinbutton(self._y_spin) self._reset_spinbutton(self._width_spin, True, buffer.scene_width, step=10) self._reset_spinbutton(self._height_spin, True, buffer.scene_height, step=10) self._move_up_btn.props.sensitive = False self._move_down_btn.props.sensitive = False self._delete_action.set_enabled(False) return # Sprite properties size = self._sceneview.get_sprite_info(sprite)[0] width = size.get_width() height = size.get_height() min_x = math.floor(-buffer.scene_width / 2 + width / 2) max_x = math.ceil(buffer.scene_width / 2 + width / 2) min_y = math.floor(-buffer.scene_height / 2 + height / 2) max_y = math.ceil(buffer.scene_height / 2 + height / 2) if self._id_entry.props.text != sprite.id: self._id_entry.props.text = sprite.id self._id_entry.props.sensitive = True self._reset_spinbutton(self._x_spin, True, sprite.x, min_x, max_x) self._reset_spinbutton(self._y_spin, True, sprite.y, min_y, max_y) self._reset_spinbutton(self._width_spin, False, width) self._reset_spinbutton(self._height_spin, False, height) layer = self._sceneview.scenebuffer.scene_sprites.index(sprite) self._move_up_btn.props.sensitive = layer < len( self._sceneview.scenebuffer.scene_sprites) - 1 self._move_down_btn.props.sensitive = layer > 0 self._delete_action.set_enabled(True)
def __init__(self, debug_info: lib.DebugInfo, stderr: str): super().__init__() if stderr: self.props.program_failed = True debug_info = dict(sorted(debug_info.items())) stderr_lines = stderr.splitlines() utils.debug(f'Program stderr:\n{stderr}') coord = None # Get program line trace_prefix = 'TURTLICO_TRACE:' for line in stderr_lines: if line.startswith(trace_prefix): trace = line[len(trace_prefix):].split(',') if 'IGNORE' in trace: self.props.program_failed = False # Remove TURTLICO_TRACE from error message stderr_lines = stderr_lines[1:] for t in trace: try: python_line = int(t) except Exception: pass else: t_coord = self._python_to_icons_coord( debug_info, python_line) if t_coord is not None: coord = t_coord break break if coord is None: coord_msg = _('Error occured outside of your program.') else: coord_msg = _('Error occured on line {} column {}.').format( coord[1] + 1, coord[0] + 1) self.error_message = '{}\n{}'.format( '\n'.join(stderr_lines), coord_msg) else: self.program_failed = False
def check_saved(self, callback: Callable[[bool], None], can_cancel=True): if ((self._scenebuffer.scene_name is None) or (not self._scenebuffer.props.scene_modified)): callback(True) return dialog = Gtk.MessageDialog( modal=True, transient_for=utils.get_parent_window(self), text=(_('The scene contains unsaved changed.') + '\n' + _('Would you like to save the scene?')), secondary_text=_('Otherwise the unsaved changed will be lost.'), message_type=Gtk.MessageType.QUESTION) dialog.add_buttons(_('Yes'), Gtk.ResponseType.YES, _('No'), Gtk.ResponseType.NO) if can_cancel: dialog.add_buttons(_('Cancel'), Gtk.ResponseType.CANCEL) def response(dialog, resposne): dialog.close() if resposne == Gtk.ResponseType.YES: callback(self._save()) return elif resposne == Gtk.ResponseType.NO: callback(True) return callback(False) dialog.connect('response', response) dialog.show()
def _check_saved(self, callback: Callable[[bool], None]): """Checks whether the project is saved. If it is not then it asks the user whether they want to save it. If cancel option is chosen then returns False and the caller should stop its operation. """ if not self.buffer.props.changed: callback(True) return dialog = Gtk.MessageDialog( modal=True, transient_for=self, text=(_('The program contains unsaved changed.') + '\n' + _('Would you like to save the project?')), secondary_text=_('Otherwise the unsaved changed will be lost.'), message_type=Gtk.MessageType.QUESTION) dialog.add_buttons(_('Yes'), Gtk.ResponseType.YES, _('No'), Gtk.ResponseType.NO, _('Cancel'), Gtk.ResponseType.CANCEL) def response(dialog, response): dialog.close() if response == Gtk.ResponseType.YES: self.save(callback) return elif response == Gtk.ResponseType.NO: callback(True) return callback(False) dialog.connect('response', response) dialog.show()
def save_as(self, callback: Callable[[bool], None]): def save(file: Gio.File): if file is None: callback(False) return try: ok = self.buffer.save_as(file) callback(ok) except GLib.Error: callback(False) utils.filedialog(self, _('Save project as'), Gtk.FileChooserAction.SAVE, [lib.MIME_TURTLICO_PROJECT_FILTER], save)
def _delete(self): def __delete(dialog, response: Gtk.ResponseType): dialog.close() if response != Gtk.ResponseType.YES: return try: self.scene.delete() except GLib.Error as e: err_dialog = Gtk.MessageDialog( modal=True, buttons=Gtk.ButtonsType.CANCEL, transient_for=utils.get_parent_window(self), text=_('Cannot delete the scene'), secondary_text=e.message, message_type=Gtk.MessageType.ERROR) def close(dialog, reposne): err_dialog.close() err_dialog.connect('response', close) err_dialog.show() self.unparent() dialog = Gtk.MessageDialog( modal=True, transient_for=utils.get_parent_window(self), text=_('Do you really want to permanently delete the scene "{}"?' ).format(self.scene.name), message_type=Gtk.MessageType.QUESTION) dialog.add_buttons(_('Delete'), Gtk.ResponseType.YES, _('Cancel'), Gtk.ResponseType.CANCEL) delete_btn = dialog.get_widget_for_response(Gtk.ResponseType.YES) delete_btn.get_style_context().add_class('destructive-action') dialog.connect('response', __delete) dialog.show()
def _parse_def(self): self.keyword_level += 1 if self.line_start_command.id != 'def': err = _('Functions have to start on a separate line.') self._append_line(f"{self.indentation}raise SyntaxError('{err}')") return fn_name = self._next_icon(1) block_start = self._next_icon(2) if (fn_name.definition.id == 'obj' and block_start.definition.id == ':'): self._append_line(f'{self.indentation}def {fn_name.data}()') self.x += 1 return self._append_line(f'{self.indentation}def ') return
def _on_debugging_done(self, debugger, result: DebuggingReuslt): self.debugger.dispose() self.debugger = None self._run_btn_set_running(False) if result.props.program_failed: dialog = Gtk.MessageDialog( transient_for=self, modal=True, buttons=Gtk.ButtonsType.OK, text=_('Program crashed'), secondary_text=result.props.error_message, message_type=Gtk.MessageType.ERROR) dialog.show() def close(dialog, reposne): dialog.close() dialog.connect('response', close)
def _rename(self): if self._entry.props.text.strip() == '': return try: self.scene.rename(self._entry.props.text) except GLib.Error as e: dialog = Gtk.MessageDialog( modal=True, buttons=Gtk.ButtonsType.CANCEL, transient_for=utils.get_parent_window(self), text=_('Cannot rename the scene'), secondary_text=e.message, message_type=Gtk.MessageType.ERROR) def close(dialog, reposne): dialog.close() dialog.connect('response', close) dialog.show() self.cancel_edit()
def _on_state_changed(self, obj, prop) -> bool: try: self._project.set_plugin_enabled(self.plugin.id, self.widget.switch.props.active) except lib.RemovedUsedPluginException as e: dialog = Gtk.MessageDialog(modal=True, transient_for=utils.get_parent_window( self.widget), text=_('Cannot disable the plugin'), secondary_text=str(e), message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK) def response(dialog, reposne): dialog.close() dialog.connect('response', response) dialog.show() self._set_state()
def __delete(dialog, response: Gtk.ResponseType): dialog.close() if response != Gtk.ResponseType.YES: return try: self.scene.delete() except GLib.Error as e: err_dialog = Gtk.MessageDialog( modal=True, buttons=Gtk.ButtonsType.CANCEL, transient_for=utils.get_parent_window(self), text=_('Cannot delete the scene'), secondary_text=e.message, message_type=Gtk.MessageType.ERROR) def close(dialog, reposne): err_dialog.close() err_dialog.connect('response', close) err_dialog.show() self.unparent()
def _parse_data(self, offset=0, toplevel=True) -> Union[str, None]: next_icon = self._next_icon(offset) if next_icon is None: return None if (next_icon.definition.command_type == lib.CommandType.LITERAL_CONST ): return next_icon.definition.function if next_icon.data is not None: if (next_icon.definition.command_type == lib.CommandType.LITERAL): code, modules = next_icon.definition.function( next_icon.data, toplevel) assert isinstance(modules, tuple) self.modules_to_load.update( dict(zip(modules, [None] * len(modules)))) return code elif (next_icon.definition.command_type != lib.CommandType.INTERNAL): raise Exception( _('Command {} can not have any data (has "{}")').format( next_icon.definition, next_icon.data)) return None
def _on_sidebar_list_box_row_activated(self, box, row: Gtk.ListBoxRow): if row == self._sidebar_project_props_row: dialog = windows.ProjectPropsWindow(self.props.project_buffer, self.props.window) dialog.show() if row == self._sidebar_scene_editor_row: if self.props.project_buffer.props.project_file is None: self.props.window.remove_app_notifications() self.props.window.add_notification( _('Please save the project before using scenes')) else: if self._scene_editor is not None: self._scene_editor.present_with_time(Gdk.CURRENT_TIME) return self._scene_editor = windows.SceneEditorWindow( self.props.project_buffer, self.props.window, self._colors) def clear(win): self._scene_editor = None self._scene_editor.connect('unmap', clear) self._scene_editor.show()
def run(self): if self.props.running: return # Sets something to subprocpid in order to prevent # from starting two threads at once self.subprocpid_lock.acquire() self.subprocpid = -1 self.subprocpid_lock.release() self.props.running = True # The child program is run as a separate process due to safety reasons launcher = _launcher.format(self.path) if self.props.use_idle: launcher += _idle_exit.format( _('Press enter to close this window')) args = [_get_python()] if self.props.use_idle: args.extend(['-m', 'idlelib', '-t', 'Turtlico']) args.extend(['-c', launcher]) thread = threading.Thread( target=self._run_child, args=[args], daemon=True) thread.start()