Merge lp:~ganadist-gmail/gwibber/me2day-branch into lp:gwibber

Proposed by Young-Ho Cha
Status: Work in progress
Proposed branch: lp:~ganadist-gmail/gwibber/me2day-branch
Merge into: lp:gwibber
Diff against target: 671 lines (+624/-1)
5 files modified
gwibber/lib/gtk/__init__.py (+1/-1)
gwibber/lib/gtk/me2day.py (+169/-0)
gwibber/microblog/dispatcher.py (+2/-0)
gwibber/microblog/me2day.py (+258/-0)
ui/gwibber-accounts-me2day.ui (+194/-0)
To merge this branch: bzr merge lp:~ganadist-gmail/gwibber/me2day-branch
Reviewer Review Type Date Requested Status
Robert Bruce Park Disapprove
gwibber-committers Pending
Review via email: mp+29186@code.launchpad.net

Description of the change

add me2day plugin

To post a comment you must log in.
Revision history for this message
Omer Akram (om26er) wrote :

If you could base your branch on the current trunk and update it so that Me2day is added as a plugin we will be happy to ship this plugin in gwibber source.

Revision history for this message
Robert Bruce Park (robru) wrote :

Thanks for taking the time to submit this patch, unfortunately Gwibber has gone through extensive changes recently and your patch no longer applies to the latest codebase.

Unfortunately, the social network you were attempting to add will require Ubuntu Online Accounts support before we would be able to integrate the work that you've done.

Here is an example of the work that was necessary to add support for the LinkedIn social network to Ubuntu Online Accounts:

http://bazaar.launchpad.net/~online-accounts/account-plugins/trunk/revision/96

If you want your plugin to be supported in the future of Gwibber, you'll need to do something similar to this, but with the me2day details substituted in place of the linkedin ones.

review: Disapprove

Unmerged revisions

752. By Young-Ho Cha

fixed copyright owner e-mail address

751. By Young-Ho Cha

add me2day plugin

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'gwibber/lib/gtk/__init__.py'
2--- gwibber/lib/gtk/__init__.py 2010-02-16 08:07:39 +0000
3+++ gwibber/lib/gtk/__init__.py 2010-07-05 06:01:22 +0000
4@@ -1,1 +1,1 @@
5-__all__ = ["twitter", "identica", "flickr", "facebook", "friendfeed", "statusnet", "digg", "qaiku"]
6+__all__ = ["twitter", "identica", "flickr", "facebook", "friendfeed", "statusnet", "digg", "qaiku", "me2day"]
7
8=== added file 'gwibber/lib/gtk/me2day.py'
9--- gwibber/lib/gtk/me2day.py 1970-01-01 00:00:00 +0000
10+++ gwibber/lib/gtk/me2day.py 2010-07-05 06:01:22 +0000
11@@ -0,0 +1,169 @@
12+# -*- coding:utf-8 -*-
13+# Copyright (C) 2010 StudioEgo <sungdh86@gmail.com>
14+#
15+# This program is free software: you can redistribute it and/or modify
16+# it under the terms of the GNU General Public License version 2 as
17+# published by the Free Software Foundation.
18+#
19+# This program is distributed in the hope that it will be useful,
20+# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+# GNU General Public License for more details.
23+#
24+# You should have received a copy of the GNU General Public License
25+# along with this program. If not, see <http://www.gnu.org/licenses/>.
26+#
27+# Copyright (C) 2010 StudioEgo <sungdh86@gmail.com>
28+# Copyright (C) 2010 ganadist <ganadist@gmail.com>
29+#
30+# this code based with facebook.py
31+#
32+import gtk
33+import urllib
34+import webkit
35+import string
36+from gtk import Builder
37+import gwibber.microblog
38+
39+from gwibber.microblog.me2day import AKEY
40+
41+import json, urlparse, gnomekeyring, uuid
42+from gettext import gettext as _
43+
44+
45+# Me2day Application Key number and token url
46+ME2DAY_AUTH_URL = "http://me2day.net/api/get_auth_url.json?akey=" + AKEY
47+ME2DAY_TOKEN_URL = "http://me2day.net/api/get_full_auth_token.json?akey=" + AKEY + "&token="
48+
49+class AccountWidget(gtk.VBox):
50+ """AccountWidget: A widget that provides a user interface for configuring me2day accounts in Gwibber
51+ """
52+ token = ""
53+
54+ def __init__(self, account=None, dialog=None):
55+ """Creates the account pane for configuring me2day accounts """
56+ gtk.VBox.__init__(self, False, 20)
57+ self.ui = gtk.Builder()
58+ self.ui.set_translation_domain ("gwibber")
59+ self.ui.add_from_file (gwibber.resources.get_ui_asset("gwibber-accounts-me2day.ui"))
60+ self.ui.connect_signals(self)
61+ self.vbox_settings = self.ui.get_object("vbox_settings")
62+ self.pack_start(self.vbox_settings, False, False)
63+ self.vbox_settings.show_all()
64+
65+ if account:
66+ self.account = account
67+ else:
68+ self.account = {}
69+
70+ self.dialog = dialog
71+ has_secret_key = True
72+
73+ if "_id" in self.account:
74+ try:
75+ value = gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET, {"id": str("%s/%s" % (self.account["_id"], "secret_key"))})[0].secret
76+ except gnomekeyring.NoMatchError:
77+ has_secret_key = False
78+
79+ try:
80+ if has_secret_key and self.account["secret_key"] and self.account["username"]:
81+ self.ui.get_object("hbox_me2day_auth").hide()
82+ self.ui.get_object("me2day_auth_done_label").set_label(_("%s has been authorized by Me2day") % str(self.account["username"]))
83+ self.ui.get_object("hbox_me2day_auth_done").show()
84+ else:
85+ self.ui.get_object("hbox_me2day_auth_done").hide()
86+ self.ui.get_object("me2day_auth_button").modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("red"))
87+
88+
89+ if self.dialog:
90+ self.dialog.get_object('vbox_create').hide()
91+
92+ except:
93+ self.ui.get_object("hbox_me2day_auth_done").hide()
94+
95+ if self.dialog:
96+ self.dialog.get_object("vbox_create").hide()
97+ else:
98+ self.ui.get_object("hbox_me2day_auth_done").hide()
99+
100+ def on_me2day_auth_clicked(self, widget, data=None):
101+ (self.win_w, self.win_h) = self.window.get_size()
102+
103+ # Get Me2day Token
104+ authurl = urllib.urlopen(ME2DAY_AUTH_URL)
105+ tokenjson = json.load(authurl)
106+
107+ tokenurl = str(tokenjson['url'])
108+ self.token = str(tokenjson['token'])
109+
110+ web = webkit.WebView()
111+ web.load_html_string(_("<p>Please wait...</p>"), "file:///")
112+
113+ web.set_size_request(500, 400)
114+ web.open(tokenurl)
115+ web.set_zoom_level(0.6)
116+ web.connect('notify::load-status', self.on_me2day_status_changed)
117+
118+ scroll = gtk.ScrolledWindow()
119+ scroll.add(web)
120+
121+ self.pack_start(scroll, True, True, 0)
122+ self.show_all()
123+ self.ui.get_object("vbox1").hide()
124+ self.ui.get_object("expander1").hide()
125+
126+ def on_me2day_status_changed(self, web, param):
127+ progress = web.props.progress
128+ status = web.props.load_status
129+ if status == 0: # PROVISIONAL
130+ pass
131+ elif status == 1: # COMMITTED
132+ pass
133+ elif status == 2: # FINISHED
134+ progress = 0
135+ window = self.get_toplevel()
136+ focused = window.get_focus()
137+ page_source = web.get_main_frame().get_data_source().get_data()
138+
139+ # XXX
140+ #if "<meta name=\"X-ME2API-AUTH-RESULT\" content=\"accepted\" />" in page_source:
141+ if web.props.uri.startswith('http://me2day.net/api/auth_submit'):
142+ authdata = urllib.urlopen(ME2DAY_TOKEN_URL + self.token)
143+ auth = json.load(authdata)
144+
145+ self.account['username'] = str(auth['user_id'])
146+ self.account['ukey'] = str(auth['auth_token'])
147+ if not "_id" in self.account:
148+ self.account["_id"] = self.account["username"]
149+
150+ self.account["secret_key"] = ":KEYRING:%s" % \
151+ gnomekeyring.item_create_sync(
152+ gnomekeyring.get_default_keyring_sync(),
153+ gnomekeyring.ITEM_GENERIC_SECRET,
154+ "Gwibber pref: %s/%s" % (self.account["_id"], "secret_key"),
155+ {"id": str("%s/%s" % (self.account["_id"], "secret_key"))},
156+ str(self.account['ukey']), True)
157+
158+ self.ui.get_object("hbox_me2day_auth").hide()
159+ self.ui.get_object("me2day_auth_done_label").set_label(_("%s has been authorized by Me2day") % str(self.account["username"]))
160+ self.ui.get_object("hbox_me2day_auth_done").show()
161+
162+ if self.dialog:
163+ self.dialog.get_object("vbox_create").show()
164+
165+ web.hide()
166+ self.window.resize(self.win_w, self.win_h)
167+ self.ui.get_object("vbox1").show()
168+ self.ui.get_object("expander1").show()
169+ elif "<meta name=\"X-ME2API-AUTH-RESULT\" content=\"denied\" />" in page_source:
170+ gtk.gdk.threads_enter()
171+ d = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Me2day authorization failed. Please try again."))
172+
173+ if d.run(): d.destroy()
174+ gtk.gdk.threads_leave()
175+ web.hide()
176+ self.window.resize(self.win_w, self.win_h)
177+ else:
178+ pass
179+ elif status == 4: # FAILED
180+ progress = 0
181
182=== modified file 'gwibber/microblog/dispatcher.py'
183--- gwibber/microblog/dispatcher.py 2010-06-11 03:53:43 +0000
184+++ gwibber/microblog/dispatcher.py 2010-07-05 06:01:22 +0000
185@@ -5,6 +5,7 @@
186 import gobject, dbus, dbus.service, mx.DateTime
187 import twitter, identica, statusnet, flickr, facebook
188 import qaiku, friendfeed, digg
189+import me2day
190
191 import urlshorter
192 import util, util.couch
193@@ -37,6 +38,7 @@
194 "statusnet": statusnet,
195 "digg": digg,
196 "qaiku": qaiku,
197+ "me2day": me2day,
198 }
199
200 FEATURES = json.loads(GWIBBER_OPERATIONS)
201
202=== added file 'gwibber/microblog/me2day.py'
203--- gwibber/microblog/me2day.py 1970-01-01 00:00:00 +0000
204+++ gwibber/microblog/me2day.py 2010-07-05 06:01:22 +0000
205@@ -0,0 +1,258 @@
206+# -*- coding: utf-8 -*-
207+# Copyright (C) 2010 Young-Ho Cha <ganadist@gmail.com>
208+#
209+# This program is free software: you can redistribute it and/or modify
210+# it under the terms of the GNU General Public License version 2 as
211+# published by the Free Software Foundation.
212+#
213+# This program is distributed in the hope that it will be useful,
214+# but WITHOUT ANY WARRANTY; without even the implied warranty of
215+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
216+# GNU General Public License for more details.
217+#
218+# You should have received a copy of the GNU General Public License
219+# along with this program. If not, see <http://www.gnu.org/licenses/>.
220+#
221+# Copyright (C) 2010 ganadist <ganadist@gmail.com>
222+# Copyright (C) 2010 bloodevil <bloodevil4@gmail.com>
223+#
224+# this code based with twitter.py
225+#
226+
227+import network, util, htmllib
228+from util import log
229+from util import exceptions
230+from gettext import lgettext as _
231+import hashlib, random
232+log.logger.name = "me2day"
233+
234+PROTOCOL_INFO = {
235+ "name": "me2day",
236+ "version": "0.1",
237+
238+ "config": [
239+ "private:secret_key",
240+ "username",
241+ "color",
242+ "receive_enabled",
243+ "send_enabled",
244+ ],
245+
246+ "authtype": "login",
247+ "color": "#729FCF",
248+
249+ "features": [
250+ "send",
251+ "receive",
252+ "thread",
253+ #"search",
254+ "reply",
255+ "responses",
256+ "public",
257+ "like",
258+ "send_thread",
259+ "user_messages",
260+ "sinceid",
261+ ],
262+
263+ "default_streams": [
264+ "receive",
265+ "responses",
266+ ],
267+}
268+
269+URL_PREFIX = "http://me2day.net/api"
270+AKEY = "6a9f596b1e7e49769dec0c550e46de80"
271+
272+def unescape(s):
273+ p = htmllib.HTMLParser(None)
274+ p.save_bgn()
275+ p.feed(s)
276+ return p.save_end()
277+
278+class Client:
279+ def __init__(self, acct):
280+ self.account = acct
281+
282+ def _common(self, data):
283+ m = {};
284+ m["id"] = str(data["post_id"])
285+ m["protocol"] = "me2day"
286+ m["account"] = self.account["_id"]
287+ m["time"] = util.parsetime(data["pubDate"])
288+ m["text"] = unescape(data["body"])
289+ m["to_me"] = ("http://me2day.net/%s" % self.account["username"]) in data["body"]
290+ #m["sender"]["url"] = user["me2dayHome"]
291+ #m["sender"]["is_me"] = m["sender"]["nick"] == self.account["username"]
292+ #m["url"] = data["permalink"]
293+
294+ m["html"] = data["body"]
295+
296+ """
297+ m["content"] = util.linkify(data["body"],
298+ ((util.PARSE_HASH, '<a class="hash" href="gwibber:/tag?acct=%s&query=\\1">\\1</a>' % m["account"]),
299+ (util.PARSE_NICK, '<a class="nick" href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"])), escape=False)
300+ """
301+
302+ m["content"] = data["body"]
303+
304+ return m
305+
306+ def _dummy(self, data):
307+ pass
308+
309+ def _send_thread(self, data):
310+ if data['code']:
311+ raise exceptions.GwibberProtocolError("info", self.account["protocol"], self.account["username"], data["description"])
312+
313+ def _metoo(self, data):
314+ if data['code']:
315+ raise exceptions.GwibberProtocolError("info", self.account["protocol"], self.account["username"], data["description"])
316+
317+ def _message(self, data):
318+ m = self._common(data)
319+ m["source"] = data.get("source", False)
320+ if data.get("commentsCount", 0):
321+ comments = self._get("get_comments.json", parse="comments", post_id = data["post_id"], single=True)
322+ if comments:
323+ m["comments"] = comments[0]
324+ else:
325+ m["comments"] = []
326+ else:
327+ m["comments"] = []
328+
329+ if data.get("media"):
330+ media = data["media"]
331+ if "photoUrl" in media:
332+ image = {}
333+ image["url"] = media["permalink"]
334+ image["src"] = media["photoUrl"]
335+ m["images"] = [image,]
336+ else:
337+ m["images"] = []
338+
339+ user = data["user"] if "user" in data else data["author"]
340+
341+ m["sender"] = {}
342+ m["sender"]["name"] = user["nickname"]
343+ m["sender"]["nick"] = user["id"]
344+ m["sender"]["id"] = user["id"]
345+ m["sender"]["location"] = ""
346+ #m["sender"]["followers"] = user["followers_count"]
347+ m["sender"]["image"] = user["face"]
348+ m["sender"]["url"] = user["me2dayHome"]
349+ m["sender"]["is_me"] = m["sender"]["id"] == self.account["username"]
350+ m["url"] = data["permalink"]
351+
352+ return m
353+
354+ def _private(self, data):
355+ m = self._message(data)
356+ m["private"] = True
357+ return m
358+
359+ def _result(self, data):
360+ m = self._common(data)
361+
362+ if data["to_user_id"]:
363+ m["reply"] = {}
364+ m["reply"]["id"] = data["to_user_id"]
365+ m["reply"]["nick"] = data["to_user"]
366+
367+ m["sender"] = {}
368+ m["sender"]["name"] = data["from_user"]
369+ m["sender"]["nick"] = data["from_user_id"]
370+ m["sender"]["id"] = data["from_user_id"]
371+ m["sender"]["image"] = data["profile_image_url"]
372+ m["sender"]["url"] = "/".join((URL_PREFIX, m["sender"]["nick"]))
373+ m["sender"]["is_me"] = m["sender"]["id"] == self.account["username"]
374+ m["url"] = "/".join((m["sender"]["url"], "statuses", str(m["id"])))
375+ return m
376+
377+ def _comments(self, data):
378+ m = []
379+ for comment in data['comments']:
380+ author = comment["author"]
381+ m.append({
382+ "text": comment["body"],
383+ "time": util.parsetime(comment["pubDate"]),
384+ "sender": {
385+ "name" : author["nickname"],
386+ "nick" : author["id"],
387+ "id" : author["id"],
388+ "image" : author["face"],
389+ "url" : author["me2dayHome"]
390+ }
391+ })
392+ return m
393+
394+ def _get(self, path, parse="message", post=True, single=False, need_auth=True, kwargs = {}, **args):
395+ #import traceback
396+ #traceback.print_stack()
397+ url = "/".join((URL_PREFIX, path))
398+ args = args.copy()
399+ if kwargs:
400+ args.update(kwargs)
401+ args['akey'] = AKEY
402+ if need_auth:
403+ username = self.account["username"]
404+ secret_key = "full_auth_token " + self.account["secret_key"]
405+ else:
406+ username = None
407+ secret_key = None
408+
409+ data = network.Download(url, util.compact(args) or None, post,
410+ username, secret_key).get_json()
411+
412+ if isinstance(data, dict):
413+ if data.get("error", 0):
414+ if "authenticate" in data["error"]:
415+ raise exceptions.GwibberProtocolError("auth", self.account["protocol"], self.account["username"], data["error"])
416+ elif data.get("code", 0):
417+ raise exceptions.GwibberProtocolError("auth", self.account["protocol"], self.account["username"], data["message"])
418+
419+ if single: return [getattr(self, "_%s" % parse)(data)]
420+ if parse: return [getattr(self, "_%s" % parse)(m) for m in data]
421+ else: return []
422+
423+ def __call__(self, opname, **args):
424+ return getattr(self, opname)(**args)
425+
426+ def receive(self, count=util.COUNT, since=None):
427+ return self._get("get_posts/%s.json"%self.account["username"], scope = "friend[all]")
428+
429+ def user_messages(self, id=None, count=util.COUNT, since=None):
430+ return self._get("get_posts/%s.json"%id, need_auth=False)
431+
432+ def responses(self, count=util.COUNT, since=None):
433+ return self._get("get_posts/%s.json"%self.account["username"], need_auth=False)
434+
435+ """
436+ def private(self, count=util.COUNT, since=None):
437+ return self._get("direct_messages.json", "private", count=count, since_id=since)
438+ """
439+
440+ def public(self):
441+ friends = self._get("get_posts/%s.json"%self.account["username"], scope = "friend[all]")
442+ mine = self._get("get_posts/%s.json"%self.account["username"], scope = "all")
443+ return friends + mine
444+
445+ """
446+ def delete(self, message):
447+ return self._get("statuses/destroy/%s.json" % message["id"], None, post=True, do=1)
448+ """
449+
450+ def like(self, message):
451+ self._get("metoo.json", parse = "metoo", post_id=message["id"], single = True)
452+ return []
453+
454+ def send(self, message):
455+ kwargs = {
456+ 'post[body]': message,
457+ 'post[tags]': "me2gwibber",
458+ }
459+ return self._get("create_post/%s.json"%self.account["username"], single = True, kwargs = kwargs)
460+
461+ def send_thread(self, message, target):
462+ self._get("create_comment.json", post_id=target["id"], body = message, single = True, parse="send_thread")
463+ return self._get("get_posts.json", post_id = target["id"])
464
465=== added file 'ui/gwibber-accounts-me2day.ui'
466--- ui/gwibber-accounts-me2day.ui 1970-01-01 00:00:00 +0000
467+++ ui/gwibber-accounts-me2day.ui 2010-07-05 06:01:22 +0000
468@@ -0,0 +1,194 @@
469+<?xml version="1.0"?>
470+<interface>
471+ <requires lib="gtk+" version="2.16"/>
472+ <!-- interface-naming-policy toplevel-contextual -->
473+ <object class="GtkVBox" id="vbox_settings">
474+ <property name="visible">True</property>
475+ <property name="orientation">vertical</property>
476+ <property name="spacing">6</property>
477+ <child>
478+ <object class="GtkVBox" id="vbox1">
479+ <property name="visible">True</property>
480+ <property name="orientation">vertical</property>
481+ <child>
482+ <object class="GtkHBox" id="hbox_me2day_auth">
483+ <property name="visible">True</property>
484+
485+ <child>
486+ <object class="GtkButton" id="me2day_auth_button">
487+ <property name="label" translatable="yes">_Authorize</property>
488+ <property name="visible">True</property>
489+ <property name="can_focus">True</property>
490+ <property name="receives_default">True</property>
491+ <property name="use_underline">True</property>
492+ <signal name="clicked" handler="on_me2day_auth_clicked"/>
493+ </object>
494+ <packing>
495+ <property name="position">0</property>
496+ </packing>
497+ </child>
498+ <child>
499+ <object class="GtkLabel" id="me2day_auth_label">
500+ <property name="visible">True</property>
501+ <property name="label" translatable="yes">Authorize with me2day</property>
502+ </object>
503+ <packing>
504+ <property name="position">1</property>
505+ </packing>
506+ </child>
507+ </object>
508+ <packing>
509+ <property name="position">0</property>
510+ </packing>
511+ </child>
512+ <child>
513+ <object class="GtkHBox" id="hbox_me2day_auth_done">
514+ <property name="visible">True</property>
515+ <child>
516+ <object class="GtkLabel" id="me2day_auth_done_label">
517+ <property name="visible">True</property>
518+ <property name="label" translatable="yes">Me2day authorized</property>
519+ </object>
520+ <packing>
521+ <property name="position">0</property>
522+ </packing>
523+ </child>
524+ </object>
525+ <packing>
526+ <property name="position">1</property>
527+ </packing>
528+ </child>
529+ </object>
530+ <packing>
531+ <property name="position">0</property>
532+ </packing>
533+ </child>
534+ <child>
535+ <object class="GtkExpander" id="expander1">
536+ <property name="visible">True</property>
537+ <property name="can_focus">True</property>
538+ <child>
539+ <object class="GtkVBox" id="vbox_advanced">
540+ <property name="visible">True</property>
541+ <property name="orientation">vertical</property>
542+ <property name="spacing">6</property>
543+ <child>
544+ <object class="GtkLabel" id="label3">
545+ <property name="visible">True</property>
546+ <property name="label" translatable="yes">Account Status</property>
547+ <attributes>
548+ <attribute name="weight" value="bold"/>
549+ </attributes>
550+ </object>
551+ <packing>
552+ <property name="position">0</property>
553+ </packing>
554+ </child>
555+ <child>
556+ <object class="GtkTable" id="table_advanced_settings">
557+ <property name="visible">True</property>
558+ <property name="n_rows">2</property>
559+ <property name="n_columns">3</property>
560+ <property name="column_spacing">12</property>
561+ <property name="row_spacing">6</property>
562+ <child>
563+ <object class="GtkCheckButton" id="send_enabled">
564+ <property name="label" translatable="yes">_Send Messages</property>
565+ <property name="visible">True</property>
566+ <property name="can_focus">True</property>
567+ <property name="receives_default">False</property>
568+ <property name="use_underline">True</property>
569+ <property name="active">True</property>
570+ <property name="draw_indicator">True</property>
571+ </object>
572+ <packing>
573+ <property name="right_attach">3</property>
574+ <property name="top_attach">1</property>
575+ <property name="bottom_attach">2</property>
576+ <property name="x_options">GTK_FILL</property>
577+ <property name="y_options"></property>
578+ </packing>
579+ </child>
580+ <child>
581+ <object class="GtkCheckButton" id="receive_enabled">
582+ <property name="label" translatable="yes">_Receive Messages</property>
583+ <property name="visible">True</property>
584+ <property name="can_focus">True</property>
585+ <property name="receives_default">False</property>
586+ <property name="use_underline">True</property>
587+ <property name="active">True</property>
588+ <property name="draw_indicator">True</property>
589+ </object>
590+ <packing>
591+ <property name="right_attach">3</property>
592+ <property name="x_options">GTK_FILL</property>
593+ <property name="y_options"></property>
594+ </packing>
595+ </child>
596+ </object>
597+ <packing>
598+ <property name="position">1</property>
599+ </packing>
600+ </child>
601+ <child>
602+ <object class="GtkLabel" id="label2">
603+ <property name="visible">True</property>
604+ <property name="label" translatable="yes">Appearance</property>
605+ <attributes>
606+ <attribute name="weight" value="bold"/>
607+ </attributes>
608+ </object>
609+ <packing>
610+ <property name="position">2</property>
611+ </packing>
612+ </child>
613+ <child>
614+ <object class="GtkHBox" id="hbox1">
615+ <property name="visible">True</property>
616+ <property name="homogeneous">True</property>
617+ <child>
618+ <object class="GtkLabel" id="label4">
619+ <property name="visible">True</property>
620+ <property name="label" translatable="yes">Message Color:</property>
621+ </object>
622+ <packing>
623+ <property name="position">0</property>
624+ </packing>
625+ </child>
626+ <child>
627+ <object class="GtkColorButton" id="color">
628+ <property name="visible">True</property>
629+ <property name="can_focus">True</property>
630+ <property name="receives_default">True</property>
631+ <property name="color">#000000000000</property>
632+ </object>
633+ <packing>
634+ <property name="expand">False</property>
635+ <property name="position">1</property>
636+ </packing>
637+ </child>
638+ </object>
639+ <packing>
640+ <property name="position">3</property>
641+ </packing>
642+ </child>
643+ </object>
644+ </child>
645+ <child type="label">
646+ <object class="GtkLabel" id="label1">
647+ <property name="visible">True</property>
648+ <property name="label" translatable="yes">Advanced</property>
649+ <attributes>
650+ <attribute name="weight" value="bold"/>
651+ </attributes>
652+ </object>
653+ </child>
654+ </object>
655+ <packing>
656+ <property name="expand">False</property>
657+ <property name="fill">False</property>
658+ <property name="position">1</property>
659+ </packing>
660+ </child>
661+ </object>
662+</interface>
663
664=== added file 'ui/icons/breakdance/16x16/me2day.png'
665Binary files ui/icons/breakdance/16x16/me2day.png 1970-01-01 00:00:00 +0000 and ui/icons/breakdance/16x16/me2day.png 2010-07-05 06:01:22 +0000 differ
666=== added file 'ui/icons/breakdance/22x22/me2day.png'
667Binary files ui/icons/breakdance/22x22/me2day.png 1970-01-01 00:00:00 +0000 and ui/icons/breakdance/22x22/me2day.png 2010-07-05 06:01:22 +0000 differ
668=== added file 'ui/icons/breakdance/32x32/me2day.png'
669Binary files ui/icons/breakdance/32x32/me2day.png 1970-01-01 00:00:00 +0000 and ui/icons/breakdance/32x32/me2day.png 2010-07-05 06:01:22 +0000 differ
670=== added file 'ui/icons/breakdance/scalable/me2day.png'
671Binary files ui/icons/breakdance/scalable/me2day.png 1970-01-01 00:00:00 +0000 and ui/icons/breakdance/scalable/me2day.png 2010-07-05 06:01:22 +0000 differ