# 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
"""Develop Activity: A programming activity."""
import logging
import os
import os.path
import simplejson
from gettext import gettext as _
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GObject
from sugar3 import profile
from sugar3.graphics.toolbarbox import ToolbarBox
from sugar3.graphics.toolbarbox import ToolbarButton
from sugar3.graphics.radiotoolbutton import RadioToolButton
from sugar3.activity.widgets import ActivityToolbarButton
from sugar3.activity.widgets import EditToolbar
from sugar3.activity.widgets import StopButton
from sugar3.activity.bundlebuilder import XOPackager, Config, Builder
from sugar3.activity import activity
from sugar3.graphics.toolbutton import ToolButton
from sugar3.graphics.combobox import ComboBox
from sugar3.graphics.alert import ConfirmationAlert
from sugar3.graphics.alert import Alert
from sugar3.graphics import iconentry, notebook
from sugar3.graphics.icon import Icon
from sugar3.graphics import style
from sugar3.datastore import datastore
from sugar3.bundle.activitybundle import ActivityBundle
import logviewer
import sourceview_editor
S_WHERE = sourceview_editor.S_WHERE
import new_activity
from symbols_tree import SymbolsTree
DEBUG_FILTER_LEVEL = 1
SERVICE = "org.laptop.Develop"
IFACE = SERVICE
PATH = "/org/laptop/Develop"
WORKING_SOURCE_DIR = 'source'
SEARCH_ICONS = {False: {S_WHERE.selection: "search-in-selection",
S_WHERE.file: "system-search",
S_WHERE.multifile: "multi-search",
},
True: {S_WHERE.selection: "regex-in-selection",
S_WHERE.file: "regex",
S_WHERE.multifile: "multi-regex",
}}
CAP_ICONS = {False: "use-caps", True: "ignore-caps"}
REPLACE_ICONS = {False: "replace-and-find", True: "multi-replace"}
TOOLBAR_SEARCH = 2
_EXCLUDE_EXTENSIONS = ('.pyc', '.pyo', '.so', '.o', '.a', '.la', '.mo', '~',
'.xo', '.tar', '.bz2', '.zip', '.gz')
_EXCLUDE_NAMES = ['.deps', '.libs']
class Options:
def __init__(self, template=None, **kw):
if template:
self.__dict__ = template.__dict__.copy()
else:
self.__dict__ = {}
self.__dict__.update(kw)
class SearchOptions(Options):
pass
class DevelopActivity(activity.Activity):
"""Develop Activity as specified in activity.info"""
external_working_dir = False
def __init__(self, handle):
"""Set up the Develop activity."""
self._dirty = False
super(DevelopActivity, self).__init__(handle)
self.max_participants = 1
logging.info(repr(handle.get_dict()))
# Source buffer
self.editor = sourceview_editor.GtkSourceview2Editor()
self.editor.connect('tab-changed', self.__editor_tab_changed_cb)
self.editor.connect('changed', self.__editor_changed_cb)
toolbarbox = ToolbarBox()
activity_button = ActivityToolbarButton(self)
toolbarbox.toolbar.insert(activity_button, 0)
self.set_toolbar_box(toolbarbox)
edit_btn = ToolbarButton()
edit_btn.props.page = DevelopEditToolbar(self)
edit_btn.props.icon_name = 'toolbar-edit'
edit_btn.props.label = _('Edit')
toolbarbox.toolbar.insert(edit_btn, -1)
search_btn = ToolbarButton()
search_btn.props.page = DevelopSearchToolbar(self)
search_btn.props.icon_name = 'search'
search_btn.props.label = _('Search')
toolbarbox.toolbar.insert(search_btn, -1)
toolbarbox.toolbar.insert(Gtk.SeparatorToolItem(), -1)
show_files_btn = RadioToolButton()
show_files_btn.props.icon_name = 'sources'
show_files_btn.props.group = show_files_btn
show_files_btn.set_active(True)
show_files_btn.set_tooltip(_('Show source files'))
toolbarbox.toolbar.insert(show_files_btn, -1)
show_files_btn.connect('clicked', self._change_treenotebook_page, 0)
show_symbols_btn = RadioToolButton()
show_symbols_btn.props.icon_name = 'symbols'
show_symbols_btn.props.group = show_files_btn
show_symbols_btn.set_active(False)
show_symbols_btn.set_tooltip(_('Show file symbols'))
toolbarbox.toolbar.insert(show_symbols_btn, -1)
show_symbols_btn.connect('clicked', self._explore_code)
show_log_btn = RadioToolButton()
show_log_btn.props.icon_name = 'logs'
show_log_btn.props.group = show_files_btn
show_log_btn.set_active(False)
show_log_btn.set_tooltip(_('Show log files'))
toolbarbox.toolbar.insert(show_log_btn, -1)
show_log_btn.connect('clicked', self._change_treenotebook_page, 2)
toolbarbox.toolbar.insert(Gtk.SeparatorToolItem(), -1)
create_file_btn = ToolButton('text-x-generic')
create_file_btn.set_tooltip(_('Create empty file'))
toolbarbox.toolbar.insert(create_file_btn, -1)
create_file_btn.show()
create_file_btn.connect('clicked', self.__create_empty_file_cb)
erase_btn = ToolButton('erase')
erase_btn.set_tooltip(_('Remove file'))
toolbarbox.toolbar.insert(erase_btn, -1)
erase_btn.show()
erase_btn.connect('clicked', self.__remove_file_cb)
separator = Gtk.SeparatorToolItem()
separator.set_draw(False)
separator.set_expand(True)
toolbarbox.toolbar.insert(separator, -1)
stopbtn = StopButton(self)
toolbarbox.toolbar.insert(stopbtn, -1)
save_bundle_btn = ToolButton('save-as-bundle')
save_bundle_btn.set_tooltip(_('Create bundle (.xo file)'))
activity_button.get_page().insert(save_bundle_btn, -1)
save_bundle_btn.connect('clicked', self.save_bundle)
save_bundle_btn.show()
toolbarbox.show_all()
# Main layout.
hbox = Gtk.HPaned()
vbox = Gtk.VBox()
#The treeview and selected pane reflect each other.
self.numb = False
#Wait to save until first change, but save an unchanged
#backup copy when that happens.
self.save_unchanged = False
# The sidebar
sidebar = Gtk.VBox()
self.treenotebook = notebook.Notebook(can_close_tabs=False)
self.treenotebook.set_show_tabs(False)
sidebar.pack_start(self.treenotebook, True, True, 0)
self.activity_tree_view = FileViewer()
#self.activity_tree_view = ActivityTreeView()
self.treenotebook.add_page(_("Activity"), self.activity_tree_view)
self.treenotebook.set_size_request(Gdk.Screen.width() / 5, -1)
# Symbols tree
self._symbolstree = SymbolsTree()
self._symbolstree.connect('symbol-selected',
self.editor.symbol_selected_cb)
scrolled = Gtk.ScrolledWindow()
scrolled.add(self._symbolstree)
scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self.treenotebook.add_page(_('Symbols Tree'), scrolled)
hbox.pack1(sidebar, resize=True, shrink=False)
sidebar.show()
logging.info('finished check')
vbox.pack_start(self.editor, True, True, 0)
self.editor.show()
hbox.pack2(vbox, resize=True, shrink=True)
vbox.show()
self.set_canvas(hbox)
hbox.show()
logging.critical('finished initialization')
self.activity_dir = None
self.show()
if not handle.object_id or not self.metadata.get('source'):
GObject.timeout_add(10, self._show_welcome)
def _change_treenotebook_page(self, button, page):
self.treenotebook.set_current_page(page)
def _explore_code(self, btn, switch_page=True):
from ninja import introspection
text = self.editor.get_text()
path = self.editor.get_file_path()
logging.error('Analyzing %s', path)
symbols = introspection.obtain_symbols(text, filename=path)
self._symbolstree.load_symbols(symbols)
if switch_page:
self._change_treenotebook_page(None, 1)
self._symbolstree.expand_all()
def show_msg(self, text, title=""):
"""show_msg(text) shows text in a drop-down alert message.
"""
alert = ConfirmationAlert()
alert.props.title = title
alert.props.msg = text
alert.connect('response', self.alert_cb)
self.add_alert(alert)
alert.show()
def create_confirmation_alert(self, text, title=""):
alert = ConfirmationAlert()
alert.props.title = title
alert.props.msg = text
self.add_alert(alert)
return alert
def alert_cb(self, alert, response_id):
self.remove_alert(alert)
def _show_welcome(self):
"""_show_welcome: when opened without a bundle, ask open/new/cancel
"""
vbox = Gtk.VBox()
edit_label = Gtk.Label(
_(''
'Edit a installed activity\n\n'
'You can modify a activity, and if there are errors the '
'activity can stop working. If you are not sure, clone the '
'activity to have a backup.'))
edit_label.set_use_markup(True)
edit_label.set_line_wrap(True)
vbox.pack_start(edit_label, expand=False, fill=True, padding=10)
hbox_edit = Gtk.HBox()
hbox_edit.pack_start(Gtk.Label(_('Select the activity')), True,
True, 10)
activity_name_combo = ComboBox()
self._load_activities_installed_combo(activity_name_combo)
hbox_edit.pack_start(activity_name_combo, expand=False, fill=False,
padding=10)
edit_btn = Gtk.Button(_('Start'))
edit_btn.connect('clicked', self._pick_existing_activity,
activity_name_combo)
hbox_edit.pack_start(edit_btn, expand=False, fill=False,
padding=10)
align = Gtk.Alignment.new(0.5, 0.5, 0, 0)
align.add(hbox_edit)
vbox.pack_start(align, expand=False, fill=False, padding=10)
new_project_label = Gtk.Label(
_(''
'Create a new activity\n\n'
'You can create something new, '
'just select the type of project.'))
new_project_label.set_use_markup(True)
new_project_label.set_line_wrap(True)
vbox.pack_start(new_project_label, expand=False, fill=True, padding=10)
hbox_create = Gtk.HBox()
hbox_create.pack_start(Gtk.Label(_('Select the type')),
expand=False, fill=False, padding=10)
project_type_combo = ComboBox()
self._load_skeletons_combo(project_type_combo)
hbox_create.pack_start(project_type_combo, expand=False, fill=False,
padding=10)
align = Gtk.Alignment.new(0.5, 0.5, 0, 0)
align.add(hbox_create)
vbox.pack_start(align, expand=False, fill=False, padding=10)
hbox_name = Gtk.HBox()
hbox_name.pack_start(Gtk.Label(_('Name the activity')), True, True, 0)
activity_name_entry = Gtk.Entry()
hbox_name.pack_start(activity_name_entry, expand=True, fill=True,
padding=10)
create_btn = Gtk.Button(_('Start'))
create_btn.connect('clicked', self._create_new_activity,
activity_name_entry, project_type_combo)
hbox_name.pack_start(create_btn, expand=True, fill=True,
padding=10)
align = Gtk.Alignment.new(0.5, 0.5, 0, 0)
align.add(hbox_name)
vbox.pack_start(align, expand=False, fill=False, padding=10)
vbox.show_all()
self.editor.append_page(vbox, Gtk.Label(label=_('Start')))
return False
def _load_activities_installed_combo(self, activities_combo):
activities_path = os.path.join(os.path.expanduser("~"), "Activities")
for dir_name in sorted(os.listdir(activities_path)):
if dir_name.endswith('.activity'):
activity_name = dir_name[:- len('.activity')]
# search the icon
info_file_name = os.path.join(activities_path, dir_name,
'activity/activity.info')
try:
info_file = open(info_file_name, 'r')
icon_name = None
for line in info_file.readlines():
if line.strip().startswith('icon'):
icon_name = line.split()[-1]
info_file.close()
icon_file_name = None
if icon_name is not None:
icon_file_name = os.path.join(
activities_path, dir_name, 'activity',
'%s.svg' % icon_name)
activities_combo.append_item(0, activity_name,
file_name=icon_file_name)
except:
logging.error('Error trying to read information about %s',
activity_name)
def _load_skeletons_combo(self, skeletons_combo):
skeletons_path = os.path.join(activity.get_bundle_path(), 'skeletons')
for dir_name in sorted(os.listdir(skeletons_path)):
skeletons_combo.append_item(0, dir_name)
def _create_new_activity(self, button, name_entry, combo_skeletons):
"""create and open a new activity in working dir
"""
if name_entry.get_text() == '':
self._show_alert(_('You must type the name for the new activity'))
return
if combo_skeletons.get_active() == -1:
self._show_alert(_('You must select the project type'))
return
activity_name = name_entry.get_text().strip()
activities_path = os.path.join(os.path.expanduser("~"),
"Activities")
skel_iter = combo_skeletons.get_active_iter()
skeleton = combo_skeletons.get_model().get_value(skel_iter, 1)
activityDir = new_activity.create_activity(activity_name,
activities_path, skeleton)
self.first_open_activity(activityDir)
# remove the welcome tab
self.editor.remove_page(0)
def _show_alert(self, message, title=None):
alert = Alert()
if title is None:
title = _('Atention')
alert.props.title = title
alert.props.msg = message
alert.add_button(Gtk.ResponseType.OK, _('Ok'))
self.add_alert(alert)
alert.connect('response', self._alert_response_cb)
def _alert_response_cb(self, alert, response_id):
self.remove_alert(alert)
def _pick_existing_activity(self, button, combo_activities):
if combo_activities.get_active() == -1:
self._show_alert(_('You must select the activity'))
else:
activities_path = os.path.join(os.path.expanduser("~"),
"Activities")
selected = combo_activities.get_active_iter()
activity_name = combo_activities.get_model().get_value(selected, 1)
logging.error('Activity selected %s', activity_name)
activity_dir = os.path.join(activities_path,
"%s.activity" % activity_name)
self.first_open_activity(activity_dir)
# remove the welcome tab
self.editor.remove_page(0)
def open_activity(self, activity_dir):
logging.info('opening %s', activity_dir)
if not activity_dir.endswith('/'):
activity_dir = activity_dir + '/'
self.activity_dir = activity_dir
name = os.path.basename(activity_dir)
self.activity_tree_view.set_title(name)
self.metadata['title'] = 'Develop %s' % name
self.refresh_files()
self.activity_tree_view.connect('file_selected',
self.__file_selected_cb)
return name
def first_open_activity(self, activity_dir):
"""Open an activity for the first time.
Subsequently, use open_activity.
"""
self.open_activity(activity_dir)
namefilter = ActivityBundle(activity_dir).get_bundle_id()
self._log_files_viewer = logviewer.LogFilesViewer(namefilter)
self._log_files_viewer.connect('file-selected',
self.__log_file_selected_cb)
self.treenotebook.add_page(_("Log"), self._log_files_viewer)
self._set_dirty(False)
def refresh_files(self):
"""Refresh the treeview of activity files.
"""
self.bundle = ActivityBundle(self.activity_dir)
self.activity_tree_view.load_activity(self.activity_dir, self.bundle)
def load_file(self, full_path):
"""Load one activity subfile into the editor view.
"""
logging.error('load_file full_path %s', full_path)
logging.error('load_file self.activity_dir %s', self.activity_dir)
if full_path.startswith(self.activity_dir):
filename = full_path[len(self.activity_dir):]
else:
filename = full_path
full_path = os.path.join(self.activity_dir, full_path)
logging.error('load_file filename %s', filename)
self.editor.load_object(full_path, filename)
def __file_selected_cb(self, file_viewer, path):
"""User selected an item in the treeview. Load it.
"""
if self.numb:
#Choosing in the notebook selects in the list, and vice versa.
#Avoid infinite recursion.
return
if path and not os.path.isdir(path):
self.numb = True
self.load_file(path)
self.numb = False
def __log_file_selected_cb(self, log_files_viewer, path):
if not path:
return
if os.path.isdir(path):
#do not try to open folders
return
# Set buffer and scroll down
if self.editor.set_to_page_like(path):
return
self.editor.load_log_file(path, log_files_viewer)
def save_bundle(self, btn):
#create bundle
builder = XOPackager(Builder(Config(self.activity_dir, '/tmp')))
builder.package()
logging.error('Packaging %s', builder.package_path)
jobject = datastore.create()
icon_color = profile.get_color().to_string()
metadata = {
'title': '%s-%s.xo' % (builder.config.bundle_name,
builder.config.version),
'title_set_by_user': '1',
'suggested_filename': '%s-%s.xo' % (builder.config.bundle_name,
builder.config.version),
'icon-color': icon_color,
'mime_type': 'application/vnd.olpc-sugar',
'activity': self.get_bundle_id(),
'activity_id': self.get_id(),
'share-scope': activity.SCOPE_PRIVATE,
'preview': '',
'source': self.activity_dir, }
for k, v in metadata.items():
jobject.metadata[k] = v
jobject.file_path = builder.package_path
datastore.write(jobject)
jobject.destroy()
self._show_alert(_('The bundle has been saved in the journal.'),
_('Success'))
def save_source_jobject(self, activity_dir, file_path, filenames=None):
if not activity_dir:
raise NotImplementedError
# fix up datastore object
# FIXME: some of this is overkill,
# legacy from when I created a new jobject each save
jobject = self._jobject
icon_color = profile.get_color().to_string()
metadata = {
'title': self.metadata['title'],
'title_set_by_user': '1',
#'suggested_filename': '%s-%s.xo' % (builder.config.bundle_name,
# builder.config.version),
'icon-color': icon_color,
'mime_type': 'application/develop-session',
'activity': self.get_bundle_id(),
'activity_id': self.get_id(),
'share-scope': activity.SCOPE_PRIVATE,
'preview': '',
'source': activity_dir, }
for k, v in metadata.items():
jobject.metadata[k] = v # dict.update method is missing =(
dev_session_data = {}
if filenames:
dev_session_data['open_filenames'] = filenames
f = open(file_path, 'w')
try:
simplejson.dump(dev_session_data, f)
finally:
f.close()
jobject.file_path = file_path
datastore.write(jobject)
jobject.destroy()
return jobject
def write_file(self, file_path):
"""Wrap up the activity as a bundle and save it to journal.
"""
logging.error('WRITE_FILE')
if self.activity_dir is None:
return
if self.save_unchanged:
self.editor.save_all()
filenames = self.editor.get_all_filenames()
logging.debug('activity_dir %s, file_path %s, filenames %s' %
(self.activity_dir, file_path, filenames))
self._jobject = self.save_source_jobject(
self.activity_dir, file_path, filenames)
self.metadata['source'] = self.activity_dir
self._set_dirty(False)
self.save_unchanged = False
def read_file(self, file_path):
self.activity_dir = self.metadata['source']
logging.error('read_file self.activity_dir %s', self.activity_dir)
self.first_open_activity(self.activity_dir)
f = open(file_path, 'r')
try:
session_data = simplejson.load(f)
logging.error('read_file session_data %s', session_data)
for filename in session_data['open_filenames']:
if filename:
logging.info('opening : %s', filename)
self.load_file(filename)
finally:
f.close()
self._set_dirty(False)
def _set_dirty(self, dirty):
logging.debug("Setting dirty to %s; activity_dir is %s" %
(str(dirty), str(self.activity_dir)))
self._dirty = dirty
if dirty:
self.save_unchanged = True
def __editor_tab_changed_cb(self, editor, new_full_path):
if self.numb:
#avoid infinite recursion
return
self.numb = True
self.activity_tree_view.select_by_file_path(new_full_path)
logging.error('new tab %s', new_full_path)
self.numb = False
# TODO: change by a constant
if self.treenotebook.get_current_page() == 1: # symbols
GObject.idle_add(self._explore_code, None)
def __editor_changed_cb(self, editor):
logging.error('Editor text changed')
self._set_dirty(True)
def __create_empty_file_cb(self, button):
alert = Alert()
alert.props.title = _('Create new file')
alert.props.msg = _('Select the name of the file')
#HACK
alert._hbox.remove(alert._buttons_box)
alert.entry = Gtk.Entry()
alert._hbox.pack_start(alert.entry, True, True, 0)
alert._buttons_box = Gtk.HButtonBox()
alert._buttons_box.set_layout(Gtk.ButtonBoxStyle.END)
alert._buttons_box.set_spacing(style.DEFAULT_SPACING)
alert._hbox.pack_start(alert._buttons_box, True, True, 0)
icon = Icon(icon_name='dialog-cancel')
alert.add_button(Gtk.ResponseType.CANCEL, _('Cancel'), icon)
icon = Icon(icon_name='dialog-ok')
alert.add_button(Gtk.ResponseType.OK, _('Ok'), icon)
alert.show_all()
#
self.add_alert(alert)
alert.connect('response', self.__create_file_alert_cb)
def __create_file_alert_cb(self, alert, response_id):
if response_id is Gtk.ResponseType.OK:
file_name = alert.entry.get_text()
try:
path = os.path.dirname(self.editor.get_file_path())
except:
path = self.activity_dir
file_path = os.path.join(path, file_name)
with open(file_path, 'w') as new_file:
new_file.write('')
self.refresh_files()
self.editor.load_object(file_path, file_name)
self.remove_alert(alert)
def __remove_file_cb(self, button):
file_path = self.editor.get_file_path()
title = _('WARNING: The action you will do can not be reverted.')
msg = _('Do you want remove the file %s?') % file_path
alert = self.create_confirmation_alert(msg, title)
alert.show()
alert.connect('response', self.__remove_file_alert_cb, file_path)
def __remove_file_alert_cb(self, alert, response_id, file_path):
if response_id is Gtk.ResponseType.OK:
if os.path.isfile(file_path):
os.unlink(file_path)
self.refresh_files()
self.editor.close_page()
self.remove_alert(alert)
class FileViewer(Gtk.ScrolledWindow):
__gtype_name__ = 'ActivityFileViewer'
__gsignals__ = {
'file-selected': (GObject.SignalFlags.RUN_FIRST,
None,
([str])),
}
def __init__(self):
Gtk.ScrolledWindow.__init__(self)
self.props.hscrollbar_policy = Gtk.PolicyType.AUTOMATIC
self.props.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
self.set_size_request(style.GRID_CELL_SIZE * 3, -1)
self._path = None
self._initial_filename = None
self._tree_view = Gtk.TreeView()
self._tree_view.connect('cursor-changed', self.__cursor_changed_cb)
self.add(self._tree_view)
self._tree_view.show()
self._tree_view.props.headers_visible = False
selection = self._tree_view.get_selection()
selection.connect('changed', self.__selection_changed_cb)
cell = Gtk.CellRendererText()
self._column = Gtk.TreeViewColumn()
self._column.pack_start(cell, True)
self._column.add_attribute(cell, 'text', 0)
self._tree_view.append_column(self._column)
self._tree_view.set_search_column(0)
# map between file_path and iter
self._opened_files = {}
def load_activity(self, path, bundle):
self._search_initial_filename(path, bundle)
self._path = path
self._tree_view.set_model(Gtk.TreeStore(str, str))
self._model = self._tree_view.get_model()
self._add_dir_to_model(path)
def _add_dir_to_model(self, dir_path, parent=None):
for f in os.listdir(dir_path):
if f.endswith(_EXCLUDE_EXTENSIONS) or f in _EXCLUDE_NAMES:
continue
full_path = os.path.join(dir_path, f)
if os.path.isdir(full_path):
new_iter = self._model.append(parent, [f, full_path])
self._add_dir_to_model(full_path, new_iter)
else:
current_iter = self._model.append(parent, [f, full_path])
self._opened_files[full_path] = current_iter
if full_path == self._initial_filename:
selection = self._tree_view.get_selection()
selection.select_iter(current_iter)
def __selection_changed_cb(self, selection):
model, tree_iter = selection.get_selected()
if tree_iter is None:
file_path = None
else:
file_path = model.get_value(tree_iter, 1)
self.emit('file-selected', file_path)
def __cursor_changed_cb(self, tree_view):
selection = tree_view.get_selection()
store, iter_ = selection.get_selected()
if iter_ is None:
# Nothing selected. This happens at startup
return
if store.iter_has_child(iter_):
path = store.get_path(iter_)
if tree_view.row_expanded(path):
tree_view.collapse_row(path)
else:
tree_view.expand_row(path, False)
def select_by_file_path(self, file_path):
if file_path in self._opened_files:
tree_iter = self._opened_files[file_path]
tree_selection = self._tree_view.get_selection()
tree_selection.unselect_all()
tree_selection.select_iter(tree_iter)
def _search_initial_filename(self, activity_path, bundle):
command = bundle.get_command()
if self._is_web_activity(bundle):
file_name = 'index.html'
elif len(command.split(' ')) > 1:
name = command.split(' ')[1].split('.')[-1]
tmppath = command.split(' ')[1].replace('.', '/')
file_name = tmppath[0:-(len(name) + 1)] + '.py'
if file_name:
path = os.path.join(activity_path, file_name)
if os.path.exists(path):
logging.error('INITIAL_FILENAME %s', path)
self._initial_filename = path
def set_title(self, title):
self._column.set_title(title)
def _is_web_activity(self, activity_bundle):
return activity_bundle.get_command() == 'sugar-activity-web'
class DevelopEditToolbar(EditToolbar):
def __init__(self, _activity):
EditToolbar.__init__(self)
self._activity = _activity
self._activity.editor.connect('changed', self._changed_cb)
self._changed_cb(None)
self.undo.connect('clicked', self._undo_cb)
self.redo.connect('clicked', self._redo_cb)
self.copy.connect('clicked', self._copy_cb)
self.paste.connect('clicked', self._paste_cb)
# make expanded non-drawn visible separator to make
#the search stuff right-align
separator = Gtk.SeparatorToolItem()
separator.props.draw = False
separator.set_expand(True)
self.insert(separator, -1)
separator.show()
def _changed_cb(self, _buffer):
can_undo, can_redo = self._activity.editor.can_undo_redo()
self.undo.set_sensitive(can_undo)
self.redo.set_sensitive(can_redo)
def _undo_cb(self, button):
self._activity.editor.undo()
self._changed_cb(None)
def _redo_cb(self, button):
self._activity.editor.redo()
self._changed_cb(None)
def _copy_cb(self, button):
self._activity.editor.copy()
def _paste_cb(self, button):
self._activity.editor.paste()
# bad paul! this function was copied from sugar's activity.py via Write
def _add_widget(self, widget, expand=False):
tool_item = Gtk.ToolItem()
tool_item.set_expand(expand)
tool_item.add(widget)
widget.show()
self.insert(tool_item, -1)
tool_item.show()
class DevelopSearchToolbar(Gtk.Toolbar):
def __init__(self, _activity):
GObject.GObject.__init__(self)
self._activity = _activity
# setup the search options
self.s_opts = SearchOptions(
where=S_WHERE.multifile,
use_regex=False,
ignore_caps=True,
replace_all=False,
#defaults to avoid creating
#a new SearchOptions object for normal searches
#should never be changed, just make a copy like:
#SearchOptions(self.s_opts, forward=False)
forward=True,
stay=False)
self.safe_to_replace = False
self._search_entry = iconentry.IconEntry()
self._search_entry.set_icon_from_name(
iconentry.ICON_ENTRY_PRIMARY,
SEARCH_ICONS[self.s_opts.use_regex][self.s_opts.where])
self._search_entry.add_clear_button()
self._search_entry.connect('activate', self._search_entry_activated_cb)
self._search_entry.connect('changed', self._search_entry_changed_cb)
self._add_widget(self._search_entry, expand=True)
self._findprev = ToolButton('go-previous')
self._findprev.set_tooltip(_('Find previous'))
self.insert(self._findprev, -1)
self._findprev.show()
self._findprev.connect('clicked', self._findprev_cb)
self._findnext = ToolButton('go-next')
self._findnext.set_tooltip(_('Find next'))
self.insert(self._findnext, -1)
self._findnext.show()
self._findnext.connect('clicked', self._findnext_cb)
"""
self._settings = ToolButton(CAP_ICONS[self.s_opts.ignore_caps])
self._settings.set_tooltip(_('Search settings'))
self.insert(self._settings, -1)
self._settings.show()
self._settings.connect('clicked', self._settings_cb)
# Search settings menu
# This menu should attach to something else beside findnext -
#location is temporary.
palette = self._settings.get_palette()
sswo = self._set_where_options
ssho = self._set_how_options
ssco = self._set_cap_options
#TODO: move data structure to a member and the logic to a function
for name, function, options, icon in (
(_('Ignore capitalization'), ssco, True, "ignore-caps"),
(_('Match capitalization'), ssco, False, "use-caps"),
(None, None, None, None),
(_('Search in selection'), sswo, S_WHERE.selection,
"search-in-selection"),
(_('Search in current file'), sswo, S_WHERE.file,
"system-search"),
(_('Search in all open files'), sswo, S_WHERE.multifile,
"multi-search"),
(None, None, None, None),
(_('Simple search'), ssho, False, "system-search"),
(_('Advanced search'), ssho, True, "regex"),
):
if not name:
menuitem = Gtk.SeparatorMenuItem()
else:
menuitem = MenuItem(name, icon)
menuitem.connect('activate', function, options)
palette.menu.append(menuitem)
menuitem.show()
# make expanded non-drawn visible separator to make the replace
#stuff right-align
separator = Gtk.SeparatorToolItem()
separator.props.draw = False
separator.set_expand(True)
self.insert(separator, -1)
separator.show()
# replace entry
self._replace_entry = iconentry.IconEntry()
self._replace_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
'system-replace')
self._replace_entry.connect('changed', self._replace_entry_changed_cb)
self._replace_entry.add_clear_button()
self._add_widget(self._replace_entry, expand=True)
#replace button
self._replace_button = ToolButton(REPLACE_ICONS[
self.s_opts.replace_all])
self._replace_button.set_tooltip(_('Replace'))
self.insert(self._replace_button, -1)
self._replace_button.show()
self._replace_button.connect('clicked', self._replace_cb)
palette = self._replace_button.get_palette()
ssro = self._set_replace_options
#TODO: move data structure to a member and the logic to a function
for name, function, options, icon in (
(_('Replace one'), ssro, False, "replace-and-find"),
(_('Replace all'), ssro, True, "multi-replace"),
):
if not name:
menuitem = Gtk.SeparatorMenuItem()
else:
menuitem = MenuItem(name, icon)
menuitem.connect('activate', function, options)
palette.menu.append(menuitem)
menuitem.show()
"""
self._activity.editor.connect('changed', self._changed_cb)
self._activity.connect('key_press_event', self._on_key_press_event)
def _on_key_press_event(self, widget, event):
keyname = Gdk.keyval_name(event.keyval)
if "F5" <= keyname and keyname <= "F8":
if keyname == "F5":
self._go_to_search_entry_cb()
elif keyname == "F6":
self._findprev_cb()
elif keyname == "F7":
self._findnext_cb()
elif keyname == "F8":
self._replace_or_go_to_replace_entry_cb()
return True
def _go_to_search_entry_cb(self):
entry = self._search_entry
text = self._activity.editor.get_selected()
entry.grab_focus()
if text:
entry.delete_text(0, -1)
entry.insert_text(text)
entry.select_region(0, -1)
else:
entry.delete_text(0, 0)
entry.set_position(-1)
#for some reason, grab_focus doesn't work otherwise
def _replace_or_go_to_replace_entry_cb(self):
if self.safe_to_replace:
self._replace_cb()
else:
self._replace_entry.select_region(0, -1)
self._replace_entry.grab_focus()
def _reset_search_icons(self):
self._search_entry.set_icon_from_name(
iconentry.ICON_ENTRY_PRIMARY,
SEARCH_ICONS[self.s_opts.use_regex][self.s_opts.where])
self._settings.set_icon(CAP_ICONS[self.s_opts.ignore_caps])
#self._replace_button.set_icon(REPLACE_ICONS[self.s_opts.replace_all])
self._reset_replace_sensitivity()
def _reset_replace_sensitivity(self):
pass
"""
self._replace_button.set_sensitive(
self.s_opts.where == S_WHERE.selection or self.s_opts.replace_all)
"""
def _set_where_options(self, menu, option):
self.s_opts.where = option # IGNORE:W0201
self._reset_search_icons()
def _set_how_options(self, menu, option):
self.s_opts.use_regex = option # IGNORE:W0201
self._reset_search_icons()
def _set_cap_options(self, menu, option):
self.s_opts.ignore_caps = option # IGNORE:W0201
self._reset_search_icons()
def _set_replace_options(self, menu, option):
self.s_opts.replace_all = option # IGNORE:W0201
if option and self.s_opts.where == S_WHERE.multifile:
self.s_opts.where = S_WHERE.file # for safety:
#do not replace all in multifile except explicitly
self._reset_search_icons()
def _changed_cb(self, _buffer):
self._reset_replace_sensitivity()
#if self.s_opts.where == S_WHERE.selection:
# self._set_where_options(None, S_WHERE.file)
def _settings_cb(self, button):
self._set_cap_options(None, not self.s_opts.ignore_caps)
def _replace_cb(self, button=None):
pass
"""
ftext = self._search_entry.props.text
rtext = self._replace_entry.props.text
__replaced, found = self._activity.editor.replace(ftext, rtext,
self.s_opts)
if found:
self._replace_button.set_sensitive(True)
"""
def _search_entry_activated_cb(self, entry):
text = self._search_entry.props.text
if text:
self._findnext_cb(None)
def _search_entry_changed_cb(self, entry):
self.safe_to_replace = False
text = self._search_entry.props.text
if not text:
self._findprev.set_sensitive(False)
self._findnext.set_sensitive(False)
else:
self._findprev.set_sensitive(True)
self._findnext.set_sensitive(True)
if not self.s_opts.use_regex:
#do not do partial searches for regex
if self._activity.editor.find_next(text):
#no multifile, or focus gets grabbed
pass
#self._replace_button.set_sensitive(True)
def _replace_entry_changed_cb(self, entry):
if self._replace_entry.props.text:
self.safe_to_replace = True
def _findprev_cb(self, button=None):
ftext = self._search_entry.props.text
if ftext:
if self._activity.editor.find_next(ftext, direction='backward'):
pass
#self._replace_button.set_sensitive(True)
def _findnext_cb(self, button=None):
ftext = self._search_entry.props.text
if ftext:
if self._activity.editor.find_next(ftext, direction='forward'):
pass
#self._replace_button.set_sensitive(True)
# bad paul! this function was copied from sugar's activity.py via Write
def _add_widget(self, widget, expand=False):
tool_item = Gtk.ToolItem()
tool_item.set_expand(expand)
tool_item.add(widget)
widget.show()
self.insert(tool_item, -1)
tool_item.show()