Merge lp:~lallenlowe/dexter-rolodex/split-in-twain into lp:dexter-rolodex
- split-in-twain
- Merge into dexter
Proposed by
Allen Lowe
Status: | Merged |
---|---|
Merged at revision: | 288 |
Proposed branch: | lp:~lallenlowe/dexter-rolodex/split-in-twain |
Merge into: | lp:dexter-rolodex |
Diff against target: |
2697 lines (+1076/-719) 19 files modified
bin/dexter (+11/-9) bin/dexter-server (+350/-0) data/ui/dexter-main.ui (+35/-18) debian/changelog (+1/-1) dexter/SimpleGtkbuilderApp.py (+16/-14) dexter/app.py (+183/-118) dexter/backend/ddbus.py (+4/-106) dexter/backend/models.py (+0/-2) dexter/backend/serverutils.py (+173/-0) dexter/backend/utilities.py (+112/-258) dexter/enums.py (+1/-1) dexter/frontend/dialogs/merge.py (+14/-8) dexter/frontend/dialogs/new_contact.py (+88/-78) dexter/frontend/utils.py (+15/-15) dexter/frontend/widgets/entries.py (+5/-12) dexter/signaltest.py (+0/-15) org.elementary.dexterserver.service (+3/-0) po/dexter.pot (+64/-64) setup.py (+1/-0) |
To merge this branch: | bzr merge lp:~lallenlowe/dexter-rolodex/split-in-twain |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Postler Devs | Pending | ||
Review via email: mp+44303@code.launchpad.net |
Commit message
Description of the change
Split Dexter into 2 completely independent pieces, back-end server and GUI communicating over dbus.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/dexter' |
2 | --- bin/dexter 2010-12-13 20:18:13 +0000 |
3 | +++ bin/dexter 2010-12-20 23:23:53 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | -#!/usr/bin/python |
6 | +#!/usr/bin/env python |
7 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
8 | ### BEGIN LICENSE |
9 | # Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com> |
10 | @@ -20,6 +20,7 @@ |
11 | from decimal import Decimal |
12 | |
13 | import logging |
14 | +import glib |
15 | import optparse |
16 | import os |
17 | import dbus |
18 | @@ -48,9 +49,6 @@ |
19 | parser.add_option( |
20 | "-n", "--newcontact", action="store_true", dest="newcontact", |
21 | help=_("Only show the New Contact dialog, then exit")) |
22 | - parser.add_option( |
23 | - "-d", "--dbusdaemon", action="store_true", dest="dbusdaemon", |
24 | - help=_("Start Dexter in daemon mode, for use with dbus")) |
25 | (options, args) = parser.parse_args() |
26 | |
27 | # Set the logging level to show debug messages. |
28 | @@ -77,17 +75,21 @@ |
29 | files_to_import.append(arg) |
30 | |
31 | session_bus = dbus.SessionBus() |
32 | - is_running = session_bus.request_name("org.elementary.dexterservice") != \ |
33 | - dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER |
34 | + try: |
35 | + dexter_object = session_bus.get_object("org.elementary.dexterapp", |
36 | + "/org/elementary/dexterapp") |
37 | + is_running = True |
38 | + except: |
39 | + is_running = False |
40 | |
41 | if is_running: |
42 | - dexter_object = session_bus.get_object("org.elementary.dexterservice", |
43 | - "/org/elementary/dexterservice") |
44 | + dexter_object = session_bus.get_object("org.elementary.dexterserver", |
45 | + "/org/elementary/dexterserver") |
46 | logging.info("Application already running") |
47 | if options.newcontact: |
48 | method = dexter_object.get_dbus_method("NewContactDialog") |
49 | method() |
50 | - elif not options.dbusdaemon: |
51 | + else: |
52 | method = dexter_object.get_dbus_method("ShowWindow") |
53 | method() |
54 | if files_to_import: |
55 | |
56 | === added file 'bin/dexter-server' |
57 | --- bin/dexter-server 1970-01-01 00:00:00 +0000 |
58 | +++ bin/dexter-server 2010-12-20 23:23:53 +0000 |
59 | @@ -0,0 +1,350 @@ |
60 | +#!/usr/bin/env python |
61 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
62 | +### BEGIN LICENSE |
63 | +# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com> |
64 | +# This program is free software: you can redistribute it and/or modify it |
65 | +# under the terms of the GNU General Public License version 3, as published |
66 | +# by the Free Software Foundation. |
67 | +# |
68 | +# This program is distributed in the hope that it will be useful, but |
69 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
70 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
71 | +# PURPOSE. See the GNU General Public License for more details. |
72 | +# |
73 | +# You should have received a copy of the GNU General Public License along |
74 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
75 | +### END LICENSE |
76 | + |
77 | +import os |
78 | +import sys |
79 | +import logging |
80 | +import gtk |
81 | +import glib |
82 | +import dbus |
83 | +import dbus.service |
84 | +from dbus.mainloop.glib import DBusGMainLoop |
85 | + |
86 | +DBusGMainLoop(set_as_default=True) |
87 | + |
88 | +PROJECT_ROOT_DIRECTORY = os.path.abspath( |
89 | + os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))) |
90 | + |
91 | +if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'dexter')) |
92 | + and PROJECT_ROOT_DIRECTORY not in sys.path): |
93 | + sys.path.insert(0, PROJECT_ROOT_DIRECTORY) |
94 | + os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY) # for subprocesses |
95 | + |
96 | +import dexter.backend.models as models |
97 | +import dexter.backend.serverutils as utilities |
98 | + |
99 | +# this is for testing the switch to a fully modular system |
100 | +class DexterServer(dbus.service.Object): |
101 | + def __init__(self): |
102 | + |
103 | + if os.path.exists(sys.path[0]+"/data/ui/dexter-main.ui"): |
104 | + logging.info("Using data from sys.path[0] dir") |
105 | + self.datadir = sys.path[0]+"/data" |
106 | + elif os.path.exists("../data/ui/dexter-main.ui"): |
107 | + logging.info("Using data from current dir") |
108 | + self.datadir = "../data" |
109 | + elif os.path.exists("./data/ui/dexter-main.ui"): |
110 | + logging.info("Running locally") |
111 | + self.datadir = "./data" |
112 | + else: |
113 | + self.datadir = "/usr/share/dexter/" |
114 | + |
115 | + session_bus = dbus.SessionBus() |
116 | + is_running = session_bus.request_name("org.elementary.dexterserver") != \ |
117 | + dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER |
118 | + |
119 | + if is_running: |
120 | + sys.exit(0) |
121 | + else: |
122 | + bus_name = dbus.service.BusName('org.elementary.dexterserver', |
123 | + bus=dbus.SessionBus()) |
124 | + dbus.service.Object.__init__(self, bus_name, |
125 | + '/org/elementary/dexterserver') |
126 | + self.store = models.setup_db(models.DATA_DIR) |
127 | + models.create_tables(self.store) |
128 | + models.update_tables(self.store) |
129 | + |
130 | + #service methods |
131 | + @dbus.service.method('org.elementary.dexterserver', in_signature='ssssssa(ss)a(ss)aa{ss}as', out_signature='i') |
132 | + def CreateContact(self, first_name, middle_name, last_name, |
133 | + organization, birthday, photo_path, emails, |
134 | + phones, addresses, notes): |
135 | + if first_name == "None": |
136 | + first_name = None |
137 | + if middle_name == "None": |
138 | + middle_name = None |
139 | + if last_name == "None": |
140 | + last_name = None |
141 | + if organization == "None": |
142 | + organization = None |
143 | + if birthday == "None": |
144 | + birthday = None |
145 | + if photo_path == "None": |
146 | + photo_path = None |
147 | + for address in addresses: |
148 | + if address["street"] == "None": |
149 | + address["street"] = None |
150 | + if address["city"] == "None": |
151 | + address["city"] = None |
152 | + if address["state"] == "None": |
153 | + address["state"] = None |
154 | + if address["zip"] == "None": |
155 | + address["zip"] = None |
156 | + if address["type"] == "None": |
157 | + address["type"] = None |
158 | + for note in notes: |
159 | + if note.strip() == "None" or not note.strip(): |
160 | + note = None |
161 | + new_id = utilities.create_new_contact(first_name, middle_name, last_name, |
162 | + organization, birthday, photo_path, emails, |
163 | + phones, addresses, notes, self.store) |
164 | + self.ContactAdded(self.convert_to_small_dict(self.store.find(models.Contact, models.Contact.id == new_id).one())) |
165 | + return new_id |
166 | + |
167 | + @dbus.service.method('org.elementary.dexterserver') |
168 | + def DeleteContact(self, id_number): |
169 | + utilities.delete_by_id(self.store, id_number) |
170 | + self.ContactsDeleted([id_number]) |
171 | + |
172 | + @dbus.service.method('org.elementary.dexterserver') |
173 | + def DeleteContacts(self, id_numbers): |
174 | + for id_number in id_numbers: |
175 | + utilities.delete_by_id(self.store, id_number) |
176 | + self.ContactsDeleted(id_numbers) |
177 | + |
178 | + @dbus.service.method('org.elementary.dexterserver', out_signature='aa{ss}') |
179 | + def GetFullList(self): |
180 | + full_list = [] |
181 | + contacts = self.store.find(models.Contact) |
182 | + for contact in contacts: |
183 | + new = self.convert_to_small_dict(contact) |
184 | + full_list.append(new) |
185 | + return full_list |
186 | + |
187 | + @dbus.service.method('org.elementary.dexterserver', out_signature='a{ss}') |
188 | + def GetContactByID(self, id_number): |
189 | + contact = self.store.find(models.Contact, models.Contact.id == id_number).one() |
190 | + if contact: |
191 | + contact_dict = self.convert_contact_to_dict(contact) |
192 | + else: |
193 | + contact_dict = dict() |
194 | + return contact_dict |
195 | + |
196 | + @dbus.service.method('org.elementary.dexterserver', out_signature='a(ss)') |
197 | + def GetPhones(self, id_number): |
198 | + phone_list = [] |
199 | + phones = self.store.find(models.Phone, models.Phone.contact_id == id_number) |
200 | + for phone in phones: |
201 | + phone_list.append((self.none_to_string(phone.number), self.none_to_string(phone.phone_type))) |
202 | + return phone_list |
203 | + |
204 | + @dbus.service.method('org.elementary.dexterserver', out_signature='a(ss)') |
205 | + def GetEmails(self, id_number): |
206 | + email_list = [] |
207 | + emails = self.store.find(models.Email, models.Email.contact_id == id_number) |
208 | + for email in emails: |
209 | + email_list.append((self.none_to_string(email.address), self.none_to_string(email.email_type))) |
210 | + return email_list |
211 | + |
212 | + @dbus.service.method('org.elementary.dexterserver', out_signature='aa{ss}') |
213 | + def GetAddresses(self, id_number): |
214 | + address_list = [] |
215 | + addresses = self.store.find(models.Address, models.Address.contact_id == id_number) |
216 | + for address in addresses: |
217 | + adict = dict() |
218 | + adict["type"] = self.none_to_string(address.address_type) |
219 | + adict["street"] = self.none_to_string(address.street) |
220 | + adict["city"] = self.none_to_string(address.city) |
221 | + adict["state"] = self.none_to_string(address.state) |
222 | + adict["zip"] = self.none_to_string(address.zip) |
223 | + address_list.append(adict) |
224 | + return address_list |
225 | + |
226 | + @dbus.service.method('org.elementary.dexterserver', out_signature='as') |
227 | + def GetNotes(self, id_number): |
228 | + text = "" |
229 | + for note in self.store.find(models.Note, models.Note.contact_id == id_number): |
230 | + text += note.text |
231 | + return [text] |
232 | + |
233 | + @dbus.service.method('org.elementary.dexterserver') |
234 | + def GetName(self, emailstring): |
235 | + """take an email address and return the full name associated with it""" |
236 | + try: |
237 | + email = self.store.find(models.Email, |
238 | + models.Email.address == emailstring).one() |
239 | + name = email.contact.full_name |
240 | + except: |
241 | + name = "" |
242 | + return name |
243 | + |
244 | + @dbus.service.method('org.elementary.dexterserver') |
245 | + def GetEmailAddresses(self, full_name): |
246 | + """take a full name and return a list of emails associated with it""" |
247 | + emails = [] |
248 | + name_pieces = full_name.split() |
249 | + contact = self.store.find(models.Contact, |
250 | + models.Contact.first_name == name_pieces[0], |
251 | + models.Contact.last_name == name_pieces[-1]).any() |
252 | + for email in contact.emails: |
253 | + emails.append(email.address) |
254 | + return emails |
255 | + |
256 | + @dbus.service.method('org.elementary.dexterserver', in_signature='s', out_signature='aa{ss}') |
257 | + def SearchContacts(self, search_string): |
258 | + """search for contacts that contain a string |
259 | + |
260 | + takes a search string, and returns a list of name/email strings. |
261 | + """ |
262 | + results = [] |
263 | + resultlist = [] |
264 | + results = utilities.search_store(search_string, self.store) |
265 | + for contact in results: |
266 | + new = dict() |
267 | + new["full_name"] = contact.full_name |
268 | + new["id"] = str(contact.id) |
269 | + if contact.last_name: |
270 | + new["last_name"] = contact.last_name |
271 | + else: |
272 | + new["last_name"] = contact.first_name |
273 | + resultlist.append(new) |
274 | + return resultlist |
275 | + |
276 | + @dbus.service.method('org.elementary.dexterserver', in_signature='s', out_signature='as') |
277 | + def AutocompleteContact(self, search_string): |
278 | + """search for contacts that contain a string |
279 | + |
280 | + takes a search string, and returns a list of name/email strings. |
281 | + """ |
282 | + results = [] |
283 | + resultlist = [] |
284 | + results = utilities.search_store(search_string, self.store) |
285 | + for contact in results: |
286 | + for email in contact.emails: |
287 | + resultlist.append(contact.full_name + " <" + email.address + ">") |
288 | + return resultlist |
289 | + |
290 | + @dbus.service.method('org.elementary.dexterserver') |
291 | + def ShowWindow(self): |
292 | + """present the running dexter window, or start one""" |
293 | + session_bus = dbus.SessionBus() |
294 | + try: |
295 | + dexter_object = session_bus.get_object("org.elementary.dexterapp", |
296 | + "/org/elementary/dexterapp") |
297 | + is_running = True |
298 | + except: |
299 | + is_running = False |
300 | + if is_running: |
301 | + self.WindowRequested() |
302 | + else: |
303 | + try: |
304 | + glib.spawn_async([self.datadir + "/../bin/dexter"]) |
305 | + except glib.GError: |
306 | + glib.spawn_async(["/usr/bin/dexter"]) |
307 | + |
308 | + @dbus.service.method('org.elementary.dexterserver') |
309 | + def NewContactDialog(self): |
310 | + """present a new contact window""" |
311 | + session_bus = dbus.SessionBus() |
312 | + try: |
313 | + dexter_object = session_bus.get_object("org.elementary.dexterapp", |
314 | + "/org/elementary/dexterapp") |
315 | + is_running = True |
316 | + except: |
317 | + is_running = False |
318 | + if is_running: |
319 | + self.ContactDialogRequested() |
320 | + else: |
321 | + glib.spawn_async(["/usr/bin/dexter", "-n"]) |
322 | + |
323 | + @dbus.service.method('org.elementary.dexterserver') |
324 | + def CancelImport(self): |
325 | + self.CancelImportRequested() |
326 | + |
327 | + @dbus.service.method('org.elementary.dexterserver', in_signature='as') |
328 | + def ImportVcardPath(self, paths): |
329 | + self.VcardImportRequested(paths) |
330 | + |
331 | + @dbus.service.method('org.elementary.dexterserver') |
332 | + def DBCommit(self): |
333 | + self.store.commit() |
334 | + |
335 | + @dbus.service.method('org.elementary.dexterserver') |
336 | + def QuitServer(self): |
337 | + self.store.commit() |
338 | + self.ServerClosed() |
339 | + gtk.main_quit() |
340 | + |
341 | + #signals |
342 | + @dbus.service.signal('org.elementary.dexterserver') |
343 | + def ContactAdded(self, contact): |
344 | + pass |
345 | + |
346 | + @dbus.service.signal('org.elementary.dexterserver') |
347 | + def ContactsDeleted(self, contact_ids): |
348 | + if not self.store.find(models.Contact).count(): |
349 | + self.ContactsCleared() |
350 | + |
351 | + @dbus.service.signal('org.elementary.dexterserver') |
352 | + def ContactsCleared(self): |
353 | + pass |
354 | + |
355 | + @dbus.service.signal('org.elementary.dexterserver') |
356 | + def WindowRequested(self): |
357 | + pass |
358 | + |
359 | + @dbus.service.signal('org.elementary.dexterserver') |
360 | + def ContactDialogRequested(self): |
361 | + pass |
362 | + |
363 | + @dbus.service.signal('org.elementary.dexterserver') |
364 | + def CancelImportRequested(self): |
365 | + pass |
366 | + |
367 | + @dbus.service.signal('org.elementary.dexterserver') |
368 | + def VcardImportRequested(self, paths): |
369 | + pass |
370 | + |
371 | + @dbus.service.signal('org.elementary.dexterserver') |
372 | + def ServerClosed(self): |
373 | + pass |
374 | + |
375 | + #helper methods |
376 | + def convert_contact_to_dict(self, contact): |
377 | + new = dict() |
378 | + new["id"] = str(contact.id) |
379 | + new["photo"] = self.none_to_string(contact.photo) |
380 | + new["first_name"] = contact.first_name |
381 | + new["middle_name"] = self.none_to_string(contact.middle_name) |
382 | + new["last_name"] = self.none_to_string(contact.last_name) |
383 | + new["full_name"] = contact.full_name |
384 | + new["organization"] = self.none_to_string(contact.organization) |
385 | + new["birthday"] = str(self.none_to_string(contact.birthday)) |
386 | + return new |
387 | + |
388 | + def convert_to_small_dict(self, contact): |
389 | + new = dict() |
390 | + new["full_name"] = contact.full_name |
391 | + new["id"] = str(contact.id) |
392 | + if contact.last_name: |
393 | + new["last_name"] = contact.last_name |
394 | + else: |
395 | + new["last_name"] = contact.first_name |
396 | + return new |
397 | + |
398 | + def none_to_string(self, data): |
399 | + if not data: |
400 | + data = "None" |
401 | + return data |
402 | + |
403 | +if __name__ == "__main__": |
404 | + service = DexterServer() |
405 | + try: |
406 | + gtk.main() |
407 | + except: |
408 | + service.ServerClosed() |
409 | + service.store.commit() |
410 | |
411 | === modified file 'data/ui/dexter-main.ui' |
412 | --- data/ui/dexter-main.ui 2010-12-13 22:23:48 +0000 |
413 | +++ data/ui/dexter-main.ui 2010-12-20 23:23:53 +0000 |
414 | @@ -410,31 +410,48 @@ |
415 | </packing> |
416 | </child> |
417 | <child> |
418 | - <object class="GtkHBox" id="hbox_progress"> |
419 | + <object class="GtkVBox" id="vbox_progress"> |
420 | <child> |
421 | - <object class="GtkVBox" id="vbox1"> |
422 | + <object class="GtkLabel" id="label_progress"> |
423 | <property name="visible">True</property> |
424 | + </object> |
425 | + <packing> |
426 | + <property name="position">0</property> |
427 | + </packing> |
428 | + </child> |
429 | + <child> |
430 | + <object class="GtkHBox" id="hbox_progress"> |
431 | <child> |
432 | - <object class="GtkProgressBar" id="progressbar"/> |
433 | + <object class="GtkVBox" id="vbox8"> |
434 | + <property name="visible">True</property> |
435 | + <child> |
436 | + <object class="GtkProgressBar" id="progressbar"/> |
437 | + <packing> |
438 | + <property name="padding">8</property> |
439 | + <property name="position">0</property> |
440 | + </packing> |
441 | + </child> |
442 | + </object> |
443 | <packing> |
444 | - <property name="padding">8</property> |
445 | + <property name="padding">4</property> |
446 | <property name="position">0</property> |
447 | </packing> |
448 | </child> |
449 | - </object> |
450 | - <packing> |
451 | - <property name="padding">4</property> |
452 | - <property name="position">0</property> |
453 | - </packing> |
454 | - </child> |
455 | - <child> |
456 | - <object class="GtkButton" id="button_cancel"> |
457 | - <property name="visible">True</property> |
458 | - <property name="can_focus">True</property> |
459 | - <property name="receives_default">True</property> |
460 | - <property name="image">image1</property> |
461 | - <property name="relief">none</property> |
462 | - <signal name="clicked" handler="_on_button_cancel_clicked"/> |
463 | + <child> |
464 | + <object class="GtkButton" id="button_cancel"> |
465 | + <property name="visible">True</property> |
466 | + <property name="can_focus">True</property> |
467 | + <property name="receives_default">True</property> |
468 | + <property name="image">image1</property> |
469 | + <property name="relief">none</property> |
470 | + <signal name="clicked" handler="_on_button_cancel_clicked"/> |
471 | + </object> |
472 | + <packing> |
473 | + <property name="expand">False</property> |
474 | + <property name="fill">False</property> |
475 | + <property name="position">1</property> |
476 | + </packing> |
477 | + </child> |
478 | </object> |
479 | <packing> |
480 | <property name="expand">False</property> |
481 | |
482 | === modified file 'debian/changelog' |
483 | --- debian/changelog 2010-12-12 20:24:32 +0000 |
484 | +++ debian/changelog 2010-12-20 23:23:53 +0000 |
485 | @@ -3,7 +3,7 @@ |
486 | * fixed bug 688411, dexter should START after installation from PPA |
487 | now, lol |
488 | |
489 | - -- Allen Lowe <allen@allen-delltop> Sun, 12 Dec 2010 13:17:12 -0700 |
490 | + -- Allen Lowe <lallenlowe@allen-work-desktop> Mon, 20 Dec 2010 16:14:52 -0700 |
491 | |
492 | dexter (0.15) maverick; urgency=low |
493 | |
494 | |
495 | === modified file 'dexter/SimpleGtkbuilderApp.py' |
496 | --- dexter/SimpleGtkbuilderApp.py 2010-12-10 17:59:44 +0000 |
497 | +++ dexter/SimpleGtkbuilderApp.py 2010-12-20 23:23:53 +0000 |
498 | @@ -1,3 +1,19 @@ |
499 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
500 | +### BEGIN LICENSE |
501 | +# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com> |
502 | +# This program is free software: you can redistribute it and/or modify it |
503 | +# under the terms of the GNU General Public License version 3, as published |
504 | +# by the Free Software Foundation. |
505 | +# |
506 | +# This program is distributed in the hope that it will be useful, but |
507 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
508 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
509 | +# PURPOSE. See the GNU General Public License for more details. |
510 | +# |
511 | +# You should have received a copy of the GNU General Public License along |
512 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
513 | +### END LICENSE |
514 | + |
515 | """ |
516 | SimpleGladeApp.py |
517 | Module that provides an object oriented abstraction to pygtk and gtkbuilder |
518 | @@ -5,20 +21,6 @@ |
519 | based on ideas from SimpleGladeBuilder by Sandino Flores Moreno |
520 | """ |
521 | |
522 | -# This library is free software; you can redistribute it and/or |
523 | -# modify it under the terms of the GNU Lesser General Public |
524 | -# License as published by the Free Software Foundation; version 3. |
525 | -# |
526 | -# This library is distributed in the hope that it will be useful, |
527 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
528 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
529 | -# Lesser General Public License for more details. |
530 | -# |
531 | -# You should have received a copy of the GNU Lesser General Public |
532 | -# License along with this library; if not, write to the Free Software |
533 | -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
534 | -# USA |
535 | - |
536 | import gtk |
537 | import logging |
538 | |
539 | |
540 | === modified file 'dexter/app.py' |
541 | --- dexter/app.py 2010-12-12 22:50:01 +0000 |
542 | +++ dexter/app.py 2010-12-20 23:23:53 +0000 |
543 | @@ -1,26 +1,28 @@ |
544 | -# Authors: |
545 | -# Andrew Higginson |
546 | -# |
547 | -# This program is free software; you can redistribute it and/or modify it under |
548 | -# the terms of the GNU General Public License as published by the Free Software |
549 | -# Foundation; version 3. |
550 | -# |
551 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
552 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
553 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
554 | -# details. |
555 | -# |
556 | -# You should have received a copy of the GNU General Public License along with |
557 | -# this program; if not, write to the Free Software Foundation, Inc., |
558 | -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
559 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
560 | +### BEGIN LICENSE |
561 | +# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com> |
562 | +# This program is free software: you can redistribute it and/or modify it |
563 | +# under the terms of the GNU General Public License version 3, as published |
564 | +# by the Free Software Foundation. |
565 | +# |
566 | +# This program is distributed in the hope that it will be useful, but |
567 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
568 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
569 | +# PURPOSE. See the GNU General Public License for more details. |
570 | +# |
571 | +# You should have received a copy of the GNU General Public License along |
572 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
573 | +### END LICENSE |
574 | |
575 | import sys |
576 | import os |
577 | import warnings |
578 | import gtk |
579 | import glib |
580 | +import dbus |
581 | import gobject |
582 | import locale |
583 | +import datetime |
584 | import Queue |
585 | |
586 | from dexter.frontend.dialogs.about import AboutDialog |
587 | @@ -50,30 +52,28 @@ |
588 | self.icons = gtk.icon_theme_get_default() |
589 | self.welcoming = False |
590 | self.q = None |
591 | + self.selected_id = None |
592 | self.config = DexterConfig() |
593 | |
594 | # Setup Translations |
595 | self._setup_translations() |
596 | |
597 | # Setup Data |
598 | + self._setup_dbus_client() |
599 | self._setup_data() |
600 | |
601 | # Setup Import from CLI |
602 | self._setup_import() |
603 | |
604 | - myservice = dexdbus.DexterService(self.icons, self.datadir, self) |
605 | |
606 | #run just the newcontact dialog |
607 | if options.newcontact: |
608 | self._purpose = RUN_CONTACT_DIALOG |
609 | self.contact_dialog = NewContactDialog(self.icons, self.datadir) |
610 | self.contact_dialog.standalone = True |
611 | - # run just the dbus daemon |
612 | - elif options.dbusdaemon: |
613 | - self._purpose = RUN_DAEMON |
614 | - myservice.daemon = True |
615 | # Run the application. |
616 | else: |
617 | + myservice = dexdbus.DexterApp() |
618 | self._purpose = RUN_APP |
619 | |
620 | if options.newcontact: |
621 | @@ -88,14 +88,47 @@ |
622 | # Load any config items (i.e. saved hpane width) |
623 | self._load_config() |
624 | |
625 | - if not self.contacts.count(): |
626 | + if not len(self.contacts): |
627 | self._setup_welcome() |
628 | else: |
629 | self._setup_main_screen() |
630 | - self.refresh_list(self.contacts) |
631 | + self.refresh_list() |
632 | if self.files_to_import: |
633 | self._import_vcard_path(self.files_to_import) |
634 | |
635 | + def _setup_dbus_client(self): |
636 | + self.session_bus = dbus.SessionBus() |
637 | + #############TODO: USE SERVICE FILE INSTEAD############################ |
638 | + try: |
639 | + self.dexter_object = self.session_bus.get_object("org.elementary.dexterserver", |
640 | + "/org/elementary/dexterserver") |
641 | + except: |
642 | + try: |
643 | + glib.spawn_async([self.datadir + "/../bin/dexter-server"]) |
644 | + except glib.GError: |
645 | + glib.spawn_async(["/usr/bin/dexter-server"]) |
646 | + import time; time.sleep(0.5) |
647 | + self.dexter_object = self.session_bus.get_object("org.elementary.dexterserver", |
648 | + "/org/elementary/dexterserver") |
649 | + #############TODO: USE SERVICE FILE INSTEAD############################ |
650 | + self.GetFullList = self.dexter_object.get_dbus_method("GetFullList") |
651 | + self.GetContactByID = self.dexter_object.get_dbus_method("GetContactByID") |
652 | + self.GetAddresses = self.dexter_object.get_dbus_method("GetAddresses") |
653 | + self.GetPhones = self.dexter_object.get_dbus_method("GetPhones") |
654 | + self.GetEmails = self.dexter_object.get_dbus_method("GetEmails") |
655 | + self.GetNotes = self.dexter_object.get_dbus_method("GetNotes") |
656 | + self.CreateContact = self.dexter_object.get_dbus_method("CreateContact", dbus_interface='org.elementary.dexterserver') |
657 | + self.DeleteContact = self.dexter_object.get_dbus_method("DeleteContact") |
658 | + self.DeleteContacts = self.dexter_object.get_dbus_method("DeleteContacts") |
659 | + self.DBCommit = self.dexter_object.get_dbus_method("DBCommit") |
660 | + self.dexter_object.connect_to_signal("ContactAdded", self._on_contact_created_dbus_signal, 'org.elementary.dexterserver') |
661 | + self.dexter_object.connect_to_signal("ContactsDeleted", self._on_contacts_deleted_dbus_signal, 'org.elementary.dexterserver') |
662 | + self.dexter_object.connect_to_signal("ContactsCleared", self._on_contacts_cleared_dbus_signal, 'org.elementary.dexterserver') |
663 | + self.dexter_object.connect_to_signal("WindowRequested", self._on_window_requested_dbus_signal, 'org.elementary.dexterserver') |
664 | + self.dexter_object.connect_to_signal("ContactDialogRequested", self._on_contact_dialog_requested_dbus_signal, 'org.elementary.dexterserver') |
665 | + self.dexter_object.connect_to_signal("CancelImportRequested", self._on_cancel_import_requested_dbus_signal, 'org.elementary.dexterserver') |
666 | + self.dexter_object.connect_to_signal("VcardImportRequested", self._on_vcard_import_requested_dbus_signal, 'org.elementary.dexterserver') |
667 | + self.dexter_object.connect_to_signal("ServerClosed", self._on_server_closed_dbus_signal, 'org.elementary.dexterserver') |
668 | |
669 | # Private Functions |
670 | def _setup_translations(self): |
671 | @@ -115,11 +148,8 @@ |
672 | self.files_to_import.append(arg) |
673 | |
674 | def _setup_data(self): |
675 | - """setup storm Store, and get a list of contacts from the database""" |
676 | - self.store = models.setup_db(models.DATA_DIR) |
677 | - models.create_tables(self.store) |
678 | - models.update_tables(self.store) |
679 | - self.contacts = self.store.find(models.Contact) |
680 | + """get a list of contacts from the database""" |
681 | + self.contacts = self.GetFullList() |
682 | self.importing = False |
683 | |
684 | def _setup_contact_list(self): |
685 | @@ -147,7 +177,7 @@ |
686 | """setup the necessary fields for live searching""" |
687 | self.toolbutton1 = self.builder.get_object("toolbutton1") |
688 | self.search_bar = SearchBar(_("Type to search..."), |
689 | - self.store) |
690 | + self.dexter_object) |
691 | self.toolbutton1.add(self.search_bar) |
692 | self.search_bar.show() |
693 | self.search_bar.connect("changed", self._on_search_bar_changed) |
694 | @@ -181,14 +211,43 @@ |
695 | def _save_config(self): |
696 | hpane_width = self.hpaned_main.get_position() |
697 | self.config.set("general", "hpane_width", hpane_width) |
698 | - |
699 | + |
700 | |
701 | # Callbacks |
702 | + def _on_contact_created_dbus_signal(self, contact): |
703 | + if self.welcoming: |
704 | + self._setup_main_screen() |
705 | + if not self.importing: |
706 | + self.refresh_list() |
707 | + self.select_by_id(int(contact["id"])) |
708 | + self.num_contacts.set_text(" %i %s" % (len(self.contact_model), _("Contacts"))) |
709 | + |
710 | + def _on_contacts_deleted_dbus_signal(self, contact_ids): |
711 | + self.refresh_list() |
712 | + |
713 | + def _on_contacts_cleared_dbus_signal(self): |
714 | + if not self.vbox_welcome in self.vbox_main.get_children(): |
715 | + self._setup_welcome() |
716 | + |
717 | + def _on_window_requested_dbus_signal(self): |
718 | + self.window_main.present() |
719 | + |
720 | + def _on_contact_dialog_requested_dbus_signal(self): |
721 | + self._on_toolbutton_new_contact_clicked(None) |
722 | + |
723 | + def _on_cancel_import_requested_dbus_signal(self): |
724 | + self.q.request_stop = True |
725 | + |
726 | + def _on_vcard_import_requested_dbus_signal(self, paths): |
727 | + self._import_vcard_path(paths) |
728 | + |
729 | + def _on_server_closed_dbus_signal(self): |
730 | + self._setup_dbus_client() |
731 | + |
732 | def _on_window_main_delete_event(self, widget, event): |
733 | # Clean up code for saving application state should be added here. |
734 | - self.store.commit() |
735 | + self.DBCommit() |
736 | self._save_config() |
737 | - |
738 | gtk.main_quit() |
739 | |
740 | def _on_menuitem_quit_activated(self, widget): |
741 | @@ -293,8 +352,7 @@ |
742 | gtk.gdk.CURRENT_TIME) |
743 | |
744 | def _on_menuitem_merge_activated(self, widget): |
745 | - self.store.flush() |
746 | - result = utilities.merge_contacts(self.get_selected_contacts(), self.store) |
747 | + result = utilities.merge_contacts(self.get_selected_contacts(), self.dexter_object) |
748 | if type(result) == dict: |
749 | merge = MergeDialog() |
750 | merge.set_transient_for(self.window_main) |
751 | @@ -303,11 +361,11 @@ |
752 | if response == gtk.RESPONSE_OK: |
753 | result = merge.data |
754 | merge.destroy() |
755 | - id_number = utilities.create_new_contact(result["first"][0], result["middle"][0], |
756 | - result["last"][0], result["org"][0], |
757 | - result["birthday"][0], result["photo"], |
758 | - result["emails"], result["phones"], result["addresses"], |
759 | - result["notes"], self.store) |
760 | + id_number = self.CreateContact(result["first"][0], result["middle"][0], |
761 | + result["last"][0], result["org"][0], |
762 | + result["birthday"][0], result["photo"], |
763 | + result["emails"], result["phones"], |
764 | + result["addresses"], result["notes"],) |
765 | elif type(result) == int: |
766 | id_number = result |
767 | else: |
768 | @@ -328,16 +386,16 @@ |
769 | chooser.set_default_response(gtk.RESPONSE_OK) |
770 | chooser.set_current_folder(os.path.expanduser("~")) # set to users home folder |
771 | if len(contacts) == 1: |
772 | - if contacts[0].last_name: |
773 | - chooser.set_current_name(contacts[0].first_name.lower() + "-" + |
774 | - contacts[0].last_name.lower() + ".vcf") |
775 | + if contacts[0]["last_name"]: |
776 | + chooser.set_current_name(contacts[0]["first_name"].lower() + "-" + |
777 | + contacts[0]["last_name"].lower() + ".vcf") |
778 | else: |
779 | - chooser.set_current_name(contacts[0].first_name.lower() + ".vcf") |
780 | + chooser.set_current_name(contacts[0]["first_name"].lower() + ".vcf") |
781 | else: |
782 | chooser.set_current_name("contacts.vcf") |
783 | response = chooser.run() |
784 | if response == gtk.RESPONSE_OK: |
785 | - utilities.export_to_vcard(contacts, chooser.get_filename()) |
786 | + utilities.export_to_vcard(contacts, chooser.get_filename(), self.dexter_object) |
787 | chooser.destroy() |
788 | |
789 | def _on_welcome_import_clicked(self, widget): |
790 | @@ -345,7 +403,6 @@ |
791 | |
792 | def _on_menuitem_import_activated(self, widget): |
793 | """user selects the import from vcard action""" |
794 | - self.store.commit() |
795 | chooser = gtk.FileChooserDialog(title=None, |
796 | action=gtk.FILE_CHOOSER_ACTION_OPEN, |
797 | buttons=(gtk.STOCK_CANCEL, |
798 | @@ -363,7 +420,6 @@ |
799 | response = chooser.run() |
800 | if response == gtk.RESPONSE_OK: |
801 | self.search_bar.hint() |
802 | - self.refresh_list(self.contacts) |
803 | vcard_paths = chooser.get_filenames() |
804 | self._import_vcard_path(vcard_paths) |
805 | chooser.destroy() |
806 | @@ -375,12 +431,15 @@ |
807 | import_thread.start() # start parsing vcard in a separate thread |
808 | self.timer = gobject.timeout_add(100, self.import_timeout)#create a timer for adding contacts, so as to not block the GUI |
809 | timer2 = gobject.timeout_add(200, self.update_timeout)# a timer to make sure the UI gets refreshed sometimes during import |
810 | + self.vbox_progress.show() |
811 | + self.label_progress.set_text(_("Importing Contacts...")) |
812 | self.hbox_progress.show() |
813 | self.progressbar.show() |
814 | + self.menuitem_import.set_sensitive(False) |
815 | |
816 | def _on_button_photo_clicked(self, widget): |
817 | """photo button clicked, open photo in external viewer""" |
818 | - photo_name = self.get_selected_contact().photo |
819 | + photo_name = self.get_selected_contact()["photo"] |
820 | try: |
821 | glib.spawn_async(["/usr/bin/xdg-open", models.IMAGE_DIR + \ |
822 | str(photo_name)]) |
823 | @@ -395,9 +454,8 @@ |
824 | |
825 | def _on_store_changed(self): |
826 | """run when a contact has been created, edited, merged, or deleted""" |
827 | - self.store.commit() |
828 | + self.DBCommit() |
829 | self.search_bar.hint() |
830 | - self.refresh_list() |
831 | self.clear_contact_viewer() |
832 | self.search_bar.result_sets = {} |
833 | self.search_bar.result_set = [] |
834 | @@ -418,8 +476,10 @@ |
835 | response = md.run() |
836 | md.destroy() |
837 | if response == gtk.RESPONSE_YES: |
838 | - for contact in selected_contacts : |
839 | - utilities.delete_contact(self.store, contact) |
840 | + ids = [] |
841 | + for contact in selected_contacts: |
842 | + ids.append(int(contact["id"])) |
843 | + self.DeleteContacts(ids) |
844 | self._on_store_changed() |
845 | |
846 | def _on_welcome_create_clicked(self, widget): |
847 | @@ -429,14 +489,11 @@ |
848 | """open a dialog to create a new contact""" |
849 | new = NewContactDialog(self.icons, self.datadir) |
850 | new.dialog.set_transient_for(self.window_main) |
851 | - self.store.flush() |
852 | - new.set_store(self.store) |
853 | response = new.run() |
854 | id_number = new.new_contact_id |
855 | new.dialog.destroy() |
856 | if response == gtk.RESPONSE_OK: |
857 | self._on_store_changed() |
858 | - self.select_by_id(id_number) |
859 | if self.welcoming: |
860 | self._setup_main_screen() |
861 | |
862 | @@ -445,8 +502,7 @@ |
863 | contact = self.get_selected_contact() |
864 | edit = NewContactDialog(self.icons, self.datadir) |
865 | edit.dialog.set_transient_for(self.window_main) |
866 | - self.store.flush() |
867 | - edit.set_contact_edit(contact, self.store) |
868 | + edit.set_contact_edit(contact) |
869 | edit.populate() |
870 | response = edit.run() |
871 | id_number = edit.new_contact_id |
872 | @@ -521,23 +577,13 @@ |
873 | if not self.q.request_stop: |
874 | try: |
875 | item = self.q.get(True, 2) |
876 | - new_id = utilities.create_new_contact(item[0], item[1], item[2], |
877 | - item[3], item[4], item[5], |
878 | - item[6], item[7], item[8], |
879 | - item[9], self.store) |
880 | + new_id = self.CreateContact(item[0], item[1], item[2], item[3], |
881 | + item[4], item[5], item[6], item[7], |
882 | + item[8], item[9]) |
883 | self.last_contact_imported = new_id |
884 | - contact = self.store.find(models.Contact, models.Contact.id == new_id).one() |
885 | + contact = self.GetContactByID(new_id) |
886 | self.progressbar.pulse() |
887 | - if contact.last_name: |
888 | - # add the newly imported contact to the contact list treeview |
889 | - self.contact_model.append([contact.full_name, contact.id, |
890 | - contact.last_name]) |
891 | - else: |
892 | - self.contact_model.append([contact.full_name, contact.id, |
893 | - contact.first_name]) |
894 | self.num_contacts.set_text(" %i %s" % (len(self.contact_model), _("Contacts"))) |
895 | - if self.welcoming: |
896 | - self._setup_main_screen() |
897 | self.importing = True |
898 | return True |
899 | except Queue.Empty: |
900 | @@ -556,14 +602,17 @@ |
901 | def finish_importing(self): |
902 | self.progressbar.hide() |
903 | self.hbox_progress.hide() |
904 | + self.vbox_progress.hide() |
905 | + self.label_progress.set_text("") |
906 | self.progressbar.set_fraction(0.0) |
907 | - self.timer = gobject.timeout_add(100, self.store.commit) |
908 | + self.DBCommit() |
909 | self.importing = False |
910 | self.refresh_list() |
911 | if self.last_contact_imported: |
912 | self.select_by_id(self.last_contact_imported) |
913 | self.search_bar.set_sensitive(True) |
914 | self.timer = None |
915 | + self.menuitem_import.set_sensitive(True) |
916 | |
917 | def refresh_list(self, contact_list="empty"): |
918 | """ |
919 | @@ -572,27 +621,37 @@ |
920 | get a list from the database |
921 | """ |
922 | if contact_list == "empty": |
923 | - self.contacts = self.store.find(models.Contact) |
924 | + self.contacts = self.GetFullList() |
925 | contact_list = self.contacts |
926 | - self.search_bar.refresh(self.contacts.count()) # make sure the searchbar has the updated contact list |
927 | + elif not contact_list: |
928 | + pass |
929 | + self.search_bar.refresh(len(self.contacts)) # give searchbar the updated contact numbers |
930 | self.contact_model.clear() |
931 | for contact in contact_list: |
932 | - if contact.last_name: |
933 | - self.contact_model.append([contact.full_name, contact.id, |
934 | - contact.last_name]) |
935 | - else: |
936 | - self.contact_model.append([contact.full_name, contact.id, |
937 | - contact.first_name]) |
938 | + self.insert_in_list(contact) |
939 | self.num_contacts.set_text(" %i %s" % (len(self.contact_model), _("Contacts"))) |
940 | #clearing the list unselects any contacts, so make these items insensitive |
941 | self.toolbutton_edit_contact.set_sensitive(False) |
942 | self.menuitem_merge.set_sensitive(False) |
943 | self.toolbutton_delete_contact.set_sensitive(False) |
944 | self.menuitem_export.set_sensitive(False) |
945 | - if not self.contacts.count(): |
946 | + if not len(self.contacts): |
947 | if not self.vbox_welcome in self.vbox_main.get_children(): |
948 | self._setup_welcome() |
949 | |
950 | + def insert_in_list(self, contact): |
951 | + self.contact_model.append([contact["full_name"], int(contact["id"]), |
952 | + contact["last_name"]]) |
953 | + |
954 | + def remove_from_list(self, contact_ids=[]): |
955 | + row_iters = [] |
956 | + for row in self.contact_model: |
957 | + if row[1] in contact_ids: |
958 | + rowiter = self.contact_model.get_iter(row.path) |
959 | + row_iters.append(rowiter) |
960 | + for rowiter in row_iters: |
961 | + self.contact_model.remove(rowiter) |
962 | + |
963 | #this is still here for compatibility with code I haven't updated yet |
964 | def get_selected_contact(self): |
965 | """get the contact that is currently selected from the database""" |
966 | @@ -606,8 +665,7 @@ |
967 | treeiter = self.contact_model.get_iter(path) |
968 | try: |
969 | id = selection[0].get_value(treeiter, 1) |
970 | - contact = self.store.find(models.Contact, |
971 | - models.Contact.id == id).one() |
972 | + contact = self.GetContactByID(id) |
973 | except TypeError: |
974 | contact = None |
975 | return contact |
976 | @@ -620,8 +678,7 @@ |
977 | treeiter = self.contact_model.get_iter(path) |
978 | try: |
979 | id = selection[0].get_value(treeiter, 1) |
980 | - contact = self.store.find(models.Contact, |
981 | - models.Contact.id == id).one() |
982 | + contact = self.GetContactByID(id) |
983 | except TypeError: |
984 | contact = None |
985 | contactlist.append(contact) |
986 | @@ -658,72 +715,79 @@ |
987 | def populate_viewer(self, contact): |
988 | """fill the contents of the contact viewer with the given contact""" |
989 | note_space = 0 |
990 | - self.label_name.set_text(contact.full_name) |
991 | - if contact.organization: |
992 | - self.label_organization.set_text(contact.organization) |
993 | + self.label_name.set_text(contact["full_name"]) |
994 | + if contact["organization"] != "None": |
995 | + self.label_organization.set_text(contact["organization"]) |
996 | else: |
997 | self.label_organization.set_text("") |
998 | - if contact.photo: |
999 | + if contact["photo"] != "None": |
1000 | self.button_photo.show() |
1001 | pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(models.IMAGE_DIR + |
1002 | - str(contact.photo), |
1003 | + str(contact["photo"]), |
1004 | 60, 60) |
1005 | self.image_photo.set_from_pixbuf(pixbuf) |
1006 | else: |
1007 | self.button_photo.hide() |
1008 | - if contact.birthday: |
1009 | + if contact["birthday"] != "None": |
1010 | + birthdaylist = contact["birthday"].split("-") |
1011 | + birthday = datetime.datetime(int(birthdaylist[0]), |
1012 | + int(birthdaylist[1]), |
1013 | + int(birthdaylist[2])) |
1014 | self.vbox_column_header.pack_start(HeaderLabel(_("Birthdate")), |
1015 | expand=False, fill=True, padding=0) |
1016 | self.vbox_column_data.pack_start(DataLabel( |
1017 | - contact.birthday.strftime("%B %d, %Y")), |
1018 | + birthday.strftime("%B %d, %Y")), |
1019 | expand=False, fill=True, padding=0) |
1020 | self.blank_line() |
1021 | - if contact.emails.count(): |
1022 | - for email in contact.emails: |
1023 | - email_label = DataLabel(email.address) |
1024 | + emails = self.GetEmails(int(contact["id"])) |
1025 | + if len(emails): |
1026 | + for email in emails: |
1027 | + email_label = DataLabel(email[0]) |
1028 | email_label_markup = "<a href=\"%s\" title=\"%s\">%s</a>" % \ |
1029 | - ("mailto:"+email.address.strip(), |
1030 | - _("Send email..."),email.address.strip()) |
1031 | + ("mailto:"+email[0].strip(), |
1032 | + _("Send email..."),email[0].strip()) |
1033 | email_label.set_markup(email_label_markup) |
1034 | - self.vbox_column_header.pack_start(HeaderLabel(email.email_type), |
1035 | + self.vbox_column_header.pack_start(HeaderLabel(email[1]), |
1036 | expand=False, fill=True, padding=0) |
1037 | self.vbox_column_data.pack_start(email_label, |
1038 | expand=False, fill=True, padding=0) |
1039 | self.blank_line() |
1040 | - if contact.phones.count(): |
1041 | - for phone in contact.phones: |
1042 | - self.vbox_column_header.pack_start(HeaderLabel(phone.phone_type), |
1043 | + phones = self.GetPhones(int(contact["id"])) |
1044 | + if len(phones): |
1045 | + for phone in phones: |
1046 | + self.vbox_column_header.pack_start(HeaderLabel(phone[1]), |
1047 | expand=False, fill=True, padding=0) |
1048 | - self.vbox_column_data.pack_start(DataLabel(phone.number), |
1049 | + self.vbox_column_data.pack_start(DataLabel(phone[0]), |
1050 | expand=False, fill=True, padding=0) |
1051 | self.blank_line() |
1052 | - if contact.addresses.count(): |
1053 | - for address in contact.addresses: |
1054 | + addresses = self.GetAddresses(int(contact["id"])) |
1055 | + if len(addresses): |
1056 | + for address in addresses: |
1057 | full_address = "" |
1058 | multi_line_address = "" |
1059 | spaced = False |
1060 | adr_label = DataLabel("") |
1061 | - if address.address_type: |
1062 | + if address["type"] != "None": |
1063 | self.vbox_column_header.pack_start( |
1064 | - HeaderLabel(address.address_type), |
1065 | + HeaderLabel(address["type"]), |
1066 | expand=False, fill=True, padding=0) |
1067 | else: |
1068 | self.vbox_column_header.pack_start( |
1069 | HeaderLabel(_("Address")), |
1070 | expand=False, fill=True, padding=0) |
1071 | - if address.street: |
1072 | - full_address = address.street |
1073 | - multi_line_address = address.street |
1074 | + if address["street"] != "None": |
1075 | + full_address = address["street"] |
1076 | + multi_line_address = address["street"] |
1077 | spaced = True |
1078 | else: |
1079 | streetlabel = None |
1080 | text = "" |
1081 | - if address.city: |
1082 | - text += "%s, " % (address.city) |
1083 | - if address.state: |
1084 | - text += "%s " % (address.state) |
1085 | - if address.zip: |
1086 | - text += "%s" % (address.zip) |
1087 | + if address["city"] != "None": |
1088 | + text += "%s, " % (address["city"]) |
1089 | + if address["state"] != "None": |
1090 | + text += "%s " % (address["state"]) |
1091 | + if address["zip"] != "None": |
1092 | + text += "%s" % (address["zip"]) |
1093 | if text: |
1094 | if spaced: |
1095 | self.vbox_column_header.pack_start(gtk.Label(), |
1096 | @@ -740,11 +804,12 @@ |
1097 | self.vbox_column_data.pack_start(adr_label, |
1098 | expand=False, fill=True, padding=0) |
1099 | self.blank_line() |
1100 | - for note in contact.notes: |
1101 | - self.vbox_column_header.pack_start(HeaderLabel(_("Note")), |
1102 | - expand=False, fill=True, padding=0) |
1103 | - self.vbox_column_data.pack_start(DataLabel(note.text), |
1104 | - expand=False, fill=True, padding=note_space) |
1105 | + for note in self.GetNotes(int(contact["id"])): |
1106 | + if note.strip() != "None" and note.strip(): |
1107 | + self.vbox_column_header.pack_start(HeaderLabel(_("Note")), |
1108 | + expand=False, fill=True, padding=0) |
1109 | + self.vbox_column_data.pack_start(DataLabel(note), expand=False, |
1110 | + fill=True, padding=note_space) |
1111 | self.vbox_column_header.show_all() |
1112 | self.vbox_column_data.show_all() |
1113 | |
1114 | |
1115 | === modified file 'dexter/backend/ddbus.py' |
1116 | --- dexter/backend/ddbus.py 2010-12-13 20:18:13 +0000 |
1117 | +++ dexter/backend/ddbus.py 2010-12-20 23:23:53 +0000 |
1118 | @@ -15,118 +15,16 @@ |
1119 | ### END LICENSE |
1120 | |
1121 | |
1122 | -import datetime |
1123 | import dbus |
1124 | import dbus.service |
1125 | from dbus.mainloop.glib import DBusGMainLoop |
1126 | |
1127 | DBusGMainLoop(set_as_default=True) |
1128 | |
1129 | -from dexter.frontend.dialogs.new_contact import NewContactDialog |
1130 | -import dexter.backend.models as models |
1131 | -import dexter.backend.utilities as utilities |
1132 | -from storm.expr import Or |
1133 | |
1134 | -class DexterService(dbus.service.Object): |
1135 | - def __init__(self, icons, datadir, app=None): |
1136 | - bus_name = dbus.service.BusName('org.elementary.dexterservice', |
1137 | +class DexterApp(dbus.service.Object): |
1138 | + def __init__(self): |
1139 | + bus_name = dbus.service.BusName('org.elementary.dexterapp', |
1140 | bus=dbus.SessionBus()) |
1141 | dbus.service.Object.__init__(self, bus_name, |
1142 | - '/org/elementary/dexterservice') |
1143 | - self.daemon = False |
1144 | - self.datadir = datadir |
1145 | - self.icons = icons |
1146 | - self.app = app |
1147 | - if self.app: |
1148 | - self.store = self.app.store |
1149 | - else: |
1150 | - self.store = models.setup_db(models.DATA_DIR) |
1151 | - |
1152 | - @dbus.service.method('org.elementary.dexterservice') |
1153 | - def ShowWindow(self): |
1154 | - """present the running dexter window, or start one""" |
1155 | - if not self.daemon: |
1156 | - self.app.window_main.present() |
1157 | - else: |
1158 | - self.app.window_main.show() |
1159 | - self.app.refresh_list() |
1160 | - self.daemon = False |
1161 | - |
1162 | - @dbus.service.method('org.elementary.dexterservice') |
1163 | - def NewContactDialog(self): |
1164 | - """present a new contact window""" |
1165 | - new = NewContactDialog(self.icons, self.datadir) |
1166 | - new.dialog.set_transient_for(self.app.window_main) |
1167 | - self.store.flush() |
1168 | - new.set_store(self.store) |
1169 | - new.called_from_dbus = True |
1170 | - new.dialog.show_all() |
1171 | - return True |
1172 | - |
1173 | - @dbus.service.method('org.elementary.dexterservice') |
1174 | - def CancelImport(self): |
1175 | - if self.app.q: |
1176 | - self.app.q.request_stop = True |
1177 | - |
1178 | - @dbus.service.method('org.elementary.dexterservice', in_signature='as') |
1179 | - def ImportVcardPath(self, paths): |
1180 | - self.app._import_vcard_path(paths) |
1181 | - |
1182 | - @dbus.service.method('org.elementary.dexterservice') |
1183 | - def CreateNewSimple(self, first_name, middle_name=None, last_name=None, |
1184 | - organization=None, birthdaystring=None, photo_path=None, email=None, |
1185 | - phone=None, note=None): |
1186 | - try: |
1187 | - birthdaylist = birthdaystring.split("-") |
1188 | - birthday = datetime.datetime(int(birthdaylist[0]), |
1189 | - int(birthdaylist[1]), |
1190 | - int(birthdaylist[2])) |
1191 | - except: |
1192 | - birthday = None |
1193 | - new_id = utilities.create_new_contact(first_name, middle_name, last_name, organization, |
1194 | - birthday, photo_path, [(email, None)], |
1195 | - [(phone, None)], [], [note], self.app.store) |
1196 | - self.app.store.commit() |
1197 | - self.ContactAdded(new_id) |
1198 | - return True |
1199 | - |
1200 | - @dbus.service.method('org.elementary.dexterservice') |
1201 | - def GetName(self, emailstring): |
1202 | - """take an email address and return the full name associated with it""" |
1203 | - try: |
1204 | - email = self.store.find(models.Email, |
1205 | - models.Email.address == emailstring).one() |
1206 | - name = email.contact.full_name |
1207 | - except: |
1208 | - name = "" |
1209 | - return name |
1210 | - |
1211 | - @dbus.service.method('org.elementary.dexterservice') |
1212 | - def GetEmailAddresses(self, full_name): |
1213 | - """take a full name and return a list of emails associated with it""" |
1214 | - emails = [] |
1215 | - name_pieces = full_name.split() |
1216 | - contact = self.store.find(models.Contact, |
1217 | - models.Contact.first_name == name_pieces[0], |
1218 | - models.Contact.last_name == name_pieces[-1]).any() |
1219 | - for email in contact.emails: |
1220 | - emails.append(email.address) |
1221 | - return emails |
1222 | - |
1223 | - @dbus.service.method('org.elementary.dexterservice', in_signature='s', out_signature='as') |
1224 | - def SearchContacts(self, search_string): |
1225 | - """search for contacts that contain a string |
1226 | - |
1227 | - takes a search string, and returns a list of name/email strings. |
1228 | - """ |
1229 | - results = [] |
1230 | - resultlist = [] |
1231 | - results = utilities.search_store(search_string, self.store) |
1232 | - for contact in results: |
1233 | - for email in contact.emails: |
1234 | - resultlist.append(contact.full_name + " <" + email.address + ">") |
1235 | - return resultlist |
1236 | - |
1237 | - @dbus.service.signal('org.elementary.dexterservice') |
1238 | - def ContactAdded(self, contact_id): |
1239 | - pass |
1240 | + '/org/elementary/dexterapp') |
1241 | |
1242 | === modified file 'dexter/backend/models.py' |
1243 | --- dexter/backend/models.py 2010-12-08 20:18:15 +0000 |
1244 | +++ dexter/backend/models.py 2010-12-20 23:23:53 +0000 |
1245 | @@ -15,8 +15,6 @@ |
1246 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
1247 | ### END LICENSE |
1248 | |
1249 | -#dexter_models.py |
1250 | - |
1251 | """ |
1252 | This module provides the classes that make up the data structures for the |
1253 | Dexter address book. It utilizes the Storm ORM for interfacing with the db. |
1254 | |
1255 | === added file 'dexter/backend/serverutils.py' |
1256 | --- dexter/backend/serverutils.py 1970-01-01 00:00:00 +0000 |
1257 | +++ dexter/backend/serverutils.py 2010-12-20 23:23:53 +0000 |
1258 | @@ -0,0 +1,173 @@ |
1259 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
1260 | +### BEGIN LICENSE |
1261 | +# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com> |
1262 | +# This program is free software: you can redistribute it and/or modify it |
1263 | +# under the terms of the GNU General Public License version 3, as published |
1264 | +# by the Free Software Foundation. |
1265 | +# |
1266 | +# This program is distributed in the hope that it will be useful, but |
1267 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1268 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1269 | +# PURPOSE. See the GNU General Public License for more details. |
1270 | +# |
1271 | +# You should have received a copy of the GNU General Public License along |
1272 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
1273 | +### END LICENSE |
1274 | + |
1275 | +import gio |
1276 | +import datetime |
1277 | +import enchant |
1278 | +import multiprocessing |
1279 | +from Queue import Empty |
1280 | +from storm.expr import Or |
1281 | +import dexter.backend.models as models |
1282 | + |
1283 | +QUEUE = multiprocessing.Queue() # this queue is used for saving photo files in a separate process |
1284 | + |
1285 | + |
1286 | +def create_new_contact(first_name, middle_name=None, last_name=None, |
1287 | + organization=None, birthday=None, photo_path=None, |
1288 | + emails=[], phones=[], addresses=[], notes=[], store=None): |
1289 | + """ |
1290 | + create a new contact in the database, all arguments should be unicode |
1291 | + strings except for birthday with sould be a datetime.date() object, or |
1292 | + it can also be a unicode string formatted yyyy-mm-dd. emails and phones |
1293 | + should be a list of tuples containing the value (address/number) and type. |
1294 | + addresses should be a list of dictionaries containing the address parts and |
1295 | + the type. Notes should be a list of unicode strings |
1296 | + """ |
1297 | + global QUEUE |
1298 | + if enchant.__ver_minor__ < 5: |
1299 | + dictionary = False |
1300 | + else: |
1301 | + dictionary = True |
1302 | + if store == None: |
1303 | + store = models.setup_db(models.DATA_DIR) # create a store if one wasn't provided |
1304 | + if dictionary: |
1305 | + try: |
1306 | + d = enchant.Dict() # for adding names to user spell check dictionary |
1307 | + dictionary = True |
1308 | + except enchant.DictNotFoundError: |
1309 | + dictionary = False |
1310 | + new = store.add(models.Contact(first_name)) |
1311 | + if dictionary: |
1312 | + d.add(first_name) |
1313 | + if middle_name: |
1314 | + new.middle_name = middle_name |
1315 | + if dictionary: |
1316 | + d.add(middle_name) |
1317 | + if last_name: |
1318 | + new.last_name = last_name |
1319 | + if dictionary: |
1320 | + d.add(last_name) |
1321 | + new.create_full_name() |
1322 | + if organization: |
1323 | + new.organization = organization |
1324 | + if dictionary: |
1325 | + d.add(organization) |
1326 | + if birthday: |
1327 | + birthdaylist = birthday.split("-") |
1328 | + new.birthday = datetime.datetime(int(birthdaylist[0]), |
1329 | + int(birthdaylist[1]), |
1330 | + int(birthdaylist[2])) |
1331 | + if photo_path: |
1332 | + store.flush() |
1333 | + dest_name = "/" + str(new.id) + str(new.first_name).lower() + \ |
1334 | + str(new.last_name).lower() |
1335 | + new.photo = unicode(dest_name) |
1336 | + QUEUE.put([dest_name, photo_path]) # put the photo data into the queue for processing in a separate process |
1337 | + if not multiprocessing.active_children(): |
1338 | + p = multiprocessing.Process(target=add_photo, args=(QUEUE,)) |
1339 | + p.start() # start the photo adding Process if it isn't already running |
1340 | + for address, type_ in emails: |
1341 | + if address != "None": |
1342 | + email = store.add(models.Email(address, type_)) |
1343 | + new.emails.add(email) |
1344 | + for number, type_ in phones: |
1345 | + if number != "None": |
1346 | + phone = store.add(models.Phone(number, type_)) |
1347 | + new.phones.add(phone) |
1348 | + for adict in addresses: |
1349 | + if adict["street"] or adict["city"] or adict["state"] or adict["zip"]: |
1350 | + address = store.add(models.Address(adict["street"], adict["city"], |
1351 | + adict["state"], adict["zip"], |
1352 | + adict["type"])) |
1353 | + new.addresses.add(address) |
1354 | + for text in notes: |
1355 | + if text: |
1356 | + note = store.add(models.Note(text)) |
1357 | + new.notes.add(note) |
1358 | + store.flush() |
1359 | + return new.id |
1360 | + |
1361 | + |
1362 | +def add_photo(q): |
1363 | + """create a photo for each item in q |
1364 | + |
1365 | + This is meant to be run in a separate process |
1366 | + """ |
1367 | + while True: |
1368 | + try: |
1369 | + item = q.get(True, 3) # wait for 3 seconds to get an item if q is empty |
1370 | + dest_name = item[0] |
1371 | + source_path = item[1] |
1372 | + dest_path = models.IMAGE_DIR + dest_name |
1373 | + source = gio.File(source_path) |
1374 | + dest = gio.File(dest_path) |
1375 | + source.copy(dest, flags=gio.FILE_COPY_OVERWRITE) |
1376 | + except Empty: |
1377 | + break # end the task if the queue is empty for more than 3 seconds |
1378 | + |
1379 | + |
1380 | +def filter_results(text, results, store): |
1381 | + """ |
1382 | + take a list of contacts and a text string, and return a filtered |
1383 | + list of contacts containing that string. |
1384 | + |
1385 | + """ |
1386 | + text = text.lower() |
1387 | + next_results = [] |
1388 | + for contact in results: |
1389 | + contact_object = store.find(models.Contact, models.Contact.id == int(contact["id"])).one() |
1390 | + if unicode(text) in contact["full_name"].lower(): |
1391 | + next_results.append(contact) |
1392 | + elif contact["organization"] != "None": |
1393 | + if unicode(text) in contact["organization"].lower(): |
1394 | + next_results.append(contact) |
1395 | + elif contact_object.emails.count() > 0: |
1396 | + for email in contact_object.emails: |
1397 | + if unicode(text) in email.address.lower(): |
1398 | + next_results.append(contact) |
1399 | + break |
1400 | + return next_results |
1401 | + |
1402 | + |
1403 | +def search_store(text, store): |
1404 | + results = [] |
1405 | + search = unicode("%" + text + "%") |
1406 | + contacts = store.find(models.Contact, Or(models.Contact.full_name.like(search), models.Contact.organization.like(search))) |
1407 | + emails = store.find(models.Email, models.Email.address.like(search)) |
1408 | + for contact in contacts: |
1409 | + results.append(contact) |
1410 | + for email in emails: |
1411 | + if email.contact not in results: |
1412 | + results.append(email.contact) |
1413 | + return results |
1414 | + |
1415 | +def delete_by_id(store, id_number): |
1416 | + """delete the given contact and it's children from the given store""" |
1417 | + contact = store.find(models.Contact, models.Contact.id == id_number).one() |
1418 | + for email in contact.emails: |
1419 | + store.remove(email) |
1420 | + for phone in contact.phones: |
1421 | + store.remove(phone) |
1422 | + for address in contact.addresses: |
1423 | + store.remove(address) |
1424 | + for note in contact.notes: |
1425 | + store.remove(note) |
1426 | +# try: |
1427 | +# os.remove(models.IMAGE_DIR + contact.photo) |
1428 | +# except: |
1429 | +# pass |
1430 | + store.remove(contact) |
1431 | + store.flush() |
1432 | |
1433 | === modified file 'dexter/backend/utilities.py' |
1434 | --- dexter/backend/utilities.py 2010-12-10 19:33:31 +0000 |
1435 | +++ dexter/backend/utilities.py 2010-12-20 23:23:53 +0000 |
1436 | @@ -13,23 +13,16 @@ |
1437 | # You should have received a copy of the GNU General Public License along |
1438 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
1439 | ### END LICENSE |
1440 | -#dexter_utilities.py |
1441 | |
1442 | """ |
1443 | This module provides various functions for managing Dexter contacts |
1444 | """ |
1445 | |
1446 | import os |
1447 | -import sys |
1448 | -import multiprocessing |
1449 | -from Queue import Empty |
1450 | import datetime |
1451 | -import enchant |
1452 | import vobject |
1453 | import base64 |
1454 | import string |
1455 | -import gio |
1456 | -from storm.expr import Or |
1457 | import threading |
1458 | |
1459 | import gettext |
1460 | @@ -38,8 +31,6 @@ |
1461 | |
1462 | import dexter.backend.models as models |
1463 | |
1464 | -QUEUE = multiprocessing.Queue() # this queue is used for saving photo files in a separate process |
1465 | - |
1466 | |
1467 | class ImportThread(threading.Thread): |
1468 | """a worker thread that parses a vcard string and puts the relevant data in a queue""" |
1469 | @@ -57,52 +48,56 @@ |
1470 | vstring = open(path, 'r').read() |
1471 | import_from_vcard_string(vstring, self.q) |
1472 | |
1473 | -def export_to_vcard(contacts, path): |
1474 | +def export_to_vcard(contacts, path, dbus): |
1475 | """export a list of contacts to the vcard file specified by path""" |
1476 | + GetEmails = dbus.get_dbus_method("GetEmails") |
1477 | + GetPhones = dbus.get_dbus_method("GetPhones") |
1478 | + GetAddresses = dbus.get_dbus_method("GetAddresses") |
1479 | + GetNotes = dbus.get_dbus_method("GetNotes") |
1480 | card_text ="" |
1481 | for contact in contacts: |
1482 | file_path = path |
1483 | card = vobject.vCard() |
1484 | card.add('n') |
1485 | - card.n.value = vobject.vcard.Name(given=str(contact.first_name)) |
1486 | - if contact.last_name: |
1487 | - card.n.value.family = contact.last_name |
1488 | - if contact.middle_name: |
1489 | - card.n.value.additional = contact.middle_name |
1490 | + card.n.value = vobject.vcard.Name(given=str(contact["first_name"])) |
1491 | + if contact["last_name"] != "None": |
1492 | + card.n.value.family = contact["last_name"] |
1493 | + if contact["middle_name"] != "None": |
1494 | + card.n.value.additional = contact["middle_name"] |
1495 | card.add('fn') |
1496 | - card.fn.value = str(contact.full_name) |
1497 | - if contact.organization: |
1498 | + card.fn.value = str(contact["full_name"]) |
1499 | + if contact["organization"] != "None": |
1500 | card.add('org') |
1501 | - card.org.value = [str(contact.organization)] |
1502 | - if contact.birthday: |
1503 | + card.org.value = [str(contact["organization"])] |
1504 | + if contact["birthday"] != "None": |
1505 | card.add('bday') |
1506 | - card.bday.value = str(contact.birthday) |
1507 | - for email in contact.emails: |
1508 | + card.bday.value = str(contact["birthday"]) |
1509 | + for email in GetEmails(int(contact["id"])): |
1510 | cardemail = card.add('email') |
1511 | - cardemail.value = str(email.address) |
1512 | - cardemail.type_param = str(email.email_type) |
1513 | - for phone in contact.phones: |
1514 | + cardemail.value = str(email[0]) |
1515 | + cardemail.type_param = str(email[1]) |
1516 | + for phone in GetPhones(int(contact["id"])): |
1517 | cardphone = card.add('tel') |
1518 | - cardphone.value = str(phone.number) |
1519 | - cardphone.type_param = str(phone.phone_type) |
1520 | - for address in contact.addresses: |
1521 | + cardphone.value = str(phone[0]) |
1522 | + cardphone.type_param = str(phone[1]) |
1523 | + for address in GetAddresses(int(contact["id"])): |
1524 | cardadr = card.add('adr') |
1525 | cardadr.value = vobject.vcard.Address() |
1526 | - if address.street: |
1527 | - cardadr.value.street=address.street |
1528 | - if address.city: |
1529 | - cardadr.value.city=address.city |
1530 | - if address.state: |
1531 | - cardadr.value.region=address.state |
1532 | - if address.zip: |
1533 | - cardadr.value.code=address.zip |
1534 | - cardadr.type_param = str(address.address_type) |
1535 | - for note in contact.notes: |
1536 | + if address["street"] != "None": |
1537 | + cardadr.value.street=address["street"] |
1538 | + if address["city"] != "None": |
1539 | + cardadr.value.city=address["city"] |
1540 | + if address["state"] != "None": |
1541 | + cardadr.value.region=address["state"] |
1542 | + if address["zip"] != "None": |
1543 | + cardadr.value.code=address["zip"] |
1544 | + cardadr.type_param = str(address["type"]) |
1545 | + for note in GetNotes(int(contact["id"])): |
1546 | cardnote = card.add('note') |
1547 | - cardnote.value = str(note.text) |
1548 | - if contact.photo: |
1549 | + cardnote.value = str(note) |
1550 | + if contact["photo"] != "None": |
1551 | b64 = base64.b64encode(open(models.IMAGE_DIR + \ |
1552 | - str(contact.photo), "rb").read()) |
1553 | + str(contact["photo"]), "rb").read()) |
1554 | card.add("photo") |
1555 | card.photo.value = b64 |
1556 | card.photo.encoding_param = "BASE64" |
1557 | @@ -123,12 +118,12 @@ |
1558 | for card in components: |
1559 | if q.request_stop: |
1560 | break |
1561 | - first_name = None |
1562 | - last_name = None |
1563 | - middle_name = None |
1564 | - organization = None |
1565 | - birthday = None |
1566 | - photo_path = None |
1567 | + first_name = "None" |
1568 | + last_name = "None" |
1569 | + middle_name = "None" |
1570 | + organization = "None" |
1571 | + birthday = "None" |
1572 | + photo_path = "None" |
1573 | notes = [] |
1574 | emails = [] |
1575 | addresses = [] |
1576 | @@ -164,9 +159,7 @@ |
1577 | if birthdaylist[2] == u"00": |
1578 | birthdaylist[2] = u"01" |
1579 | try: |
1580 | - birthday = datetime.datetime(int(birthdaylist[0]), |
1581 | - int(birthdaylist[1]), |
1582 | - int(birthdaylist[2])) |
1583 | + birthday = "-".join(birthdaylist) |
1584 | except: # TODO: replace with proper |
1585 | print _("Could not import birthday") # error handling and logging |
1586 | if hasattr(card, 'photo'): |
1587 | @@ -178,7 +171,7 @@ |
1588 | photo_file.write(card.photo.value) # by create_contact later |
1589 | photo_file.close() |
1590 | except: |
1591 | - photo_path = None # TODO: replace with proper |
1592 | + photo_path = "None" # TODO: replace with proper |
1593 | print _("couldn't add photo") # error handling and logging |
1594 | if hasattr(card, 'note'): |
1595 | notes.append(card.note.value.strip()) |
1596 | @@ -191,7 +184,7 @@ |
1597 | else: |
1598 | email_type = child.params[u'TYPE'][0].strip().title() |
1599 | else: |
1600 | - email_type = None |
1601 | + email_type = "Home" |
1602 | emails.append((child.value.strip(), email_type)) |
1603 | if child.name == u'ADR': |
1604 | if hasattr(child, 'value'): |
1605 | @@ -199,8 +192,8 @@ |
1606 | if hasattr(child, 'type_param'): |
1607 | adict["type"] = child.type_param.title() |
1608 | else: |
1609 | - adict["type"] = None |
1610 | - adict["street"] = child.value.street |
1611 | + adict["type"] = "Home" |
1612 | + adict["street"] = "".join(child.value.street) |
1613 | adict["city"] = child.value.city.strip() |
1614 | adict["state"] = child.value.region.strip() |
1615 | adict["zip"] = child.value.code.strip() |
1616 | @@ -210,78 +203,84 @@ |
1617 | if hasattr(child, 'type_param'): |
1618 | phone_type = child.type_param.strip().title() |
1619 | else: |
1620 | - phone_type = None |
1621 | + phone_type = "Home" |
1622 | phones.append((child.value.strip(), phone_type)) |
1623 | q.put([first_name, middle_name, last_name, organization, birthday, photo_path, |
1624 | emails, phones, addresses, notes], True, 2) |
1625 | |
1626 | |
1627 | -def merge_contacts(contacts, store): |
1628 | +def merge_contacts(contacts, dbus): |
1629 | + GetEmails = dbus.get_dbus_method("GetEmails") |
1630 | + GetPhones = dbus.get_dbus_method("GetPhones") |
1631 | + GetAddresses = dbus.get_dbus_method("GetAddresses") |
1632 | + GetNotes = dbus.get_dbus_method("GetNotes") |
1633 | + CreateContact = dbus.get_dbus_method("CreateContact", dbus_interface='org.elementary.dexterserver') |
1634 | + DeleteContact = dbus.get_dbus_method("DeleteContact") |
1635 | firstnames = [] |
1636 | middlenames = [] |
1637 | lastnames = [] |
1638 | organizations = [] |
1639 | birthdays = [] |
1640 | - photo_path = None |
1641 | + photo_path = "None" |
1642 | emails = [] |
1643 | phones = [] |
1644 | addresses = [] |
1645 | note = u"" |
1646 | for contact in contacts: |
1647 | - if contact.first_name: |
1648 | - if not contact.first_name in firstnames: |
1649 | - firstnames.append(contact.first_name) |
1650 | - if contact.middle_name: |
1651 | - if not contact.middle_name in middlenames: |
1652 | - middlenames.append(contact.middle_name) |
1653 | - if contact.last_name: |
1654 | - if not contact.last_name in lastnames: |
1655 | - lastnames.append(contact.last_name) |
1656 | - if contact.organization: |
1657 | - if not contact.organization in organizations: |
1658 | - organizations.append(contact.organization) |
1659 | - if contact.birthday: |
1660 | - if not contact.birthday in birthdays: |
1661 | - birthdays.append(contact.birthday) |
1662 | - if contact.photo: |
1663 | - if not photo_path: |
1664 | - photo_path = models.IMAGE_DIR + contact.photo |
1665 | + if contact["first_name"] != "None": |
1666 | + if not contact["first_name"] in firstnames: |
1667 | + firstnames.append(contact["first_name"]) |
1668 | + if contact["middle_name"] != "None": |
1669 | + if not contact["middle_name"] in middlenames: |
1670 | + middlenames.append(contact["middle_name"]) |
1671 | + if contact["last_name"] != "None": |
1672 | + if not contact["last_name"] in lastnames: |
1673 | + lastnames.append(contact["last_name"]) |
1674 | + if contact["organization"] != "None": |
1675 | + if not contact["organization"] in organizations: |
1676 | + organizations.append(contact["organization"]) |
1677 | + if contact["birthday"] != "None": |
1678 | + if not contact["birthday"] in birthdays: |
1679 | + birthdays.append(contact["birthday"]) |
1680 | + if contact["photo"] != "None": |
1681 | + if photo_path == "None": |
1682 | + photo_path = models.IMAGE_DIR + contact["photo"] |
1683 | else: |
1684 | - thissize = os.path.getsize(models.IMAGE_DIR + contact.photo) |
1685 | + thissize = os.path.getsize(models.IMAGE_DIR + contact["photo"]) |
1686 | oldsize = os.path.getsize(photo_path) |
1687 | if thissize > oldsize: |
1688 | - photo_path = models.IMAGE_DIR + contact.photo |
1689 | - for email in contact.emails: |
1690 | - emails.append((email.address, email.email_type)) |
1691 | - for phone in contact.phones: |
1692 | - phones.append((phone.number, phone.phone_type)) |
1693 | - for address in contact.addresses: |
1694 | + photo_path = models.IMAGE_DIR + contact["photo"] |
1695 | + for email in GetEmails(int(contact["id"])): |
1696 | + emails.append((email[0], email[1])) |
1697 | + for phone in GetPhones(int(contact["id"])): |
1698 | + phones.append((phone[0], phone[1])) |
1699 | + for address in GetAddresses(int(contact["id"])): |
1700 | address_dict = dict() |
1701 | - if address.street: |
1702 | - address_dict["street"] = address.street |
1703 | - else: |
1704 | - address_dict["street"] = None |
1705 | - if address.city: |
1706 | - address_dict["city"] = address.city |
1707 | - else: |
1708 | - address_dict["city"] = None |
1709 | - if address.state: |
1710 | - address_dict["state"] = address.state |
1711 | - else: |
1712 | - address_dict["state"] = None |
1713 | - if address.zip: |
1714 | - address_dict["zip"] = address.zip |
1715 | - else: |
1716 | - address_dict["zip"] = None |
1717 | - if address.address_type: |
1718 | - address_dict["type"] = address.address_type |
1719 | - else: |
1720 | - address_dict["type"] = None |
1721 | + if address["street"] != "None": |
1722 | + address_dict["street"] = address["street"] |
1723 | + else: |
1724 | + address_dict["street"] = "None" |
1725 | + if address["city"] != "None": |
1726 | + address_dict["city"] = address["city"] |
1727 | + else: |
1728 | + address_dict["city"] = "None" |
1729 | + if address["state"] != "None": |
1730 | + address_dict["state"] = address["state"] |
1731 | + else: |
1732 | + address_dict["state"] = "None" |
1733 | + if address["zip"] != "None": |
1734 | + address_dict["zip"] = address["zip"] |
1735 | + else: |
1736 | + address_dict["zip"] = "None" |
1737 | + if address["type"] != "None": |
1738 | + address_dict["type"] = address["type"] |
1739 | + else: |
1740 | + address_dict["type"] = "None" |
1741 | addresses.append(address_dict) |
1742 | - for anote in contact.notes: |
1743 | - if anote.text and anote.text.strip() != "": |
1744 | - note += (anote.text.strip() + "\n") |
1745 | - delete_contact(store, contact) |
1746 | + for anote in GetNotes(int(contact["id"])): |
1747 | + if anote and anote.strip() != "": |
1748 | + note += (anote.strip() + "\n") |
1749 | + DeleteContact(int(contact["id"])) |
1750 | if len(firstnames) > 1 or len(middlenames) > 1 or len(lastnames) > 1 or \ |
1751 | len(organizations) > 1 or len(birthdays) > 1: |
1752 | data = {"first" : firstnames, "middle" : middlenames, "last" : lastnames, |
1753 | @@ -291,161 +290,16 @@ |
1754 | return data |
1755 | else: |
1756 | if not firstnames: |
1757 | - firstnames = [None] |
1758 | + firstnames = ["None"] |
1759 | if not middlenames: |
1760 | - middlenames = [None] |
1761 | + middlenames = ["None"] |
1762 | if not lastnames: |
1763 | - lastnames = [None] |
1764 | + lastnames = ["None"] |
1765 | if not organizations: |
1766 | - organizations = [None] |
1767 | + organizations = ["None"] |
1768 | if not birthdays: |
1769 | - birthdays = [None] |
1770 | - new_id = create_new_contact(firstnames[0], middlenames[0], lastnames[0], |
1771 | - organizations[0], birthdays[0], photo_path, |
1772 | - emails, phones, addresses, [note], store) |
1773 | + birthdays = ["None"] |
1774 | + new_id = CreateContact(firstnames[0], middlenames[0], lastnames[0], |
1775 | + organizations[0], birthdays[0], photo_path, |
1776 | + emails, phones, addresses, [note]) |
1777 | return new_id |
1778 | - |
1779 | - |
1780 | -def filter_results(text, results): |
1781 | - """ |
1782 | - take a list of contacts and a text string, and return a filtered |
1783 | - list of contacts containing that string. |
1784 | - |
1785 | - """ |
1786 | - text = text.lower() |
1787 | - next_results = [] |
1788 | - for contact in results: |
1789 | - if unicode(text) in contact.full_name.lower(): |
1790 | - next_results.append(contact) |
1791 | - elif contact.organization: |
1792 | - if unicode(text) in contact.organization.lower(): |
1793 | - next_results.append(contact) |
1794 | - elif contact.emails.count() > 0: |
1795 | - for email in contact.emails: |
1796 | - if unicode(text) in email.address.lower(): |
1797 | - next_results.append(contact) |
1798 | - break |
1799 | - return next_results |
1800 | - |
1801 | - |
1802 | -def search_store(text, store): |
1803 | - results = [] |
1804 | - search = unicode("%" + text + "%") |
1805 | - contacts = store.find(models.Contact, Or(models.Contact.full_name.like(search), models.Contact.organization.like(search))) |
1806 | - emails = store.find(models.Email, models.Email.address.like(search)) |
1807 | - for contact in contacts: |
1808 | - results.append(contact) |
1809 | - for email in emails: |
1810 | - if email.contact not in results: |
1811 | - results.append(email.contact) |
1812 | - return results |
1813 | - |
1814 | - |
1815 | -def create_new_contact(first_name, middle_name=None, last_name=None, organization=None, |
1816 | - birthday=None, photo_path=None, emails=[], |
1817 | - phones=[], addresses=[], notes=[], store=None): |
1818 | - """ |
1819 | - create a new contact in the database, all arguments should be unicode |
1820 | - strings except for birthday with sould be a datetime.date() object, or |
1821 | - it can also be a unicode string formatted yyyy-mm-dd. emails and phones |
1822 | - should be a list of tuples containing the value (address/number) and type. |
1823 | - addresses should be a list of dictionaries containing the address parts and |
1824 | - the type. Notes should be a list of unicode strings |
1825 | - """ |
1826 | - global QUEUE |
1827 | - if enchant.__ver_minor__ < 5: |
1828 | - dictionary = False |
1829 | - else: |
1830 | - dictionary = True |
1831 | - if store == None: |
1832 | - store = models.setup_db(models.DATA_DIR) # create a store if one wasn't provided |
1833 | - if dictionary: |
1834 | - try: |
1835 | - d = enchant.Dict() # for adding names to user spell check dictionary |
1836 | - dictionary = True |
1837 | - except enchant.DictNotFoundError: |
1838 | - dictionary = False |
1839 | - new = store.add(models.Contact(first_name)) |
1840 | - if dictionary: |
1841 | - d.add(first_name) |
1842 | - if middle_name: |
1843 | - new.middle_name = middle_name |
1844 | - if dictionary: |
1845 | - d.add(middle_name) |
1846 | - if last_name: |
1847 | - new.last_name = last_name |
1848 | - if dictionary: |
1849 | - d.add(last_name) |
1850 | - new.create_full_name() |
1851 | - if organization: |
1852 | - new.organization = organization |
1853 | - if dictionary: |
1854 | - d.add(organization) |
1855 | - if birthday: |
1856 | - new.birthday = birthday |
1857 | - if photo_path: |
1858 | - store.flush() |
1859 | - dest_name = "/" + str(new.id) + str(new.first_name).lower() + \ |
1860 | - str(new.last_name).lower() |
1861 | - new.photo = unicode(dest_name) |
1862 | - QUEUE.put([dest_name, photo_path]) # put the photo data into the queue for processing in a separate process |
1863 | - if not multiprocessing.active_children(): |
1864 | - p = multiprocessing.Process(target=add_photo, args=(QUEUE,)) |
1865 | - p.start() # start the photo adding Process if it isn't already running |
1866 | - for address, type_ in emails: |
1867 | - if address: |
1868 | - email = store.add(models.Email(address, type_)) |
1869 | - new.emails.add(email) |
1870 | - for number, type_ in phones: |
1871 | - if number: |
1872 | - phone = store.add(models.Phone(number, type_)) |
1873 | - new.phones.add(phone) |
1874 | - for adict in addresses: |
1875 | - if adict["street"] or adict["city"] or adict["state"] or adict["zip"]: |
1876 | - address = store.add(models.Address(adict["street"], adict["city"], |
1877 | - adict["state"], adict["zip"], |
1878 | - adict["type"])) |
1879 | - new.addresses.add(address) |
1880 | - for text in notes: |
1881 | - if text: |
1882 | - note = store.add(models.Note(text)) |
1883 | - new.notes.add(note) |
1884 | - store.flush() |
1885 | - return new.id |
1886 | - |
1887 | - |
1888 | -def add_photo(q): |
1889 | - """create a photo for each item in q |
1890 | - |
1891 | - This is meant to be run in a separate process |
1892 | - """ |
1893 | - while True: |
1894 | - try: |
1895 | - item = q.get(True, 3) # wait for 3 seconds to get an item if q is empty |
1896 | - dest_name = item[0] |
1897 | - source_path = item[1] |
1898 | - dest_path = models.IMAGE_DIR + dest_name |
1899 | - source = gio.File(source_path) |
1900 | - dest = gio.File(dest_path) |
1901 | - source.copy(dest, flags=gio.FILE_COPY_OVERWRITE) |
1902 | - except Empty: |
1903 | - break # end the task if the queue is empty for more than 3 seconds |
1904 | - |
1905 | - |
1906 | -def delete_contact(store, contact): |
1907 | - """delete the given contact and it's children from the given store""" |
1908 | - for email in contact.emails: |
1909 | - store.remove(email) |
1910 | - for phone in contact.phones: |
1911 | - store.remove(phone) |
1912 | - for address in contact.addresses: |
1913 | - store.remove(address) |
1914 | - for note in contact.notes: |
1915 | - store.remove(note) |
1916 | -# try: |
1917 | -# os.remove(models.IMAGE_DIR + contact.photo) |
1918 | -# except: |
1919 | -# pass |
1920 | - store.remove(contact) |
1921 | - store.flush() |
1922 | - |
1923 | |
1924 | === modified file 'dexter/enums.py' |
1925 | --- dexter/enums.py 2010-12-08 07:34:05 +0000 |
1926 | +++ dexter/enums.py 2010-12-20 23:23:53 +0000 |
1927 | @@ -14,4 +14,4 @@ |
1928 | # this program; if not, write to the Free Software Foundation, Inc., |
1929 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
1930 | |
1931 | -(RUN_DAEMON, RUN_CONTACT_DIALOG, RUN_APP) = range(3) |
1932 | +(RUN_CONTACT_DIALOG, RUN_APP) = range(2) |
1933 | |
1934 | === modified file 'dexter/frontend/dialogs/merge.py' |
1935 | --- dexter/frontend/dialogs/merge.py 2010-12-12 20:24:32 +0000 |
1936 | +++ dexter/frontend/dialogs/merge.py 2010-12-20 23:23:53 +0000 |
1937 | @@ -71,7 +71,7 @@ |
1938 | self.first_combo.connect("changed", self._on_combo_changed, "first") |
1939 | self.vbox1.pack_start(self.first_combo, expand=False, fill=True, padding=5) |
1940 | elif not self.data["first"]: |
1941 | - self.data["first"] = [None] |
1942 | + self.data["first"] = ["None"] |
1943 | if len(self.data["middle"]) > 1: |
1944 | self.middle_combo = gtk.combo_box_new_text() |
1945 | self.middle_combo.append_text(_("Middle Name... ")) |
1946 | @@ -81,7 +81,7 @@ |
1947 | self.middle_combo.connect("changed", self._on_combo_changed, "middle") |
1948 | self.vbox1.pack_start(self.middle_combo, expand=False, fill=True, padding=5) |
1949 | elif not self.data["middle"]: |
1950 | - self.data["middle"] = [None] |
1951 | + self.data["middle"] = ["None"] |
1952 | if len(self.data["last"]) > 1: |
1953 | self.last_combo = gtk.combo_box_new_text() |
1954 | self.last_combo.append_text(_("Last Name... ")) |
1955 | @@ -91,7 +91,7 @@ |
1956 | self.last_combo.connect("changed", self._on_combo_changed, "last") |
1957 | self.vbox1.pack_start(self.last_combo, expand=False, fill=True, padding=5) |
1958 | elif not self.data["last"]: |
1959 | - self.data["last"] = [None] |
1960 | + self.data["last"] = ["None"] |
1961 | if len(self.data["org"]) > 1: |
1962 | self.org_combo = gtk.combo_box_new_text() |
1963 | self.org_combo.append_text(_("Organization... ")) |
1964 | @@ -101,17 +101,21 @@ |
1965 | self.org_combo.connect("changed", self._on_combo_changed, "org") |
1966 | self.vbox1.pack_start(self.org_combo, expand=False, fill=True, padding=5) |
1967 | elif not self.data["org"]: |
1968 | - self.data["org"] = [None] |
1969 | + self.data["org"] = ["None"] |
1970 | if len(self.data["birthday"]) > 1: |
1971 | self.birthday_combo = gtk.combo_box_new_text() |
1972 | self.birthday_combo.append_text(_("Birthday... ")) |
1973 | for name in self.data["birthday"]: |
1974 | - self.birthday_combo.append_text(name.strftime("%B %d, %Y")) |
1975 | + birthdaylist = name.split("-") |
1976 | + birthday = datetime.datetime(int(birthdaylist[0]), |
1977 | + int(birthdaylist[1]), |
1978 | + int(birthdaylist[2])) |
1979 | + self.birthday_combo.append_text(birthday.strftime("%B %d, %Y")) |
1980 | self.birthday_combo.set_active(0) |
1981 | self.birthday_combo.connect("changed", self._on_combo_changed, "birthday") |
1982 | self.vbox1.pack_start(self.birthday_combo, expand=False, fill=True, padding=5) |
1983 | elif not self.data["birthday"]: |
1984 | - self.data["birthday"] = [None] |
1985 | + self.data["birthday"] = ["None"] |
1986 | self.vbox1.show_all() |
1987 | |
1988 | def all_combos_chosen(self): |
1989 | @@ -126,8 +130,10 @@ |
1990 | def _on_combo_changed(self, widget, param): |
1991 | if widget.get_active() != 0: |
1992 | if param == "birthday": |
1993 | - self.data[param] = [datetime.datetime.strptime(widget.get_active_text(), |
1994 | - "%B %d, %Y").date()] |
1995 | + date = datetime.datetime.strptime(widget.get_active_text(), |
1996 | + "%B %d, %Y").date() |
1997 | + datestring = str(date.year) + "-" + str(date.month) + "-" + str(date.day) |
1998 | + self.data[param] = [datestring] |
1999 | else: |
2000 | self.data[param] = [unicode(widget.get_active_text())] |
2001 | if self.all_combos_chosen(): |
2002 | |
2003 | === modified file 'dexter/frontend/dialogs/new_contact.py' |
2004 | --- dexter/frontend/dialogs/new_contact.py 2010-12-13 19:09:44 +0000 |
2005 | +++ dexter/frontend/dialogs/new_contact.py 2010-12-20 23:23:53 +0000 |
2006 | @@ -18,10 +18,9 @@ |
2007 | import glib |
2008 | import gobject |
2009 | import os |
2010 | -import sys |
2011 | +import dbus |
2012 | import datetime |
2013 | |
2014 | -from dexter.helpers import get_builder |
2015 | |
2016 | from dexter.frontend.utils import setup_ui |
2017 | from dexter.frontend.widgets.glade import DexterContactDialog |
2018 | @@ -45,7 +44,7 @@ |
2019 | self.called_from_dbus = False |
2020 | self.edit = False |
2021 | self.new_contact_id = None |
2022 | - self.store = models.setup_db(models.DATA_DIR) |
2023 | + self._setup_dbus() |
2024 | |
2025 | self._setup_fields() |
2026 | self._setup_email() |
2027 | @@ -58,6 +57,17 @@ |
2028 | |
2029 | # Private Functions |
2030 | |
2031 | + def _setup_dbus(self): |
2032 | + self.session_bus = dbus.SessionBus() |
2033 | + self.dbus = self.session_bus.get_object("org.elementary.dexterserver", |
2034 | + "/org/elementary/dexterserver") |
2035 | + self.CreateContact = self.dbus.get_dbus_method("CreateContact", dbus_interface='org.elementary.dexterserver') |
2036 | + self.DeleteContact = self.dbus.get_dbus_method("DeleteContact") |
2037 | + self.GetEmails = self.dbus.get_dbus_method("GetEmails") |
2038 | + self.GetPhones = self.dbus.get_dbus_method("GetPhones") |
2039 | + self.GetAddresses = self.dbus.get_dbus_method("GetAddresses") |
2040 | + self.GetNotes = self.dbus.get_dbus_method("GetNotes") |
2041 | + |
2042 | def _setup_fields(self): |
2043 | self.textbuffer_notes = self.builder.get_object("textbuffer_notes") |
2044 | |
2045 | @@ -166,21 +176,20 @@ |
2046 | return email_hbox |
2047 | |
2048 | def _new_contact(self): |
2049 | - firstname = None |
2050 | - middlename = None |
2051 | - lastname = None |
2052 | - organization = None |
2053 | + firstname = "None" |
2054 | + middlename = "None" |
2055 | + lastname = "None" |
2056 | + organization = "None" |
2057 | emails = [] |
2058 | phones = [] |
2059 | addresses = [] |
2060 | - note = None |
2061 | - birthday = None |
2062 | + note = "None" |
2063 | + birthday = "None" |
2064 | |
2065 | # TODO: should we allow contact creation with no first name and |
2066 | # use email to guess first name? |
2067 | if not self.firstname_entry.get_text(): |
2068 | return |
2069 | - |
2070 | if self.firstname_entry.get_text(): |
2071 | firstname = unicode(self.firstname_entry.get_text().strip()) |
2072 | if self.middlename_entry.get_text(): |
2073 | @@ -191,17 +200,19 @@ |
2074 | organization = unicode(self.organization_entry.get_text().strip()) |
2075 | |
2076 | try: |
2077 | - month = self.months[self.combobox_month.get_active()] |
2078 | - birthday = datetime.date(int(self.spinbutton_year.get_value()), |
2079 | - month, |
2080 | - int(self.spinbutton_day.get_value())) |
2081 | + month = self.combobox_month.get_active()+1 |
2082 | + birthday = datetime.date(int(self.spinbutton_year.get_value()), |
2083 | + month, |
2084 | + int(self.spinbutton_day.get_value())) |
2085 | except (KeyError, TypeError, ValueError): |
2086 | pass |
2087 | # FIXME: If someone wanted to set a newborn as a contact, this wouldn't work |
2088 | # Birthday hasn't been modified |
2089 | if birthday == datetime.date.today(): |
2090 | - birthday = None |
2091 | - |
2092 | + birthday = "None" |
2093 | + elif birthday != "None": |
2094 | + birthday = str(int(self.spinbutton_year.get_value())) + "-" + str(month) + "-" + str(int(self.spinbutton_day.get_value())) |
2095 | + |
2096 | for emailbox in self.email_boxes: |
2097 | if emailbox.entry.get_text(): |
2098 | email = unicode(emailbox.entry.get_text().strip()) |
2099 | @@ -210,8 +221,9 @@ |
2100 | if emailbox.combo.get_active_text(): |
2101 | email_type = unicode(emailbox.combo.get_active_text()) |
2102 | else: |
2103 | - email_type = None |
2104 | - emails.append((email, email_type)) |
2105 | + email_type = "Home" |
2106 | + if email: |
2107 | + emails.append((email, email_type)) |
2108 | |
2109 | for phonebox in self.phone_boxes: |
2110 | if phonebox.entry.get_text(): |
2111 | @@ -221,42 +233,42 @@ |
2112 | if phonebox.combo.get_active_text(): |
2113 | phone_type = unicode(phonebox.combo.get_active_text()) |
2114 | else: |
2115 | - phone_type = None |
2116 | - phones.append((phone, phone_type)) |
2117 | + phone_type = "Home" |
2118 | + if phone: |
2119 | + phones.append((phone, phone_type)) |
2120 | |
2121 | for addressbox in self.address_boxes: |
2122 | address_dict = dict() |
2123 | if addressbox.entry_street.get_text(): |
2124 | address_dict["street"] = unicode(addressbox.entry_street.get_text().strip()) |
2125 | else: |
2126 | - address_dict["street"] = None |
2127 | + address_dict["street"] = "None" |
2128 | if addressbox.entry_city.get_text(): |
2129 | address_dict["city"] = unicode(addressbox.entry_city.get_text().strip()) |
2130 | else: |
2131 | - address_dict["city"] = None |
2132 | + address_dict["city"] = "None" |
2133 | if addressbox.entry_state.get_text() != addressbox.entry_state.hint_string: |
2134 | address_dict["state"] = unicode(addressbox.entry_state.get_text().strip()) |
2135 | else: |
2136 | - address_dict["state"] = None |
2137 | + address_dict["state"] = "None" |
2138 | if addressbox.entry_zip.get_text() != addressbox.entry_zip.hint_string: |
2139 | address_dict["zip"] = unicode(addressbox.entry_zip.get_text().strip()) |
2140 | else: |
2141 | - address_dict["zip"] = None |
2142 | + address_dict["zip"] = "None" |
2143 | if addressbox.combo.get_active_text(): |
2144 | address_dict["type"] = unicode(addressbox.combo.get_active_text()) |
2145 | else: |
2146 | - address_dict["type"] = None |
2147 | + address_dict["type"] = "None" |
2148 | addresses.append((address_dict)) |
2149 | startiter, enditer = self.textbuffer_notes.get_bounds() |
2150 | if self.textbuffer_notes.get_text(startiter, enditer): |
2151 | note = unicode(self.textbuffer_notes.get_text(startiter, enditer)) |
2152 | - |
2153 | + if not self.photo_path: |
2154 | + self.photo_path = "None" |
2155 | # Add the new contact to the DB and store the ID |
2156 | - self.new_contact_id = utilities.create_new_contact(firstname, |
2157 | - middlename, lastname, organization, |
2158 | - birthday, self.photo_path, emails, phones, |
2159 | - addresses, [note], self.store) |
2160 | - self.store.commit() |
2161 | + self.new_contact_id = self.CreateContact(firstname, middlename, lastname, |
2162 | + organization, birthday, self.photo_path, |
2163 | + emails, phones, addresses, [note]) |
2164 | |
2165 | # Private Callbacks |
2166 | |
2167 | @@ -362,7 +374,7 @@ |
2168 | Called before the dialog returns gtk.RESONSE_OK from run(). |
2169 | """ |
2170 | if self.edit == True: |
2171 | - utilities.delete_contact(self.store, self.contact) |
2172 | + self.DeleteContact(int(self.contact["id"])) |
2173 | self._new_contact() |
2174 | if self.standalone: |
2175 | gtk.main_quit() |
2176 | @@ -383,77 +395,75 @@ |
2177 | |
2178 | def populate(self): |
2179 | """populate the various fields, used for editing a contact""" |
2180 | - if self.contact.first_name: |
2181 | - self.firstname_entry.unhint(self.contact.first_name) |
2182 | - if self.contact.middle_name: |
2183 | - self.middlename_entry.unhint(self.contact.middle_name) |
2184 | - if self.contact.last_name: |
2185 | - self.lastname_entry.unhint(self.contact.last_name) |
2186 | - if self.contact.organization: |
2187 | - self.organization_entry.unhint(self.contact.organization) |
2188 | - if self.contact.photo: |
2189 | - self.photo_path = models.IMAGE_DIR + self.contact.photo |
2190 | + if self.contact["first_name"] != "None": |
2191 | + self.firstname_entry.unhint(self.contact["first_name"]) |
2192 | + if self.contact["middle_name"] != "None": |
2193 | + self.middlename_entry.unhint(self.contact["middle_name"]) |
2194 | + if self.contact["last_name"] != "None": |
2195 | + self.lastname_entry.unhint(self.contact["last_name"]) |
2196 | + if self.contact["organization"] != "None": |
2197 | + self.organization_entry.unhint(self.contact["organization"]) |
2198 | + if self.contact["photo"] != "None": |
2199 | + self.photo_path = models.IMAGE_DIR + self.contact["photo"] |
2200 | pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.photo_path, |
2201 | 50, 50) |
2202 | self.image_photo.set_from_pixbuf(pixbuf) |
2203 | - for email in self.contact.emails: |
2204 | + for email in self.GetEmails(int(self.contact["id"])): |
2205 | emailbox = self._new_email_field() |
2206 | - if email.address: |
2207 | - emailbox.entry.set_text(email.address) |
2208 | + if email[0] != "None": |
2209 | + emailbox.entry.set_text(email[0]) |
2210 | try: |
2211 | - i = self.email_types.index(str(email.email_type)) |
2212 | + i = self.email_types.index(str(email[1])) |
2213 | emailbox.combo.set_active(i) |
2214 | except ValueError: |
2215 | # For custom labels |
2216 | - emailbox.combo.insert_text(0, email.email_type) |
2217 | + emailbox.combo.insert_text(0, email[1]) |
2218 | emailbox.combo.set_active(0) |
2219 | |
2220 | - for phone in self.contact.phones: |
2221 | + for phone in self.GetPhones(int(self.contact["id"])): |
2222 | phonebox = self._new_phone_field() |
2223 | - if phone.number: |
2224 | - phonebox.entry.set_text(phone.number) |
2225 | + if phone[0] != "None": |
2226 | + phonebox.entry.set_text(phone[0]) |
2227 | try: |
2228 | - i = self.phone_types.index(str(phone.phone_type)) |
2229 | + i = self.phone_types.index(str(phone[1])) |
2230 | phonebox.combo.set_active(i) |
2231 | except ValueError: |
2232 | # For custom labels |
2233 | - phonebox.combo.insert_text(0, phone.phone_type) |
2234 | + phonebox.combo.insert_text(0, phone[1]) |
2235 | phonebox.combo.set_active(0) |
2236 | - for address in self.contact.addresses: |
2237 | + for address in self.GetAddresses(int(self.contact["id"])): |
2238 | addressbox = self._new_address_field() |
2239 | - if address.street: |
2240 | - addressbox.entry_street.unhint(address.street) |
2241 | - if address.city: |
2242 | - addressbox.entry_city.unhint(address.city) |
2243 | - if address.state: |
2244 | - addressbox.entry_state.unhint(address.state) |
2245 | - if address.zip: |
2246 | - addressbox.entry_zip.unhint(address.zip) |
2247 | - if address.address_type: |
2248 | + if address["street"] != "None": |
2249 | + addressbox.entry_street.unhint(address["street"]) |
2250 | + if address["city"] != "None": |
2251 | + addressbox.entry_city.unhint(address["city"]) |
2252 | + if address["state"] != "None": |
2253 | + addressbox.entry_state.unhint(address["state"]) |
2254 | + if address["zip"] != "None": |
2255 | + addressbox.entry_zip.unhint(address["zip"]) |
2256 | + if address["type"] != "None": |
2257 | try: |
2258 | - i = self.address_types.index(str(address.address_type)) |
2259 | + i = self.address_types.index(str(address["type"])) |
2260 | addressbox.combo.set_active(i) |
2261 | # For custom labels |
2262 | except ValueError: |
2263 | - addressbox.combo.insert_text(0, address.address_type) |
2264 | + addressbox.combo.insert_text(0, address["type"]) |
2265 | addressbox.combo.set_active(0) |
2266 | - if self.contact.birthday: |
2267 | - self.combobox_month.set_active(self.contact.birthday.month-1) |
2268 | - self.spinbutton_day.set_value(self.contact.birthday.day) |
2269 | - self.spinbutton_year.set_value(self.contact.birthday.year) |
2270 | - if self.contact.notes.one(): |
2271 | - self.textbuffer_notes.set_text(self.contact.notes.one().text) |
2272 | + if self.contact["birthday"] != "None": |
2273 | + birthdaylist = self.contact["birthday"].split("-") |
2274 | + self.combobox_month.set_active(int(birthdaylist[1])-1) |
2275 | + self.spinbutton_day.set_value(int(birthdaylist[2])) |
2276 | + self.spinbutton_year.set_value(int(birthdaylist[0])) |
2277 | + note = "" |
2278 | + for anote in self.GetNotes(int(self.contact["id"])): |
2279 | + if anote and anote.strip() != "": |
2280 | + note += (anote.strip() + "\n") |
2281 | + self.textbuffer_notes.set_text(note) |
2282 | |
2283 | - def set_contact_edit(self, contact, store): |
2284 | - """sets the contact model and edit attribute, gets a store object""" |
2285 | - assert type(contact) == models.Contact |
2286 | + def set_contact_edit(self, contact): |
2287 | + """sets the contact model and edit attribute""" |
2288 | self.contact = contact |
2289 | self.edit = True |
2290 | - self.store = store |
2291 | - |
2292 | - def set_store(self, store): |
2293 | - """gets a store object""" |
2294 | - self.store = store |
2295 | |
2296 | def run(self): |
2297 | self.dialog.show_all() |
2298 | |
2299 | === modified file 'dexter/frontend/utils.py' |
2300 | --- dexter/frontend/utils.py 2010-12-10 17:59:44 +0000 |
2301 | +++ dexter/frontend/utils.py 2010-12-20 23:23:53 +0000 |
2302 | @@ -1,18 +1,18 @@ |
2303 | -# Authors: |
2304 | -# Andrew Higginson |
2305 | -# |
2306 | -# This program is free software; you can redistribute it and/or modify it under |
2307 | -# the terms of the GNU General Public License as published by the Free Software |
2308 | -# Foundation; version 3. |
2309 | -# |
2310 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
2311 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
2312 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
2313 | -# details. |
2314 | -# |
2315 | -# You should have received a copy of the GNU General Public License along with |
2316 | -# this program; if not, write to the Free Software Foundation, Inc., |
2317 | -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
2318 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2319 | +### BEGIN LICENSE |
2320 | +# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com> |
2321 | +# This program is free software: you can redistribute it and/or modify it |
2322 | +# under the terms of the GNU General Public License version 3, as published |
2323 | +# by the Free Software Foundation. |
2324 | +# |
2325 | +# This program is distributed in the hope that it will be useful, but |
2326 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
2327 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2328 | +# PURPOSE. See the GNU General Public License for more details. |
2329 | +# |
2330 | +# You should have received a copy of the GNU General Public License along |
2331 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
2332 | +### END LICENSE |
2333 | |
2334 | import gtk |
2335 | import os |
2336 | |
2337 | === modified file 'dexter/frontend/widgets/entries.py' |
2338 | --- dexter/frontend/widgets/entries.py 2010-12-11 19:29:29 +0000 |
2339 | +++ dexter/frontend/widgets/entries.py 2010-12-20 23:23:53 +0000 |
2340 | @@ -62,7 +62,7 @@ |
2341 | |
2342 | def is_hinted(self): |
2343 | """check if this entry contains it's hint_string""" |
2344 | - if self.get_text() == self.hint_string: |
2345 | + if not self.get_text(): |
2346 | return True |
2347 | else: |
2348 | return False |
2349 | @@ -91,10 +91,11 @@ |
2350 | """A HintedEntry with the search logic built in""" |
2351 | __gtype_name__ = "SearchBar" |
2352 | |
2353 | - def __init__(self, hint_string, store): |
2354 | + def __init__(self, hint_string, dbus): |
2355 | """create a new SearchBar, data is the list of contacts to search""" |
2356 | HintedEntry.__init__(self, hint_string) |
2357 | - self.store = store |
2358 | + self.dbus = dbus |
2359 | + self.SearchContacts = self.dbus.get_dbus_method("SearchContacts") |
2360 | self.searching = False |
2361 | self.previous_search = "" |
2362 | self.hint() |
2363 | @@ -164,15 +165,7 @@ |
2364 | self.result_set = self.result_sets[text] |
2365 | else: # else if the string hasn't been searched yet, or the result sets cache has been emptied |
2366 | # if there is no result set already, search the entire contact list |
2367 | - if not self.result_set: |
2368 | - self.result_set = utilities.search_store(text, self.store) |
2369 | - elif len(text) < 3: |
2370 | - self.result_set = utilities.search_store(text, self.store) |
2371 | - elif self.previous_search in text: # just filter the previous results if the search is incremental |
2372 | - self.result_set = utilities.filter_results(text, |
2373 | - self.result_set) |
2374 | - else: |
2375 | - self.result_set = utilities.search_store(text, self.store) |
2376 | + self.result_set = self.SearchContacts(text) |
2377 | # cache the result set for later use |
2378 | self.result_sets[text] = self.result_set |
2379 | self.previous_search = text |
2380 | |
2381 | === removed file 'dexter/signaltest.py' |
2382 | --- dexter/signaltest.py 2010-12-13 20:18:13 +0000 |
2383 | +++ dexter/signaltest.py 1970-01-01 00:00:00 +0000 |
2384 | @@ -1,15 +0,0 @@ |
2385 | -import gtk |
2386 | -import dbus |
2387 | -from dbus.mainloop.glib import DBusGMainLoop |
2388 | - |
2389 | -DBusGMainLoop(set_as_default=True) |
2390 | - |
2391 | -def testing(new_id): |
2392 | - print new_id |
2393 | - |
2394 | -session_bus = dbus.SessionBus() |
2395 | -dexter_object = session_bus.get_object("org.elementary.dexterservice", |
2396 | - "/org/elementary/dexterservice") |
2397 | -dexter_object.connect_to_signal("ContactAdded", testing, 'org.elementary.dexterservice') |
2398 | - |
2399 | -gtk.main() |
2400 | |
2401 | === added file 'org.elementary.dexterserver.service' |
2402 | --- org.elementary.dexterserver.service 1970-01-01 00:00:00 +0000 |
2403 | +++ org.elementary.dexterserver.service 2010-12-20 23:23:53 +0000 |
2404 | @@ -0,0 +1,3 @@ |
2405 | +[D-BUS Service] |
2406 | +Name=org.elementary.dexterserver |
2407 | +Exec=/usr/bin/dexter-server |
2408 | |
2409 | === modified file 'po/dexter.pot' |
2410 | --- po/dexter.pot 2010-12-12 20:24:32 +0000 |
2411 | +++ po/dexter.pot 2010-12-20 23:23:53 +0000 |
2412 | @@ -8,7 +8,7 @@ |
2413 | msgstr "" |
2414 | "Project-Id-Version: PACKAGE VERSION\n" |
2415 | "Report-Msgid-Bugs-To: \n" |
2416 | -"POT-Creation-Date: 2010-12-12 13:17-0700\n" |
2417 | +"POT-Creation-Date: 2010-12-20 16:14-0700\n" |
2418 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
2419 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
2420 | "Language-Team: LANGUAGE <LL@li.org>\n" |
2421 | @@ -161,67 +161,67 @@ |
2422 | msgid "<b>Phones</b>" |
2423 | msgstr "" |
2424 | |
2425 | -#: ../bin/dexter.py:47 |
2426 | +#: ../bin/dexter.py:48 |
2427 | msgid "Show debug messages" |
2428 | msgstr "" |
2429 | |
2430 | -#: ../bin/dexter.py:50 |
2431 | +#: ../bin/dexter.py:51 |
2432 | msgid "Only show the New Contact dialog, then exit" |
2433 | msgstr "" |
2434 | |
2435 | -#: ../bin/dexter.py:53 |
2436 | -msgid "Start Dexter in daemon mode, for use with dbus" |
2437 | -msgstr "" |
2438 | - |
2439 | -#: ../dexter/app.py:149 |
2440 | +#: ../dexter/app.py:179 |
2441 | msgid "Type to search..." |
2442 | msgstr "" |
2443 | |
2444 | -#: ../dexter/app.py:359 |
2445 | +#: ../dexter/app.py:223 ../dexter/app.py:586 ../dexter/app.py:632 |
2446 | +msgid "Contacts" |
2447 | +msgstr "" |
2448 | + |
2449 | +#: ../dexter/app.py:416 |
2450 | msgid "vCards" |
2451 | msgstr "" |
2452 | |
2453 | -#: ../dexter/app.py:409 |
2454 | +#: ../dexter/app.py:435 |
2455 | +msgid "Importing Contacts..." |
2456 | +msgstr "" |
2457 | + |
2458 | +#: ../dexter/app.py:467 |
2459 | msgid "Are you sure you want to delete the selected contact?" |
2460 | msgstr "" |
2461 | |
2462 | -#: ../dexter/app.py:410 |
2463 | +#: ../dexter/app.py:468 |
2464 | msgid "You will not be able to recover this contact at a later date." |
2465 | msgstr "" |
2466 | |
2467 | -#: ../dexter/app.py:412 |
2468 | +#: ../dexter/app.py:470 |
2469 | msgid "Are you sure you want to delete the selected contacts?" |
2470 | msgstr "" |
2471 | |
2472 | -#: ../dexter/app.py:413 |
2473 | +#: ../dexter/app.py:471 |
2474 | msgid "You will not be able to recover these contacts at a later date." |
2475 | msgstr "" |
2476 | |
2477 | -#: ../dexter/app.py:538 ../dexter/app.py:586 |
2478 | -msgid "contacts" |
2479 | -msgstr "" |
2480 | - |
2481 | -#: ../dexter/app.py:547 |
2482 | +#: ../dexter/app.py:593 |
2483 | msgid "Could not import vCard file" |
2484 | msgstr "" |
2485 | |
2486 | -#: ../dexter/app.py:675 |
2487 | +#: ../dexter/app.py:736 |
2488 | msgid "Birthdate" |
2489 | msgstr "" |
2490 | |
2491 | -#: ../dexter/app.py:686 |
2492 | +#: ../dexter/app.py:748 |
2493 | msgid "Send email..." |
2494 | msgstr "" |
2495 | |
2496 | -#: ../dexter/app.py:712 |
2497 | +#: ../dexter/app.py:776 |
2498 | msgid "Address" |
2499 | msgstr "" |
2500 | |
2501 | -#: ../dexter/app.py:738 |
2502 | +#: ../dexter/app.py:802 |
2503 | msgid "Search on Google maps..." |
2504 | msgstr "" |
2505 | |
2506 | -#: ../dexter/app.py:744 |
2507 | +#: ../dexter/app.py:809 |
2508 | msgid "Note" |
2509 | msgstr "" |
2510 | |
2511 | @@ -233,6 +233,16 @@ |
2512 | msgid "translator-credits" |
2513 | msgstr "" |
2514 | |
2515 | +#. TODO: replace with proper |
2516 | +#: ../dexter/backend/utilities.py:164 |
2517 | +msgid "Could not import birthday" |
2518 | +msgstr "" |
2519 | + |
2520 | +#. TODO: replace with proper |
2521 | +#: ../dexter/backend/utilities.py:175 |
2522 | +msgid "couldn't add photo" |
2523 | +msgstr "" |
2524 | + |
2525 | #: ../data/ui/MergeDialog.ui.h:1 |
2526 | msgid " " |
2527 | msgstr "" |
2528 | @@ -247,16 +257,6 @@ |
2529 | msgid "Please select the correct piece of information below: " |
2530 | msgstr "" |
2531 | |
2532 | -#. TODO: replace with proper |
2533 | -#: ../dexter/backend/utilities.py:171 |
2534 | -msgid "Could not import birthday" |
2535 | -msgstr "" |
2536 | - |
2537 | -#. TODO: replace with proper |
2538 | -#: ../dexter/backend/utilities.py:182 |
2539 | -msgid "couldn't add photo" |
2540 | -msgstr "" |
2541 | - |
2542 | #: ../dexter/frontend/widgets/boxes.py:72 |
2543 | msgid "Street..." |
2544 | msgstr "" |
2545 | @@ -273,110 +273,110 @@ |
2546 | msgid "ZIP..." |
2547 | msgstr "" |
2548 | |
2549 | -#: ../dexter/frontend/dialogs/new_contact.py:65 |
2550 | +#: ../dexter/frontend/dialogs/new_contact.py:75 |
2551 | msgid "First..." |
2552 | msgstr "" |
2553 | |
2554 | -#: ../dexter/frontend/dialogs/new_contact.py:68 |
2555 | +#: ../dexter/frontend/dialogs/new_contact.py:78 |
2556 | msgid "Middle..." |
2557 | msgstr "" |
2558 | |
2559 | -#: ../dexter/frontend/dialogs/new_contact.py:70 |
2560 | +#: ../dexter/frontend/dialogs/new_contact.py:80 |
2561 | msgid "Last..." |
2562 | msgstr "" |
2563 | |
2564 | -#: ../dexter/frontend/dialogs/new_contact.py:72 |
2565 | +#: ../dexter/frontend/dialogs/new_contact.py:82 |
2566 | msgid "Organization..." |
2567 | msgstr "" |
2568 | |
2569 | #. FIXME: Store custom types in database |
2570 | -#: ../dexter/frontend/dialogs/new_contact.py:84 |
2571 | -#: ../dexter/frontend/dialogs/new_contact.py:90 |
2572 | -#: ../dexter/frontend/dialogs/new_contact.py:95 |
2573 | +#: ../dexter/frontend/dialogs/new_contact.py:94 |
2574 | +#: ../dexter/frontend/dialogs/new_contact.py:100 |
2575 | +#: ../dexter/frontend/dialogs/new_contact.py:105 |
2576 | msgid "Home" |
2577 | msgstr "" |
2578 | |
2579 | -#: ../dexter/frontend/dialogs/new_contact.py:84 |
2580 | -#: ../dexter/frontend/dialogs/new_contact.py:90 |
2581 | -#: ../dexter/frontend/dialogs/new_contact.py:95 |
2582 | +#: ../dexter/frontend/dialogs/new_contact.py:94 |
2583 | +#: ../dexter/frontend/dialogs/new_contact.py:100 |
2584 | +#: ../dexter/frontend/dialogs/new_contact.py:105 |
2585 | msgid "Work" |
2586 | msgstr "" |
2587 | |
2588 | -#: ../dexter/frontend/dialogs/new_contact.py:84 |
2589 | +#: ../dexter/frontend/dialogs/new_contact.py:94 |
2590 | msgid "Personal" |
2591 | msgstr "" |
2592 | |
2593 | -#: ../dexter/frontend/dialogs/new_contact.py:84 |
2594 | +#: ../dexter/frontend/dialogs/new_contact.py:94 |
2595 | msgid "School" |
2596 | msgstr "" |
2597 | |
2598 | -#: ../dexter/frontend/dialogs/new_contact.py:85 |
2599 | -#: ../dexter/frontend/dialogs/new_contact.py:90 |
2600 | -#: ../dexter/frontend/dialogs/new_contact.py:96 |
2601 | +#: ../dexter/frontend/dialogs/new_contact.py:95 |
2602 | +#: ../dexter/frontend/dialogs/new_contact.py:100 |
2603 | +#: ../dexter/frontend/dialogs/new_contact.py:106 |
2604 | msgid "Other" |
2605 | msgstr "" |
2606 | |
2607 | -#: ../dexter/frontend/dialogs/new_contact.py:95 |
2608 | +#: ../dexter/frontend/dialogs/new_contact.py:105 |
2609 | msgid "Mobile" |
2610 | msgstr "" |
2611 | |
2612 | -#: ../dexter/frontend/dialogs/new_contact.py:95 |
2613 | +#: ../dexter/frontend/dialogs/new_contact.py:105 |
2614 | msgid "Business" |
2615 | msgstr "" |
2616 | |
2617 | -#: ../dexter/frontend/dialogs/new_contact.py:102 |
2618 | +#: ../dexter/frontend/dialogs/new_contact.py:112 |
2619 | msgid "January" |
2620 | msgstr "" |
2621 | |
2622 | -#: ../dexter/frontend/dialogs/new_contact.py:102 |
2623 | +#: ../dexter/frontend/dialogs/new_contact.py:112 |
2624 | msgid "February" |
2625 | msgstr "" |
2626 | |
2627 | -#: ../dexter/frontend/dialogs/new_contact.py:102 |
2628 | +#: ../dexter/frontend/dialogs/new_contact.py:112 |
2629 | msgid "March" |
2630 | msgstr "" |
2631 | |
2632 | -#: ../dexter/frontend/dialogs/new_contact.py:102 |
2633 | +#: ../dexter/frontend/dialogs/new_contact.py:112 |
2634 | msgid "April" |
2635 | msgstr "" |
2636 | |
2637 | -#: ../dexter/frontend/dialogs/new_contact.py:103 |
2638 | +#: ../dexter/frontend/dialogs/new_contact.py:113 |
2639 | msgid "May" |
2640 | msgstr "" |
2641 | |
2642 | -#: ../dexter/frontend/dialogs/new_contact.py:103 |
2643 | +#: ../dexter/frontend/dialogs/new_contact.py:113 |
2644 | msgid "June" |
2645 | msgstr "" |
2646 | |
2647 | -#: ../dexter/frontend/dialogs/new_contact.py:103 |
2648 | +#: ../dexter/frontend/dialogs/new_contact.py:113 |
2649 | msgid "July" |
2650 | msgstr "" |
2651 | |
2652 | -#: ../dexter/frontend/dialogs/new_contact.py:103 |
2653 | +#: ../dexter/frontend/dialogs/new_contact.py:113 |
2654 | msgid "August" |
2655 | msgstr "" |
2656 | |
2657 | -#: ../dexter/frontend/dialogs/new_contact.py:103 |
2658 | +#: ../dexter/frontend/dialogs/new_contact.py:113 |
2659 | msgid "September" |
2660 | msgstr "" |
2661 | |
2662 | -#: ../dexter/frontend/dialogs/new_contact.py:104 |
2663 | +#: ../dexter/frontend/dialogs/new_contact.py:114 |
2664 | msgid "October" |
2665 | msgstr "" |
2666 | |
2667 | -#: ../dexter/frontend/dialogs/new_contact.py:104 |
2668 | +#: ../dexter/frontend/dialogs/new_contact.py:114 |
2669 | msgid "November" |
2670 | msgstr "" |
2671 | |
2672 | -#: ../dexter/frontend/dialogs/new_contact.py:104 |
2673 | +#: ../dexter/frontend/dialogs/new_contact.py:114 |
2674 | msgid "December" |
2675 | msgstr "" |
2676 | |
2677 | -#: ../dexter/frontend/dialogs/new_contact.py:141 |
2678 | +#: ../dexter/frontend/dialogs/new_contact.py:151 |
2679 | msgid "Number:" |
2680 | msgstr "" |
2681 | |
2682 | -#: ../dexter/frontend/dialogs/new_contact.py:155 |
2683 | +#: ../dexter/frontend/dialogs/new_contact.py:165 |
2684 | msgid "Address:" |
2685 | msgstr "" |
2686 | |
2687 | |
2688 | === modified file 'setup.py' |
2689 | --- setup.py 2010-12-10 03:12:04 +0000 |
2690 | +++ setup.py 2010-12-20 23:23:53 +0000 |
2691 | @@ -95,6 +95,7 @@ |
2692 | description='Address Book that goes hand in hand with Postler', |
2693 | long_description='Dexter is a very simple, easy to use address book, designed with the home user in mind. This personal contact manager integrates with Postler, and can import and export contacts in vCard format.', |
2694 | url='https://launchpad.net/dexter-rolodex', |
2695 | + data_files=[('share/dbus-1/services/', ['org.elementary.dexterserver.service'])], |
2696 | cmdclass={'install': InstallAndUpdateDataDirectory} |
2697 | ) |
2698 |