#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2006, Red Hat, Inc.
# Copyright (C) 2011, One Laptop Per Child
# Copyright (C) 2009, Tomeu Vizoso, Simon Schampijer
#
# 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 logging
import os
import os.path
from gettext import gettext as _
from gi.repository import Gtk
from gi.repository import GObject
from sugar3.activity import activity
from sugar3.graphics import style
from sugar3.graphics.combobox import ComboBox
from sugar3.graphics.icon import Icon
import new_activity
_EXCLUDE_EXTENSIONS = ('.pyc', '.pyo', '.so', '.o', '.a', '.la', '.mo', '~',
'.xo', '.tar', '.bz2', '.zip', '.gz')
_EXCLUDE_NAMES = ['.deps', '.libs']
try:
activities_path = os.environ['SUGAR_ACTIVITIES_PATH']
except KeyError:
activities_path = os.path.join(os.path.expanduser("~"), "Activities")
class TabLabel(Gtk.HBox):
__gtype_name__ = 'BrowseTabLabel'
__gsignals__ = {
'tab-close': (GObject.SignalFlags.RUN_FIRST,
None,
([object])),
}
def __init__(self, child, label=""):
GObject.GObject.__init__(self)
self._child = child
self._label = Gtk.Label(label=label)
self._label.set_alignment(0, 0.5)
self.pack_start(self._label, True, True, 0)
self._label.show()
#self.modify_base(Gtk.StateType.NORMAL, Gdk.Color(0, 0, 0, 1))
close_tab_icon = Icon(icon_name='close-tab')
button = Gtk.Button()
button.props.relief = Gtk.ReliefStyle.NONE
button.props.focus_on_click = False
icon_box = Gtk.HBox()
icon_box.pack_start(close_tab_icon, True, False, 0)
button.add(icon_box)
button.connect('clicked', self.__button_clicked_cb)
button.set_name('browse-tab-close')
self.pack_start(button, False, True, 0)
close_tab_icon.show()
icon_box.show()
button.show()
self._close_button = button
def set_text(self, title):
self._label.set_text(title)
def update_size(self, size):
self.set_size_request(size, -1)
def hide_close_button(self):
self._close_button.hide()
def show_close_button(self):
self._close_button.show()
def __button_clicked_cb(self, button):
self.emit('tab-close', self._child)
class WelcomePage(Gtk.EventBox):
__gsignals__ = {
'open-activity': (GObject.SignalFlags.RUN_FIRST,
None,
([str])),
'show-alert': (GObject.SignalFlags.RUN_FIRST,
None,
([str])),
}
def __init__(self):
Gtk.EventBox.__init__(self)
vbox_outer = Gtk.VBox()
vbox = Gtk.VBox()
edit_label = Gtk.Label(
_(''
'Edit an installed activity\n\n'
'You can modify an 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)
self.modify_bg(Gtk.StateType.NORMAL,
style.COLOR_WHITE.get_gdk_color())
vbox_outer.pack_start(vbox, expand=True, fill=False, padding=0)
self.add(vbox_outer)
self.show_all()
def _load_activities_installed_combo(self, activities_combo):
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.emit('show-alert',
_('You must type the name for the new activity'))
return
if combo_skeletons.get_active() == -1:
self.emit('show-alert', _('You must select the project type'))
return
activity_name = name_entry.get_text().strip()
skel_iter = combo_skeletons.get_active_iter()
skeleton = combo_skeletons.get_model().get_value(skel_iter, 1)
activity_dir = new_activity.create_activity(activity_name,
activities_path, skeleton)
self.emit('open-activity', activity_dir)
def _pick_existing_activity(self, button, combo_activities):
if combo_activities.get_active() == -1:
self.emit('show-alert', _('You must select the activity'))
else:
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.emit('open-activity', activity_dir)
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()
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'
else:
file_name = command
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
self.emit('file-selected', 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'