Commit 968c81bf authored by Jasper St. Pierre's avatar Jasper St. Pierre

Add our own custom launcher editor

Replace simple uses of gnome-desktop-item-edit with our own desktop
file editor. For now, this only supports launcher entries -- menu
directories are not supported yet.
parent 1443592c
# -*- coding: utf-8 -*-
# Alacarte Menu Editor - Simple fd.o Compliant Menu Editor
# Copyright (C) 2013 Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import gettext
import os
from gi.repository import GLib, Gtk
from Alacarte import config, util
from gi._glib import GError
_ = gettext.gettext
EXTENSIONS = (".png", ".xpm", ".svg")
def try_icon_name(filename):
# Detect if the user picked an icon, and make
# it into an icon name.
if not filename.endswith(EXTENSIONS):
return filename
filename = filename[:-4]
theme = Gtk.IconTheme.get_default()
resolved_path = None
for path in theme.get_search_path():
if filename.startswith(path):
resolved_path = filename[len(path):].lstrip(os.sep)
break
if resolved_path is None:
return filename
parts = resolved_path.split(os.sep)
# icon-theme/size/category/icon
if len(parts) != 4:
return filename
return parts[3]
def get_icon_string(image):
filename = image.props.file
if filename is not None:
return try_icon_name(filename)
return image.props.icon_name
def strip_extensions(icon):
if icon.endswith(EXTENSIONS):
return icon[:-4]
else:
return icon
def set_icon_string(image, icon):
if GLib.path_is_absolute(icon):
image.props.file = icon
else:
image.props.icon_name = strip_extensions(icon)
DESKTOP_GROUP = GLib.KEY_FILE_DESKTOP_GROUP
class LauncherEditor(object):
def __init__(self, item_path):
self.builder = Gtk.Builder()
self.builder.add_from_file('data/launcher-editor.ui')
self.dialog = self.builder.get_object('launcher-editor')
self.dialog.connect('response', self.on_response)
self.builder.get_object('icon-button').connect('clicked', self.pick_icon)
self.builder.get_object('exec-browse').connect('clicked', self.pick_exec)
self.item_path = item_path
self.load()
def load(self):
self.keyfile = GLib.KeyFile()
try:
self.keyfile.load_from_file(self.item_path, util.KEY_FILE_FLAGS)
except IOError:
return
def set_text(ctl, name):
try:
val = self.keyfile.get_string(DESKTOP_GROUP, name)
except GError:
pass
else:
self.builder.get_object(ctl).set_text(val)
def set_check(ctl, name):
try:
val = self.keyfile.get_boolean(DESKTOP_GROUP, name)
except GError:
pass
else:
self.builder.get_object(ctl).set_active(val)
def set_icon(ctl, name):
try:
val = self.keyfile.get_string(DESKTOP_GROUP, name)
except GError:
pass
else:
set_icon_string(self.builder.get_object(ctl), val)
set_text('name-entry', "Name")
set_text('exec-entry', "Exec")
set_text('comment-entry', "Comment")
set_check('terminal-check', "Terminal")
set_icon('icon-image', "Icon")
def run(self):
self.dialog.present()
def save(self):
params = dict(Name=self.builder.get_object('name-entry').get_text(),
Exec=self.builder.get_object('exec-entry').get_text(),
Comment=self.builder.get_object('comment-entry').get_text(),
Terminal=self.builder.get_object('terminal-check').get_active(),
Icon=get_icon_string(self.builder.get_object('icon-image')))
util.fillKeyFile(self.keyfile, params)
contents, length = self.keyfile.to_data()
with open(self.item_path, 'w') as f:
f.write(contents)
def on_response(self, dialog, response):
if response == Gtk.ResponseType.OK:
self.save()
self.dialog.destroy()
def pick_icon(self, button):
chooser = Gtk.FileChooserDialog(title=_("Choose an icon"),
parent=self.dialog,
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT))
response = chooser.run()
if response == Gtk.ResponseType.ACCEPT:
self.builder.get_object('icon-image').props.file = chooser.get_filename()
chooser.destroy()
def pick_exec(self, button):
chooser = Gtk.FileChooserDialog(title=_("Choose a command"),
parent=self.dialog,
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT))
response = chooser.run()
if response == Gtk.ResponseType.ACCEPT:
self.builder.get_object('exec-entry').set_text(chooser.get_filename())
chooser.destroy()
def test():
import sys
Gtk.Window.set_default_icon_name('alacarte')
editor = LauncherEditor(sys.argv[1])
editor.dialog.connect('destroy', Gtk.main_quit)
editor.run()
Gtk.main()
if __name__ == "__main__":
test()
......@@ -30,6 +30,7 @@ gettext.textdomain(config.GETTEXT_PACKAGE)
_ = gettext.gettext
from Alacarte.MenuEditor import MenuEditor
from Alacarte.ItemEditor import LauncherEditor
from Alacarte import util
class MainWindow(object):
......@@ -252,13 +253,6 @@ class MainWindow(object):
return False
return True
#this callback keeps you from editing the same item twice
def waitForEditProcess(self, process, file_path):
if process.poll() is not None:
self.edit_pool.remove(file_path)
return False
return True
def on_new_menu_button_clicked(self, button):
menu_tree = self.tree.get_object('menu_tree')
menus, iter = menu_tree.get_selection().get_selected()
......@@ -329,10 +323,8 @@ class MainWindow(object):
if not os.path.isfile(file_path):
shutil.copy(item.get_desktop_file_path(), file_path)
if file_path not in self.edit_pool:
self.edit_pool.append(file_path)
process = subprocess.Popen(['gnome-desktop-item-edit', file_path], env=os.environ)
GObject.timeout_add(100, self.waitForEditProcess, process, file_path)
editor = LauncherEditor(file_path)
editor.run()
def on_menu_tree_cursor_changed(self, treeview):
selection = treeview.get_selection()
......
## Process this file with automake to produce Makefile.in
appdir = $(pythondir)/Alacarte
app_PYTHON = __init__.py MainWindow.py MenuEditor.py util.py
app_PYTHON = __init__.py MainWindow.py MenuEditor.py ItemEditor.py util.py
nodist_app_PYTHON = config.py
config.py: config.py.in
......
......@@ -3,10 +3,10 @@ SUBDIRS = icons
@INTLTOOL_DESKTOP_RULE@
desktopdir = $(datadir)/applications
desktop_in_files = alacarte.desktop.in
ndesktop_in_files = alacarte.desktop.in
desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
pkgdata_DATA = alacarte.ui
pkgdata_DATA = alacarte.ui launcher-editor.ui
CLEANFILES = $(desktop_DATA)
......
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<object class="GtkDialog" id="launcher-editor">
<property name="can_focus">False</property>
<property name="border_width">4</property>
<property name="title" translatable="yes">Launcher Properties</property>
<property name="modal">True</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-box">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="ok">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="yalign">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkButton" id="icon-button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<child>
<object class="GtkImage" id="icon-image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pixel_size">64</property>
<property name="icon_name">gnome-panel-launcher</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">10</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Name:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Command:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Comment:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="name-entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char"></property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="command-box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkEntry" id="exec-entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char"></property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="exec-browse">
<property name="label" translatable="yes">Browse</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="comment-entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char"></property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="terminal-check">
<property name="label" translatable="yes">Launch in Terminal?</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">cancel</action-widget>
<action-widget response="-5">ok</action-widget>
</action-widgets>
</object>
</interface>
[encoding: UTF-8]
Alacarte/MainWindow.py
Alacarte/MenuEditor.py
Alacarte/ItemEditor.py
data/alacarte.desktop.in.in
[type: gettext/glade]data/alacarte.ui
[type: gettext/glade]data/launcher-editor.ui
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment