c20c886bf41fadabf2a00fc7221c293388af116e
[simple-launcher] / simple-launcher.cc
1 // This file is a part of Simple Launcher
2 //
3 // Copyright (C) 2006, 2007, Mikhail Sobolev
4 //
5 // Simple Launcher is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License version 2 as published by
7 // the Free Software Foundation.
8 //
9 // This program is distributed in the hope that it will be useful, but WITHOUT
10 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12 // more details.
13 //
14 // You should have received a copy of the GNU General Public License along with
15 // this program; if not, write to the Free Software Foundation, Inc., 51
16 // Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 #include <string>
19 #include <vector>
20 #include <fstream>
21
22 #include <dirent.h>
23
24 #include <gtk/gtk.h>
25
26 #include <libosso.h>
27
28 #include "launcher-item.h"
29 #include "launchable-item.h"
30 #include "settings-dialog.h"
31 #include "gconf-wrapper.h"
32
33 #define SL_APPLET_DBUS_NAME  "simple-launcher"
34 #define SL_APPLET_VERSION    "0.0"
35
36 #define SL_APPLET_GCONF_PATH  "/apps/simple-launcher"
37
38 // A copy of interface functions from hildon-home-plugin-interface (new hildon desktop does not have it) {{{
39
40 extern "C" {
41
42   void *hildon_home_applet_lib_initialize(void *state_data, int *state_size, GtkWidget **widget);
43   int hildon_home_applet_lib_save_state(void *applet_data, void **state_data, int *state_size);
44   void hildon_home_applet_lib_background(void *applet_data);
45   void hildon_home_applet_lib_foreground(void *applet_data);
46   void hildon_home_applet_lib_deinitialize(void *applet_data);
47   GtkWidget *hildon_home_applet_lib_settings(void *applet_data, GtkWindow *parent);
48
49 };
50
51 // }}}
52
53 class SimpleLauncherApplet {
54 public:
55   SimpleLauncherApplet(const GConfKey&);
56  ~SimpleLauncherApplet();
57
58   bool doInit(void *state_data, int *state_size);
59
60   void background() {}
61   void foreground() {}
62   int saveState(void **state_data, int *state_size);
63   GtkWidget *settings(GtkWindow *parent);
64
65   GtkWidget *getWidget() { return myWidget; }
66
67 private:
68   static void addItem(LauncherItems&, const std::string&, bool);
69
70   void loadConfig();
71   void saveConfig();
72
73   static void updateItems(LauncherItems&);
74   static void processDirectory(LauncherItems&, const std::string&);
75
76   bool initWidget();
77   void updateWidget();
78
79   void buttonPressed(GtkWidget *button, GdkEventButton *event);
80   void runDialog();
81
82   static void _button_pressed(GtkWidget *button, GdkEventButton *event, void *self);
83   static void _run_dialog(GtkMenuItem *, void *);
84
85 private:
86   // GConfClientWrapper myClient;
87   // GConfKey myMainSettings;
88
89   osso_context_t *myContext;
90
91   GtkWidget *myWidget;
92   GtkWindow *myParent;
93
94   LauncherItems myItems;
95
96   GConfBooleanOption myTransparent;
97   // bool myShowInfobanner; // FIXME: to implement
98   GConfIntegerOption myIconSize;
99
100   static char *ourDirs[];
101 };
102
103 // Hildon home applet interface functions
104
105 void *hildon_home_applet_lib_initialize(void *state_data, int *state_size, GtkWidget **widget) {
106   GConfKey baseKey(SL_APPLET_GCONF_PATH);
107
108   SimpleLauncherApplet *applet = new SimpleLauncherApplet(baseKey);
109
110   if (applet != NULL) {
111     if (applet->doInit(state_data, state_size)) {
112       *widget = applet->getWidget();
113     } else {
114       delete applet;
115       applet = NULL;
116     }
117   }
118
119   return (void*)applet;
120 }
121
122 void hildon_home_applet_lib_deinitialize(void *applet_data) {
123   SimpleLauncherApplet *applet = (SimpleLauncherApplet *)applet_data;
124
125   delete applet;
126 }
127
128 void hildon_home_applet_lib_background(void *applet_data) {
129   ((SimpleLauncherApplet *)applet_data)->background();
130 }
131
132 void hildon_home_applet_lib_foreground (void *applet_data) {
133   ((SimpleLauncherApplet *)applet_data)->foreground();
134 }
135
136 GtkWidget *hildon_home_applet_lib_settings(void *applet_data, GtkWindow *parent) {
137   return ((SimpleLauncherApplet *)applet_data)->settings(parent);
138 }
139
140 int hildon_home_applet_lib_save_state (void *applet_data, void **state_data, int *state_size) {
141   return ((SimpleLauncherApplet *)applet_data)->saveState(state_data, state_size);
142 }
143
144 // SimpleLauncherApplet implementation
145
146 char *SimpleLauncherApplet::ourDirs[] = {
147   "/usr/share/applications/hildon",
148   NULL
149 };
150
151 // SimpleLauncherApplet::SimpleLauncherApplet() : myMainSettings(myClient.getKey(SL_APPLET_GCONF_PATH)), myContext(NULL), myWidget(NULL), myParent(NULL) {
152 SimpleLauncherApplet::SimpleLauncherApplet(const GConfKey& base) : myContext(NULL), myWidget(NULL), myParent(NULL), myTransparent(base, "transparent", false), myIconSize(base, "icon_size", 48) {
153 }
154
155 bool SimpleLauncherApplet::doInit(void *state_data, int *state_size) {
156   if ((myContext = osso_initialize(SL_APPLET_DBUS_NAME, SL_APPLET_VERSION, FALSE, NULL)) == NULL) {
157     g_debug("sla-applet: failed to initialize the osso layer");
158     return false;
159   }
160
161   loadConfig();
162
163   if (!initWidget()) {
164     return false;
165   }
166
167   return true;
168 }
169
170 SimpleLauncherApplet::~SimpleLauncherApplet() {
171   myItems.clear();
172 #if 0
173   // This does not seem to be necessary
174   if (myWidget != NULL) {
175     gtk_widget_destroy(myWidget);
176     myWidget = NULL;
177   }
178 #endif
179   if (myContext != NULL) {
180     osso_deinitialize(myContext);
181     myContext = NULL;
182   }
183 }
184
185 void SimpleLauncherApplet::addItem(LauncherItems& items, const std::string& name, bool enabled) {
186   if (!items.exists(name)) {
187     LaunchableItem *item = new LaunchableItem();
188
189     item->load(name);
190
191     if (enabled) {
192       item->enable();
193     } else {
194       item->disable();
195     }
196
197     items.add(name, item);
198   }
199 }
200
201 // {{{ Configuration file managment
202 static const gchar *getConfigFileName() {
203   static gchar *configFileName = NULL;
204
205   if (configFileName == NULL) {
206     configFileName = g_build_filename(g_get_home_dir(), ".slarc", NULL);
207   }
208
209   return configFileName;
210 }
211
212 void SimpleLauncherApplet::loadConfig() {
213   std::ifstream config(getConfigFileName());
214
215   if (config) {
216     char *buffer = new char [1024];
217
218     while (config.getline(buffer, 1024)) {
219       char *p = strchr(buffer, ',');
220
221       if (p != NULL) {
222         *p++ = '\0';
223       }
224
225       addItem(myItems, buffer, (p != NULL && (*p == '1' || *p == 'y' || *p == 'Y')));
226     }
227
228     delete [] buffer;
229   }
230 }
231
232 void SimpleLauncherApplet::saveConfig() {
233   // TODO: make saving config an atomic operation
234   std::ofstream config(getConfigFileName());
235
236   if (config) {
237     for (size_t i = 0 ; i < myItems.size() ; ++i) {
238       config << myItems.name(i) << ',' << myItems[i]->isEnabled() << std::endl;
239     }
240   }
241 }
242
243 // }}}
244
245 void SimpleLauncherApplet::updateItems(LauncherItems& items) {
246   for (int i = 0 ; ourDirs[i] != NULL ; ++i) {
247     processDirectory(items, ourDirs[i]);
248   }
249 }
250
251 void SimpleLauncherApplet::processDirectory(LauncherItems& items, const std::string& dirname) {
252   DIR *dir = opendir(dirname.c_str());
253
254   if (dir != NULL) {
255     const std::string namePrefix = dirname + "/";
256     std::string shortName;
257     std::string desktopExtension = ".desktop";
258     const dirent *file;
259
260     while ((file = readdir(dir)) != 0) {
261       shortName = file->d_name;
262       if ((shortName == ".") || (shortName == "..")) {
263         continue;
264       }
265
266       if ((shortName.length() >= desktopExtension.length()) && (shortName.compare(shortName.length() - desktopExtension.length(), desktopExtension.length(), desktopExtension) == 0)) {
267         addItem(items, namePrefix+shortName, false);
268       }
269     }
270
271     closedir(dir);
272   }
273 }
274
275 bool SimpleLauncherApplet::initWidget() {
276   myWidget = gtk_hbox_new(false, 2);
277
278   if (myWidget != NULL) {
279     updateWidget();
280   }
281
282   return myWidget != NULL;
283 }
284
285 void SimpleLauncherApplet::updateWidget() {
286   gtk_container_foreach(GTK_CONTAINER(myWidget), (GtkCallback)gtk_widget_destroy, NULL);
287
288   GtkSizeGroup *group = gtk_size_group_new(GTK_SIZE_GROUP_BOTH);
289
290   int button_no = 0;
291
292   for (size_t i = 0 ; i < myItems.size() ; ++i) {
293     LauncherItem *item = myItems[i];
294
295     if (item != NULL && item->isEnabled()) {
296       GtkWidget *button = gtk_event_box_new();
297
298       gtk_widget_set_events(button, GDK_BUTTON_PRESS_MASK);
299       g_signal_connect(button, "button-press-event", G_CALLBACK(_button_pressed), this);
300
301       gtk_event_box_set_visible_window(GTK_EVENT_BOX(button), !myTransparent.value());
302
303       {
304         GdkPixbuf *pixbuf = item->getIcon(myIconSize.value());
305         gtk_container_add(GTK_CONTAINER(button), gtk_image_new_from_pixbuf(pixbuf));
306         g_object_unref(G_OBJECT(pixbuf));
307       }
308
309       gtk_object_set_user_data(GTK_OBJECT(button), item);
310
311       gtk_size_group_add_widget(group, button);
312
313       gtk_box_pack_start(GTK_BOX(myWidget), GTK_WIDGET(button), false, false, 0);
314
315       ++button_no;
316     }
317   }
318
319   g_object_unref(G_OBJECT(group));
320
321   int totalSize = myIconSize.value();
322
323   if (button_no == 0) {
324     gtk_widget_set_size_request(myWidget, totalSize, totalSize);
325   } else {
326     gtk_widget_set_size_request(myWidget, button_no*totalSize, totalSize);
327   }
328
329   gtk_widget_show_all(myWidget);
330 }
331
332 void SimpleLauncherApplet::_button_pressed(GtkWidget *button, GdkEventButton *event, void *self) {
333   ((SimpleLauncherApplet *)self)->buttonPressed(button, event);
334 }
335
336 void SimpleLauncherApplet::buttonPressed(GtkWidget *button, GdkEventButton *event) {
337   if (button != NULL && event->button == 1) {
338     LaunchableItem *item = (LaunchableItem *)gtk_object_get_user_data(GTK_OBJECT(button));
339
340     if (item != NULL) {
341       item->activate(myContext);
342     }
343   }
344 }
345
346 int SimpleLauncherApplet::saveState(void **state_data, int *state_size) {
347   if (state_data != NULL) {
348     *state_data = NULL;
349   }
350
351   if (state_size != NULL) {
352     *state_size = 0;
353   }
354
355   return 1;
356 }
357
358 GtkWidget *SimpleLauncherApplet::settings(GtkWindow *parent) {
359   myParent = parent;  // FIXME: Ugly piece of code :(
360
361   GtkWidget *menuItem = gtk_menu_item_new_with_label("Launcher settings...");
362
363   g_signal_connect(menuItem, "activate", G_CALLBACK(_run_dialog), this);
364
365   return menuItem;
366 }
367
368 void SimpleLauncherApplet::_run_dialog(GtkMenuItem *, void *self) {
369   ((SimpleLauncherApplet *)self)->runDialog();
370 }
371
372 void SimpleLauncherApplet::runDialog() {
373   // We update the items before using them to avoid a small memory leak
374   // FIXME: deal with the situation in a better way (figure it out first :))
375   updateItems(myItems);       // User requested 'settings', let's give her the latest stuff :)
376
377   LauncherItems newItems = myItems;
378
379   // TODO: make it nicer... this code is ugly :(
380   SettingsDialog dialog(myParent, newItems, myTransparent, myIconSize);
381
382   switch (dialog.run()) {
383     case GTK_RESPONSE_OK:
384       myItems = newItems;
385       dialog.updateValues();  // FIXME: hackish :( make it better
386
387       saveConfig();   // save it immediately!
388       updateWidget();
389       break;
390
391     case GTK_RESPONSE_CANCEL:
392       break;
393
394     default:
395       ;     // FIXME: do I want to do anything in here?
396   }
397
398   // newItems.clear(); // TODO: do I really need it?
399 }
400
401 // vim:ts=2:sw=2:et