#!/usr/bin/env python # -*- coding: utf-8 -*- # WorkPanel.py por: # Cristian García # Ignacio Rodriguez # Flavio Danesse # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os import mimetypes import gi from gi.repository import Gtk from gi.repository import GObject from gi.repository import GtkSource from gi.repository import Pango from Widgets import JAMediaTerminal PATH = os.path.dirname(__file__) class WorkPanel(Gtk.Paned): """ Panel, área de trabajo. zona superior: Notebook + source view para archivos abiertos zona inferior: terminales. """ __gtype_name__ = 'WorkPanel' __gsignals__ = { 'new_select': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING, GObject.TYPE_STRING))} def __init__(self): Gtk.Paned.__init__(self, orientation=Gtk.Orientation.VERTICAL) self.notebook_sourceview = Notebook_SourceView() self.debug_notebook = DebugNotebook() self.pack1(self.notebook_sourceview, resize = True, shrink = False) self.pack2(self.debug_notebook, resize = False, shrink = True) self.show_all() self.debug_notebook.set_size_request(-1, 170) self.notebook_sourceview.connect('new_select', self.__re_emit_new_select) def set_linea(self, texto): """ Recibe la linea seleccionada en instrospeccion y y la pasa a notebook_sourceview para seleccionarla. """ self.notebook_sourceview.set_linea(texto) def __re_emit_new_select(self, widget, nombre, texto): """ Recibe nombre y contenido de archivo seleccionado en Notebook_SourceView y los envia BasePanel. """ self.emit('new_select', nombre, texto) def abrir_archivo(self, archivo): """ Abre un archivo. """ self.notebook_sourceview.abrir_archivo(archivo) class Notebook_SourceView(Gtk.Notebook): """ Notebook contenedor de sourceview para archivos abiertos. """ __gsignals__ = { 'new_select': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING, GObject.TYPE_STRING))} def __init__(self): Gtk.Notebook.__init__(self) self.show_all() self.abrir_archivo(False) self.connect('switch_page', self.__switch_page) def set_linea(self, texto): """ Recibe la linea seleccionada en instrospeccion y y la selecciona en el sourceview activo. """ scrolled = self.get_children()[self.get_current_page()] view = scrolled.get_children()[0] buffer = view.get_buffer() start = buffer.get_start_iter() end = buffer.get_end_iter() if start.get_offset() == buffer.get_char_count(): start = buffer.get_start_iter() match = start.forward_search(texto, 0, end) if match: match_start, match_end = match buffer.select_range(match_start, match_end) view.scroll_to_iter(match_end, 0.1, 1, 1, 1) def __switch_page(self, widget, widget_child, indice): """ Cuando el usuario selecciona una lengüeta en el notebook, se emite la señal 'new_select'. """ view = widget_child.get_child() buffer = view.get_buffer() archivo = view.archivo nombre = False if archivo: nombre = os.path.basename(archivo) inicio, fin = buffer.get_bounds() self.emit( 'new_select', nombre, buffer.get_text(inicio, fin, 0)) def abrir_archivo(self, archivo): """ Abre un archivo y agrega una página para él, con su código. """ paginas = self.get_children() for pagina in paginas: view = pagina.get_child() if view.archivo == archivo: return sourceview = SourceView() hbox = Gtk.HBox() label = Gtk.Label("sin título") boton = Gtk.ToolButton(Gtk.STOCK_CLOSE) hbox.pack_start(label, False, False, 0) hbox.pack_start(boton, False, False, 0) if archivo: if os.path.exists(archivo): label.set_text(os.path.basename(archivo)) sourceview.set_archivo(archivo) scroll = Gtk.ScrolledWindow() scroll.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll.add(sourceview) self.append_page(scroll, hbox) label.show() boton.show() self.show_all() class SourceView(GtkSource.View): """ Visor de código para archivos abiertos. """ def __init__(self): GtkSource.View.__init__(self) self.archivo = False self.buffer = GtkSource.Buffer() self.lenguaje_manager = GtkSource.LanguageManager() self.lenguajes = self.lenguaje_manager.get_language_ids() self.lenguajes.sort() self.lenguajes.remove(self.lenguajes[0]) self.lenguajes.insert(0, 'Texto Plano') self.set_buffer(self.buffer) self.set_insert_spaces_instead_of_tabs(True) self.set_tab_width(4) self.modify_font(Pango.FontDescription('Monospace')) completion = self.get_completion() completion.add_provider(AutoCompletado(self.buffer)) self.show_all() def set_archivo(self, archivo): """ Setea el archivo cuyo codigo debe mostrarse. """ self.archivo = archivo if self.archivo: if os.path.exists(self.archivo): texto = open(self.archivo, 'r').read() nombre = os.path.basename(self.archivo) self.__set_lenguaje(archivo) self.buffer.set_text(texto) self.buffer.begin_not_undoable_action() self.buffer.end_not_undoable_action() def get_modified(self): """ Obtiene el estado de modificación del buffer y lo devuelve """ return self.buffer.get_modified() def __set_lenguaje(self, archivo): """ Setea los colores del texto según tipo de archivo. """ encontrado = False tipo = mimetypes.guess_type(archivo)[0] for id in self.lenguajes: lenguaje = self.lenguaje_manager.get_language(id) if lenguaje and len(lenguaje.get_mime_types()): mime = lenguaje.get_mime_types()[0] if tipo == mime: self.buffer.set_highlight_syntax(True) self.buffer.set_language(lenguaje) if id == 'python': self.set_insert_spaces_instead_of_tabs(True) self.set_tab_width(4) else: self.set_insert_spaces_instead_of_tabs(False) self.set_tab_width(8) self.lenguaje = lenguaje encontrado = True break if not encontrado: self.set_highlight_syntax(False) self.buffer.set_language(None) class AutoCompletado(GObject.Object, GtkSource.CompletionProvider): __gtype_name__ = 'AutoCompletado' def __init__(self, buffer): GObject.Object.__init__(self) self.buffer = buffer def __set_imports(self, imports): """ Guarda los datos para importaciones previas, para calculos de autocompletado. """ import shelve pathin = os.path.join("/tmp", "shelvein") archivo = shelve.open(pathin) archivo["Lista"] = imports archivo.close() def __get_auto_completado(self): """ Devuelve la lista de opciones posibles para auto completar. """ import commands pathin = os.path.join("/tmp", "shelvein") pathout = os.path.join(commands.getoutput( 'python %s %s' % ( os.path.join(PATH, "gtkintrospection.py"), pathin))) lista = [] if os.path.exists(pathout): ### Obtener lista para autocompletado. import shelve archivo = shelve.open(pathout) lista = archivo["Lista"] archivo.close() return lista def do_activate_proposal(self, dato1, dato2): """ Cuando se selecciona y clickea una posible solución. """ pass def do_get_name(self, coso=None, coso2=None): """ Devuelve el nombre del último módulo al que se auto completó. """ pass def do_populate(self, context): """ Cuando se producen cambios en el buffer. Metodología para autocompletado: * Importar todos los paquetes y módulos que se están importando en el archivo sobre el cual estamos auto completando. * Hacer el auto completado propiamente dicho, trabajando sobre la línea de código que se está editando. """ ### Iterador de texto sobre el código actual. textiter = self.buffer.get_iter_at_mark(self.buffer.get_insert())# Gtk.TextIter ### indice de linea activa. indice_de_linea_activa = textiter.get_line() ### Texto de la linea activa. texto_de_linea_en_edicion = textiter.get_slice( self.buffer.get_iter_at_line(indice_de_linea_activa)) ### Si hay un punto en la línea. if "." in texto_de_linea_en_edicion: ### Si el punto está en la última palabra. if "." in texto_de_linea_en_edicion.split()[-1]: ### Auto completado se hace sobre "." if texto_de_linea_en_edicion.endswith("."): palabras = texto_de_linea_en_edicion.split() if palabras: ### Importar paquetes y modulos previos inicio = self.buffer.get_start_iter() texto = self.buffer.get_text(inicio, textiter, True) lineas = texto.splitlines() imports = [] for linea in lineas: # FIXME: Analizar mejor los casos como ''', """, etc. if "import " in linea and not linea.startswith("#") and \ not linea.startswith("\"") and not linea.startswith("'"): imports.append(linea) ### ['import os', 'import sys', 'from os import path'] ### Esto es [] si auto completado se hace antes de los imports ### Auto completado se hace sobre la última palabra palabra = palabras[-1] palabra = palabra.split("(")[-1] # Caso: class Ventana(gtk. pals = palabra.split(".")[:-1] imports.append(pals) # ['import os', 'import sys', 'from os import path', ['gtk', 'gdk', '']] ### Guardar en un archivo. self.__set_imports(imports) ### Obtener lista para autocompletado. lista = self.__get_auto_completado() opciones = [] for item in lista: opciones.append(GtkSource.CompletionItem.new(item, item, None, None)) context.add_proposals(self, opciones, True) else: # FIXME: Se está autocompletando. # Esto debe actualizar la lista de opciones disponibles, # Filtrando en la lista según el texto escrito por el usuario. text = texto_de_linea_en_edicion.split(".")[-1] else: context.add_proposals(self, [], True) else: context.add_proposals(self, [], True) class DebugNotebook(Gtk.Notebook): """ Notebook para terminales y debugs. """ def __init__(self): Gtk.Notebook.__init__(self) self.terminal = JAMediaTerminal() self.append_page(self.terminal, Gtk.Label('Terminal')) self.show_all()